import Modal from './common/ResizableDraggableModal'; import React, { useState, useEffect } from "react"; import { Table, Alert, Progress, Button, Space } from 'antd'; import { CheckCircleOutlined, CloseCircleOutlined } from "@ant-design/icons"; import { PreviewImportFile, ImportDataWithProgress, } from "../../wailsjs/go/app/App"; import { EventsOn, EventsOff } from "../../wailsjs/runtime/runtime"; import { useStore } from "../store"; import { t as defaultTranslate } from "../i18n"; import { useOptionalI18n } from "../i18n/provider"; import { buildRpcConnectionConfig } from "../utils/connectionRpcConfig"; interface ImportPreviewModalProps { visible: boolean; filePath: string; connectionId: string; dbName: string; tableName: string; onClose: () => void; onSuccess: () => void; } interface PreviewData { columns: string[]; totalRows: number; previewRows: any[]; } interface ImportProgress { current: number; total: number; success: number; errors: number; totalRowsKnown?: boolean; } const ImportPreviewModal: React.FC = ({ visible, filePath, connectionId, dbName, tableName, onClose, onSuccess, }) => { const i18n = useOptionalI18n(); const t = i18n?.t ?? defaultTranslate; const connections = useStore((state) => state.connections); const [loading, setLoading] = useState(true); const [previewData, setPreviewData] = useState(null); const [error, setError] = useState(null); const [importing, setImporting] = useState(false); const [progress, setProgress] = useState(null); const [importResult, setImportResult] = useState(null); useEffect(() => { if (visible && filePath) { loadPreview(); } }, [visible, filePath]); useEffect(() => { if (importing) { const unsubscribe = EventsOn( "import:progress", (data: ImportProgress) => { setProgress((prev) => { const fallbackTotal = prev?.total || previewData?.totalRows || 0; const nextTotal = typeof data.total === "number" && data.total > 0 ? data.total : fallbackTotal; return { current: data.current ?? prev?.current ?? 0, total: nextTotal, success: data.success ?? prev?.success ?? 0, errors: data.errors ?? prev?.errors ?? 0, totalRowsKnown: data.totalRowsKnown ?? nextTotal > 0, }; }); }, ); return () => { unsubscribe?.(); EventsOff("import:progress"); }; } }, [importing, previewData?.totalRows]); const loadPreview = async () => { setLoading(true); setError(null); try { const res = await PreviewImportFile(filePath); if (res.success && res.data) { setPreviewData({ columns: res.data.columns || [], totalRows: res.data.totalRows || 0, previewRows: res.data.previewRows || [], }); } else { setError(res.message || t("import_preview.error.preview_failed")); } } catch (e: any) { setError( t("import_preview.error.preview_failed_detail", { detail: String(e?.message || e), }), ); } finally { setLoading(false); } }; const handleImport = async () => { if (!previewData) return; setImporting(true); setProgress({ current: 0, total: previewData.totalRows, success: 0, errors: 0, }); setImportResult(null); try { const conn = connections.find((c) => c.id === connectionId); if (!conn) { setError(t("import_preview.error.connection_config_not_found")); setImporting(false); return; } const config = { ...conn.config, port: Number(conn.config.port), password: conn.config.password || "", database: conn.config.database || "", useSSH: conn.config.useSSH || false, ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "", }, }; const res = await ImportDataWithProgress( buildRpcConnectionConfig(config) as any, dbName, tableName, filePath, ); if (res.success && res.data) { setImportResult(res.data); if (res.data.failed === 0) { onSuccess(); } } else { setError(res.message || t("import_preview.error.import_failed")); } } catch (e: any) { setError( t("import_preview.error.import_failed_detail", { detail: String(e?.message || e), }), ); } finally { setImporting(false); } }; const columns = previewData?.columns.map((col) => ({ title: col, dataIndex: col, key: col, ellipsis: true, width: 150, })) || []; const progressPercent = progress && progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0; return ( ) : importing ? null : ( ) } > {error && ( )} {loading && (
{t("import_preview.status.loading_preview")}
)} {!loading && previewData && !importing && !importResult && ( <>
{t("import_preview.preview.field_list")}
{previewData.columns.join(", ")}
{t("import_preview.preview.table_title")}
)} {importing && progress && (
{t("import_preview.status.importing")}
{t("import_preview.progress.processed_rows", { current: progress.current, total: progress.total, })} {" "} {t("import_preview.progress.success_count", { count: progress.success, })} {progress.errors > 0 && ( {" "} {t("import_preview.progress.error_count", { count: progress.errors, })} )}
)} {importResult && (
{t("import_preview.result.success_rows", { count: importResult.success, })}
{importResult.failed > 0 && (
{t("import_preview.result.failed_rows", { count: importResult.failed, })}
)}
} showIcon style={{ marginBottom: 16 }} /> {importResult.errorLogs && importResult.errorLogs.length > 0 && ( <>
{t("import_preview.result.error_logs")}
{importResult.errorLogs.map((log: string, idx: number) => (
{log}
))}
)} )} ); }; export default ImportPreviewModal;