223 lines
6.4 KiB
TypeScript
223 lines
6.4 KiB
TypeScript
/**
|
|
* Global Import/Export Configuration System
|
|
* Provides reusable import/export configs for table pages
|
|
*
|
|
* Usage:
|
|
* ```typescript
|
|
* import { useImportExport } from '@/config/import-export.config';
|
|
*
|
|
* const { handleExport, handleImport, ImportModal } = useImportExport({
|
|
* exportEndpoint: '/v1/planner/keywords/export/',
|
|
* importEndpoint: '/v1/planner/keywords/import_keywords/',
|
|
* filename: 'keywords',
|
|
* formats: ['csv', 'json'],
|
|
* onImportSuccess: () => loadData(),
|
|
* });
|
|
* ```
|
|
*/
|
|
|
|
import React, { useState, useCallback } from 'react';
|
|
import { exportTableData, importTableData, ExportConfig, ImportConfig } from '../utils/table-import-export';
|
|
import { Modal } from '../components/ui/modal';
|
|
import Button from '../components/ui/button/Button';
|
|
import FileInput from '../components/form/input/FileInput';
|
|
import Label from '../components/form/Label';
|
|
|
|
export interface ImportExportConfigOptions {
|
|
exportEndpoint: string;
|
|
importEndpoint: string;
|
|
filename: string;
|
|
formats?: ('csv' | 'json')[];
|
|
acceptedFormats?: string[];
|
|
maxFileSize?: number;
|
|
importQueryParams?: Record<string, any>; // Query params for import (e.g., site_id, sector_id)
|
|
onImportSuccess?: (result: any) => void;
|
|
onExportSuccess?: () => void;
|
|
onError?: (error: Error) => void;
|
|
}
|
|
|
|
export interface ImportExportHandlers {
|
|
handleExport: (format?: 'csv' | 'json', filters?: Record<string, any>) => Promise<void>;
|
|
handleImportClick: () => void;
|
|
ImportModal: React.FC;
|
|
}
|
|
|
|
/**
|
|
* React hook for import/export functionality
|
|
*/
|
|
export function useImportExport(
|
|
options: ImportExportConfigOptions
|
|
): ImportExportHandlers {
|
|
const {
|
|
exportEndpoint,
|
|
importEndpoint,
|
|
filename,
|
|
formats = ['csv'],
|
|
acceptedFormats = ['.csv'],
|
|
maxFileSize = 5 * 1024 * 1024, // 5MB default
|
|
onImportSuccess,
|
|
onExportSuccess,
|
|
onError,
|
|
} = options;
|
|
|
|
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
const handleExport = useCallback(async (
|
|
format: 'csv' | 'json' = 'csv',
|
|
filters: Record<string, any> = {}
|
|
) => {
|
|
setIsProcessing(true);
|
|
try {
|
|
const exportConfig: ExportConfig = {
|
|
endpoint: exportEndpoint,
|
|
filename,
|
|
format,
|
|
filters,
|
|
};
|
|
|
|
await exportTableData(
|
|
exportConfig,
|
|
(progress) => console.log(progress),
|
|
(error) => {
|
|
onError?.(error);
|
|
throw error;
|
|
}
|
|
);
|
|
|
|
onExportSuccess?.();
|
|
} catch (error) {
|
|
const err = error instanceof Error ? error : new Error('Export failed');
|
|
onError?.(err);
|
|
throw err;
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
}, [exportEndpoint, filename, onError, onExportSuccess]);
|
|
|
|
const handleImport = useCallback(async (file: File) => {
|
|
setIsProcessing(true);
|
|
try {
|
|
const importConfig: ImportConfig = {
|
|
endpoint: importEndpoint,
|
|
acceptedFormats,
|
|
maxFileSize,
|
|
queryParams: options.importQueryParams,
|
|
onSuccess: (result) => {
|
|
onImportSuccess?.(result);
|
|
},
|
|
};
|
|
|
|
const result = await importTableData(
|
|
file,
|
|
importConfig,
|
|
(progress) => console.log(progress),
|
|
(error) => {
|
|
onError?.(error);
|
|
throw error;
|
|
}
|
|
);
|
|
|
|
setIsImportModalOpen(false);
|
|
return result;
|
|
} catch (error) {
|
|
const err = error instanceof Error ? error : new Error('Import failed');
|
|
onError?.(err);
|
|
throw err;
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
}, [importEndpoint, acceptedFormats, maxFileSize, onImportSuccess, onError]);
|
|
|
|
const handleImportFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
await handleImport(file);
|
|
// Reset file input
|
|
e.target.value = '';
|
|
}, [handleImport]);
|
|
|
|
const ImportModalComponent: React.FC = () => (
|
|
<Modal
|
|
isOpen={isImportModalOpen}
|
|
onClose={() => setIsImportModalOpen(false)}
|
|
className="max-w-md"
|
|
>
|
|
<div className="p-6">
|
|
<h2 className="text-xl font-bold mb-6 text-gray-800 dark:text-white">
|
|
Import {filename.charAt(0).toUpperCase() + filename.slice(1)}
|
|
</h2>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label>CSV File</Label>
|
|
<FileInput
|
|
onChange={handleImportFileChange}
|
|
accept={acceptedFormats.join(',')}
|
|
disabled={isProcessing}
|
|
/>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
|
Upload a CSV file (max {maxFileSize / 1024 / 1024}MB)
|
|
</p>
|
|
{filename === 'keywords' && (
|
|
<p className="text-xs text-gray-600 dark:text-gray-300 mt-2 p-2 bg-brand-50 dark:bg-brand-900/20 rounded border border-brand-200 dark:border-brand-800">
|
|
<strong>Expected columns:</strong> keyword, volume, difficulty, country, status
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-4 pt-4">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setIsImportModalOpen(false)}
|
|
disabled={isProcessing}
|
|
>
|
|
Close
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
|
|
return {
|
|
handleExport,
|
|
handleImportClick: () => setIsImportModalOpen(true),
|
|
ImportModal: ImportModalComponent,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Pre-configured import/export hooks for common modules
|
|
*/
|
|
export const useKeywordsImportExport = (
|
|
onImportSuccess?: () => void,
|
|
onError?: (error: Error) => void,
|
|
importQueryParams?: Record<string, any>
|
|
) => {
|
|
return useImportExport({
|
|
exportEndpoint: '/v1/planner/keywords/export/',
|
|
importEndpoint: '/v1/planner/keywords/import_keywords/',
|
|
filename: 'keywords',
|
|
formats: ['csv', 'json'],
|
|
acceptedFormats: ['.csv'],
|
|
maxFileSize: 5 * 1024 * 1024,
|
|
importQueryParams,
|
|
onImportSuccess,
|
|
onError,
|
|
});
|
|
};
|
|
|
|
export const useClustersImportExport = (onImportSuccess?: () => void, onError?: (error: Error) => void) => {
|
|
return useImportExport({
|
|
exportEndpoint: '/v1/planner/clusters/export/',
|
|
importEndpoint: '/v1/planner/clusters/import_clusters/',
|
|
filename: 'clusters',
|
|
formats: ['csv', 'json'],
|
|
acceptedFormats: ['.csv'],
|
|
maxFileSize: 5 * 1024 * 1024,
|
|
onImportSuccess,
|
|
onError,
|
|
});
|
|
};
|