"use client"; import * as React from "react"; import { useParams, useRouter } from "next/navigation"; import { useTranslation } from "@/i18n/client"; import { ClientDataTable } from "../client-data-table/data-table"; import { getColumns, DataTableRowAction, DataTableColumnJSON, ColumnType, } from "./form-data-table-columns"; import type { DataTableAdvancedFilterField } from "@/types/table"; import { Button } from "../ui/button"; import { Download, Loader, Save, Upload, Plus, Tag, TagsIcon, FileText, FileSpreadsheet, FileOutput, Clipboard, Send } from "lucide-react"; import { toast } from "sonner"; import { getReportTempList, sendFormDataToSEDP, syncMissingTags, updateFormDataInDB, } from "@/lib/forms/services"; import { UpdateTagSheet } from "./update-form-sheet"; import { FormDataReportTempUploadDialog } from "./form-data-report-temp-upload-dialog"; import { FormDataReportDialog } from "./form-data-report-dialog"; import { FormDataReportBatchDialog } from "./form-data-report-batch-dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; import { AddFormTagDialog } from "./add-formTag-dialog"; import { importExcelData } from "./import-excel-form"; import { exportExcelData } from "./export-excel-form"; import { SEDPConfirmationDialog, SEDPStatusDialog } from "./sedp-components"; interface GenericData { [key: string]: any; } export interface DynamicTableProps { dataJSON: GenericData[]; columnsJSON: DataTableColumnJSON[]; contractItemId: number; formCode: string; formId: number; projectId: number; formName?: string; objectCode?: string; } export default function DynamicTable({ dataJSON, columnsJSON, contractItemId, formCode, formId, projectId, formName = `VD)${formCode}`, // Default form name based on formCode objectCode = "LO_PT_CLAS", // Default object code }: DynamicTableProps) { const params = useParams(); const router = useRouter(); const lng = (params?.lng as string) || "ko"; const { t } = useTranslation(lng, "translation"); const [rowAction, setRowAction] = React.useState | null>(null); const [tableData, setTableData] = React.useState(dataJSON); console.log(tableData) console.log(columnsJSON) // Update tableData when dataJSON changes React.useEffect(() => { setTableData(dataJSON); }, [dataJSON]); // Separate loading states for different operations const [isSyncingTags, setIsSyncingTags] = React.useState(false); const [isImporting, setIsImporting] = React.useState(false); const [isExporting, setIsExporting] = React.useState(false); const [isSaving, setIsSaving] = React.useState(false); const [isSendingSEDP, setIsSendingSEDP] = React.useState(false); // Any operation in progress const isAnyOperationPending = isSyncingTags || isImporting || isExporting || isSaving || isSendingSEDP; // SEDP dialogs state const [sedpConfirmOpen, setSedpConfirmOpen] = React.useState(false); const [sedpStatusOpen, setSedpStatusOpen] = React.useState(false); const [sedpStatusData, setSedpStatusData] = React.useState({ status: 'success' as 'success' | 'error' | 'partial', message: '', successCount: 0, errorCount: 0, totalCount: 0 }); const [tempUpDialog, setTempUpDialog] = React.useState(false); const [reportData, setReportData] = React.useState([]); const [batchDownDialog, setBatchDownDialog] = React.useState(false); const [tempCount, setTempCount] = React.useState(0); const [addTagDialogOpen, setAddTagDialogOpen] = React.useState(false); React.useEffect(() => { const getTempCount = async () => { const tempList = await getReportTempList(contractItemId, formId); setTempCount(tempList.length); }; getTempCount(); }, [contractItemId, formId, tempUpDialog]); const columns = React.useMemo( () => getColumns({ columnsJSON, setRowAction, setReportData, tempCount }), [columnsJSON, setRowAction, setReportData, tempCount] ); function mapColumnTypeToAdvancedFilterType( columnType: ColumnType ): DataTableAdvancedFilterField["type"] { switch (columnType) { case "STRING": return "text"; case "NUMBER": return "number"; case "LIST": return "select"; default: return "text"; } } const advancedFilterFields = React.useMemo< DataTableAdvancedFilterField[] >(() => { return columnsJSON.map((col) => ({ id: col.key, label: col.label, type: mapColumnTypeToAdvancedFilterType(col.type), options: col.type === "LIST" ? col.options?.map((v) => ({ label: v, value: v })) : undefined, })); }, [columnsJSON]); // 태그 불러오기 async function handleSyncTags() { try { setIsSyncingTags(true); const result = await syncMissingTags(contractItemId, formCode); // Prepare the toast messages based on what changed const changes = []; if (result.createdCount > 0) changes.push(`${result.createdCount}건 태그 생성`); if (result.updatedCount > 0) changes.push(`${result.updatedCount}건 태그 업데이트`); if (result.deletedCount > 0) changes.push(`${result.deletedCount}건 태그 삭제`); if (changes.length > 0) { // If any changes were made, show success message and reload toast.success(`동기화 완료: ${changes.join(", ")}`); router.refresh(); // Use router.refresh instead of location.reload } else { // If no changes were made, show an info message toast.info("변경사항이 없습니다. 모든 태그가 최신 상태입니다."); } } catch (err) { console.error(err); toast.error("태그 동기화 중 에러가 발생했습니다."); } finally { setIsSyncingTags(false); } } // Excel Import - Modified to directly save to DB async function handleImportExcel(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; try { setIsImporting(true); // Call the updated importExcelData function with direct save capability const result = await importExcelData({ file, tableData, columnsJSON, formCode, // Pass formCode for direct save contractItemId, // Pass contractItemId for direct save onPendingChange: setIsImporting, onDataUpdate: (newData) => { // This is called only after successful DB save setTableData(Array.isArray(newData) ? newData : newData(tableData)); } }); // If import and save was successful, refresh the page if (result.success) { router.refresh(); } } catch (error) { console.error("Import failed:", error); toast.error("Failed to import Excel data"); } finally { // Always clear the file input value e.target.value = ""; setIsImporting(false); } } // SEDP Send handler (with confirmation) function handleSEDPSendClick() { if (tableData.length === 0) { toast.error("No data to send to SEDP"); return; } // Open confirmation dialog setSedpConfirmOpen(true); } // Actual SEDP send after confirmation // In your DynamicTable component, update the handler for SEDP sending async function handleSEDPSendConfirmed() { try { setIsSendingSEDP(true); // Validate data const invalidData = tableData.filter((item) => !item.TAG_NO?.trim()); if (invalidData.length > 0) { toast.error(`태그 번호가 없는 항목이 ${invalidData.length}개 있습니다.`); setSedpConfirmOpen(false); return; } // Then send to SEDP - pass formCode instead of formName const sedpResult = await sendFormDataToSEDP( formCode, // Send formCode instead of formName projectId, // Project ID tableData, // Table data columnsJSON // Column definitions ); // Close confirmation dialog setSedpConfirmOpen(false); // Set status data based on result if (sedpResult.success) { setSedpStatusData({ status: 'success', message: "Data successfully sent to SEDP", successCount: tableData.length, errorCount: 0, totalCount: tableData.length }); } else { setSedpStatusData({ status: 'error', message: sedpResult.message || "Failed to send data to SEDP", successCount: 0, errorCount: tableData.length, totalCount: tableData.length }); } // Open status dialog to show result setSedpStatusOpen(true); // Refresh the route to get fresh data router.refresh(); } catch (err: any) { console.error("SEDP error:", err); // Set error status setSedpStatusData({ status: 'error', message: err.message || "An unexpected error occurred", successCount: 0, errorCount: tableData.length, totalCount: tableData.length }); // Close confirmation and open status setSedpConfirmOpen(false); setSedpStatusOpen(true); } finally { setIsSendingSEDP(false); } } // Template Export async function handleExportExcel() { try { setIsExporting(true); await exportExcelData({ tableData, columnsJSON, formCode, onPendingChange: setIsExporting }); } finally { setIsExporting(false); } } // Handle batch document check const handleBatchDocument = () => { if (tempCount > 0) { setBatchDownDialog(true); } else { toast.error("업로드된 Template File이 없습니다."); } }; return ( <> {/* 버튼 그룹 */}
{/* 태그 관리 드롭다운 */} Sync Tags setAddTagDialogOpen(true)} disabled={isAnyOperationPending}> Add Tags {/* 리포트 관리 드롭다운 */} setTempUpDialog(true)} disabled={isAnyOperationPending}> Upload Template Batch Document {/* IMPORT 버튼 (파일 선택) */} {/* EXPORT 버튼 */} {/* SEDP 전송 버튼 */}
{/* Modal dialog for tag update */} { if (!open) setRowAction(null); }} columns={columnsJSON} rowData={rowAction?.row.original ?? null} formCode={formCode} contractItemId={contractItemId} onUpdateSuccess={(updatedValues) => { // Update the specific row in tableData when a single row is updated if (rowAction?.row.original?.TAG_NO) { const tagNo = rowAction.row.original.TAG_NO; setTableData(prev => prev.map(item => item.TAG_NO === tagNo ? updatedValues : item ) ); } }} /> {/* Dialog for adding tags */} {/* SEDP Confirmation Dialog */} setSedpConfirmOpen(false)} onConfirm={handleSEDPSendConfirmed} formName={formName} tagCount={tableData.length} isLoading={isSendingSEDP} /> {/* SEDP Status Dialog */} setSedpStatusOpen(false)} status={sedpStatusData.status} message={sedpStatusData.message} successCount={sedpStatusData.successCount} errorCount={sedpStatusData.errorCount} totalCount={sedpStatusData.totalCount} /> {/* Other dialogs */} {tempUpDialog && ( )} {reportData.length > 0 && ( )} {batchDownDialog && ( )} ); }