From bac0228d21b7195065e9cddcc327ae33659c7bcc Mon Sep 17 00:00:00 2001 From: dujinkim Date: Sun, 1 Jun 2025 13:52:21 +0000 Subject: (대표님) 20250601까지 작업사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/form-data/form-data-table.tsx | 265 ++++++++++++++++++++----------- 1 file changed, 172 insertions(+), 93 deletions(-) (limited to 'components/form-data/form-data-table.tsx') diff --git a/components/form-data/form-data-table.tsx b/components/form-data/form-data-table.tsx index 6de6dd0b..9a438957 100644 --- a/components/form-data/form-data-table.tsx +++ b/components/form-data/form-data-table.tsx @@ -13,21 +13,22 @@ import { } 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, +import { + Download, + Loader, + Save, + Upload, + Plus, + Tag, + TagsIcon, + FileText, FileSpreadsheet, FileOutput, - Clipboard, + Clipboard, Send, GitCompareIcon, - RefreshCcw + RefreshCcw, + Trash2 } from "lucide-react"; import { toast } from "sonner"; import { @@ -54,16 +55,17 @@ import { exportExcelData } from "./export-excel-form"; import { SEDPConfirmationDialog, SEDPStatusDialog } from "./sedp-components"; import { SEDPCompareDialog } from "./sedp-compare-dialog"; import { getSEDPToken } from "@/lib/sedp/sedp-token"; +import { DeleteFormDataDialog } from "./delete-form-data-dialog"; // 새로 추가 async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Promise { try { // Get the token const apiKey = await getSEDPToken(); - + // Define the API base URL const SEDP_API_BASE_URL = process.env.SEDP_API_BASE_URL || 'http://sedpwebapi.ship.samsung.co.kr/api'; - + // Make the API call const response = await fetch( `${SEDP_API_BASE_URL}/Data/GetPubData`, @@ -82,12 +84,12 @@ async function fetchTagDataFromSEDP(projectCode: string, formCode: string): Prom }) } ); - + if (!response.ok) { const errorText = await response.text(); throw new Error(`SEDP API request failed: ${response.status} ${response.statusText} - ${errorText}`); } - + const data = await response.json(); return data; } catch (error: any) { @@ -119,7 +121,7 @@ export default function DynamicTable({ contractItemId, formCode, formId, - projectId, + projectId, mode = "IM", // 기본값 설정 formName = `${formCode}`, // Default form name based on formCode editableFieldsMap = new Map(), // 새로 추가 @@ -134,19 +136,21 @@ export default function DynamicTable({ const [tableData, setTableData] = React.useState(dataJSON); // 배치 선택 관련 상태 - const [selectedRows, setSelectedRows] = React.useState>({}); + const [selectedRowsData, setSelectedRowsData] = React.useState([]); + const [clearSelection, setClearSelection] = React.useState(false); + // 삭제 관련 상태 간소화 + const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); + const [deleteTarget, setDeleteTarget] = React.useState([]); // Update tableData when dataJSON changes React.useEffect(() => { setTableData(dataJSON); - // 데이터가 변경되면 선택 상태 초기화 - setSelectedRows({}); }, [dataJSON]); - + // 폴링 상태 관리를 위한 ref const pollingRef = React.useRef(null); const [syncId, setSyncId] = React.useState(null); - + // Separate loading states for different operations const [isSyncingTags, setIsSyncingTags] = React.useState(false); const [isImporting, setIsImporting] = React.useState(false); @@ -154,10 +158,10 @@ export default function DynamicTable({ const [isSaving, setIsSaving] = React.useState(false); const [isSendingSEDP, setIsSendingSEDP] = React.useState(false); const [isLoadingTags, setIsLoadingTags] = React.useState(false); - + // Any operation in progress const isAnyOperationPending = isSyncingTags || isImporting || isExporting || isSaving || isSendingSEDP || isLoadingTags; - + // SEDP dialogs state const [sedpConfirmOpen, setSedpConfirmOpen] = React.useState(false); const [sedpStatusOpen, setSedpStatusOpen] = React.useState(false); @@ -168,11 +172,11 @@ export default function DynamicTable({ errorCount: 0, totalCount: 0 }); - + // SEDP compare dialog state const [sedpCompareOpen, setSedpCompareOpen] = React.useState(false); const [projectCode, setProjectCode] = React.useState(''); - + const [tempUpDialog, setTempUpDialog] = React.useState(false); const [reportData, setReportData] = React.useState([]); const [batchDownDialog, setBatchDownDialog] = React.useState(false); @@ -216,26 +220,22 @@ export default function DynamicTable({ // 선택된 행들의 실제 데이터 가져오기 const getSelectedRowsData = React.useCallback(() => { - const selectedIndices = Object.keys(selectedRows).filter(key => selectedRows[key]); - return selectedIndices.map(index => tableData[parseInt(index)]).filter(Boolean); - }, [selectedRows, tableData]); + return selectedRowsData; + }, [selectedRowsData]); // 선택된 행 개수 계산 const selectedRowCount = React.useMemo(() => { - return Object.values(selectedRows).filter(Boolean).length; - }, [selectedRows]); + return selectedRowsData.length; + }, [selectedRowsData]); const columns = React.useMemo( - () => getColumns({ - columnsJSON, - setRowAction, - setReportData, + () => getColumns({ + columnsJSON, + setRowAction, + setReportData, tempCount, - selectedRows, - onRowSelectionChange: setSelectedRows, - editableFieldsMap }), - [columnsJSON, setRowAction, setReportData, tempCount, selectedRows] + [columnsJSON, setRowAction, setReportData, tempCount] ); function mapColumnTypeToAdvancedFilterType( @@ -297,30 +297,30 @@ export default function DynamicTable({ setIsSyncingTags(false); } } - + // ENG 모드: 태그 가져오기 함수 const handleGetTags = async () => { try { setIsLoadingTags(true); - + // API 엔드포인트 호출 - 작업 시작만 요청 const response = await fetch('/api/cron/form-tags/start', { method: 'POST', - body: JSON.stringify({ projectCode ,formCode ,contractItemId }) + body: JSON.stringify({ projectCode, formCode, contractItemId }) }); - + if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Failed to start tag import'); } - + const data = await response.json(); - + // 작업 ID 저장 if (data.syncId) { setSyncId(data.syncId); toast.info('Tag import started. This may take a while...'); - + // 상태 확인을 위한 폴링 시작 startPolling(data.syncId); } else { @@ -336,49 +336,49 @@ export default function DynamicTable({ setIsLoadingTags(false); } }; - + const startPolling = (id: string) => { // 이전 폴링이 있다면 제거 if (pollingRef.current) { clearInterval(pollingRef.current); } - + // 5초마다 상태 확인 pollingRef.current = setInterval(async () => { try { const response = await fetch(`/api/cron/form-tags/status?id=${id}`); - + if (!response.ok) { throw new Error('Failed to get tag import status'); } - + const data = await response.json(); - + if (data.status === 'completed') { // 폴링 중지 if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } - + router.refresh(); - + // 상태 초기화 setIsLoadingTags(false); setSyncId(null); - + // 성공 메시지 표시 toast.success( `Tags imported successfully! ${data.result?.processedCount || 0} items processed.` ); - + } else if (data.status === 'failed') { // 에러 처리 if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } - + setIsLoadingTags(false); setSyncId(null); toast.error(data.error || 'Import failed'); @@ -395,15 +395,16 @@ export default function DynamicTable({ } }, 5000); // 5초마다 체크 }; - - // Excel Import - Modified to directly save to DB + + // Excel Import - Fixed version with proper loading state management async function handleImportExcel(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; - + try { - setIsImporting(true); - + // Don't set setIsImporting here - let importExcelData handle it completely + // setIsImporting(true); // Remove this line + // Call the updated importExcelData function with editableFieldsMap const result = await importExcelData({ file, @@ -411,28 +412,37 @@ export default function DynamicTable({ columnsJSON, formCode, contractItemId, - editableFieldsMap, // 추가: 편집 가능 필드 정보 전달 - onPendingChange: setIsImporting, + // editableFieldsMap, // 추가: 편집 가능 필드 정보 전달 + onPendingChange: setIsImporting, // Let importExcelData handle loading state onDataUpdate: (newData) => { setTableData(Array.isArray(newData) ? newData : newData(tableData)); } }); - + // If import and save was successful, refresh the page if (result.success) { // Show additional info about skipped fields if any if (result.skippedFields && result.skippedFields.length > 0) { console.log("Import completed with some fields skipped:", result.skippedFields); } - router.refresh(); + + // Ensure loading state is cleared before refresh + setIsImporting(false); + + // Add a small delay to ensure state update is processed + setTimeout(() => { + router.refresh(); + }, 100); } } catch (error) { console.error("Import failed:", error); toast.error("Failed to import Excel data"); + // Ensure loading state is cleared on error + setIsImporting(false); } finally { // Always clear the file input value e.target.value = ""; - setIsImporting(false); + // Don't set setIsImporting(false) here since we handle it above } } // SEDP Send handler (with confirmation) @@ -441,23 +451,23 @@ export default function DynamicTable({ toast.error("No data to send to SEDP"); return; } - + // Open confirmation dialog setSedpConfirmOpen(true); } - + // Handle SEDP compare button click function handleSEDPCompareClick() { if (tableData.length === 0) { toast.error("No data to compare with SEDP"); return; } - + if (!projectCode) { toast.error("Project code is not available"); return; } - + // Open compare dialog setSedpCompareOpen(true); } @@ -466,7 +476,7 @@ export default function DynamicTable({ async function handleSEDPSendConfirmed() { try { setIsSendingSEDP(true); - + // Validate data const invalidData = tableData.filter((item) => !item.TAG_NO?.trim()); if (invalidData.length > 0) { @@ -479,13 +489,14 @@ export default function DynamicTable({ const sedpResult = await sendFormDataToSEDP( formCode, // Send formCode instead of formName projectId, // Project ID + contractItemId, tableData, // Table data columnsJSON // Column definitions ); // Close confirmation dialog setSedpConfirmOpen(false); - + // Set status data based on result if (sedpResult.success) { setSedpStatusData({ @@ -504,16 +515,16 @@ export default function DynamicTable({ 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', @@ -522,16 +533,16 @@ export default function DynamicTable({ errorCount: tableData.length, totalCount: tableData.length }); - + // Close confirmation and open status setSedpConfirmOpen(false); setSedpStatusOpen(true); - + } finally { setIsSendingSEDP(false); } } - + // Template Export async function handleExportExcel() { try { @@ -561,24 +572,76 @@ export default function DynamicTable({ } else { toast.info(`전체 ${tableData.length}개 항목으로 배치 문서를 생성합니다.`); } - + setBatchDownDialog(true); }; + // 개별 행 삭제 핸들러 + const handleDeleteRow = (rowData: GenericData) => { + setDeleteTarget([rowData]); + setDeleteDialogOpen(true); + }; + + // 배치 삭제 핸들러 + const handleBatchDelete = () => { + const selectedData = getSelectedRowsData(); + if (selectedData.length === 0) { + toast.error("삭제할 항목을 선택해주세요."); + return; + } + + setDeleteTarget(selectedData); + setDeleteDialogOpen(true); + }; + + // 삭제 성공 후 처리 + const handleDeleteSuccess = () => { + // 로컬 상태에서 삭제된 항목들 제거 + const tagNosToDelete = deleteTarget + .map(item => item.TAG_NO) + .filter(Boolean); + + setTableData(prev => + prev.filter(item => !tagNosToDelete.includes(item.TAG_NO)) + ); + + // 선택 상태 초기화 + setSelectedRowsData([]); + setClearSelection(prev => !prev); // ClientDataTable의 선택 상태 초기화 + + // 삭제 타겟 초기화 + setDeleteTarget([]); + }; + + // rowAction 처리 부분 수정 + React.useEffect(() => { + if (rowAction?.type === "delete") { + handleDeleteRow(rowAction.row.original); + setRowAction(null); // 액션 초기화 + } + }, [rowAction]); + + return ( <> {/* 선택된 항목 수 표시 (선택된 항목이 있을 때만) */} {selectedRowCount > 0 && ( -
-

- {selectedRowCount}개 항목이 선택되었습니다. 배치 문서는 선택된 항목만으로 생성됩니다. -

-
+ )} {/* 버튼 그룹 */} @@ -613,7 +676,7 @@ export default function DynamicTable({ - + {/* 리포트 관리 드롭다운 */} @@ -706,6 +769,7 @@ export default function DynamicTable({
+ {/* Modal dialog for tag update */} {/* Modal dialog for tag update */} { // 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 => + setTableData(prev => + prev.map(item => item.TAG_NO === tagNo ? updatedValues : item ) ); } }} /> - + { + if (!open) { + setDeleteDialogOpen(false); + setDeleteTarget([]); + } + }} + onSuccess={handleDeleteSuccess} + showTrigger={false} + /> + {/* Dialog for adding tags */} - - + {/* SEDP Confirmation Dialog */} - + {/* SEDP Status Dialog */} - + {/* SEDP Compare Dialog */} - + {/* Other dialogs */} {tempUpDialog && (