From bac0228d21b7195065e9cddcc327ae33659c7bcc Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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<any> {
   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<GenericData[]>(dataJSON);
 
   // 배치 선택 관련 상태
-  const [selectedRows, setSelectedRows] = React.useState<Record<string, boolean>>({});
+  const [selectedRowsData, setSelectedRowsData] = React.useState<GenericData[]>([]);
+  const [clearSelection, setClearSelection] = React.useState(false);
+  // 삭제 관련 상태 간소화
+  const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
+  const [deleteTarget, setDeleteTarget] = React.useState<GenericData[]>([]);
 
   // Update tableData when dataJSON changes
   React.useEffect(() => {
     setTableData(dataJSON);
-    // 데이터가 변경되면 선택 상태 초기화
-    setSelectedRows({});
   }, [dataJSON]);
-  
+
   // 폴링 상태 관리를 위한 ref
   const pollingRef = React.useRef<NodeJS.Timeout | null>(null);
   const [syncId, setSyncId] = React.useState<string | null>(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<string>('');
-  
+
   const [tempUpDialog, setTempUpDialog] = React.useState(false);
   const [reportData, setReportData] = React.useState<GenericData[]>([]);
   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<GenericData>({ 
-      columnsJSON, 
-      setRowAction, 
-      setReportData, 
+    () => getColumns<GenericData>({
+      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<HTMLInputElement>) {
     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 (
     <>
       <ClientDataTable
         data={tableData}
         columns={columns}
         advancedFilterFields={advancedFilterFields}
+        autoSizeColumns
+        onSelectedRowsChange={setSelectedRowsData}
+        clearSelection={clearSelection}
       >
         {/* 선택된 항목 수 표시 (선택된 항목이 있을 때만) */}
         {selectedRowCount > 0 && (
-          <div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-md">
-            <p className="text-sm text-blue-700">
-              {selectedRowCount}개 항목이 선택되었습니다. 배치 문서는 선택된 항목만으로 생성됩니다.
-            </p>
-          </div>
+          <Button
+            variant="destructive"
+            size="sm"
+            onClick={handleBatchDelete}
+          >
+            <Trash2 className="mr-2 size-4" />
+            Delete ({selectedRowCount})
+          </Button>
         )}
 
         {/* 버튼 그룹 */}
@@ -613,7 +676,7 @@ export default function DynamicTable({
               </DropdownMenuItem>
             </DropdownMenuContent>
           </DropdownMenu>
-          
+
           {/* 리포트 관리 드롭다운 */}
           <DropdownMenu>
             <DropdownMenuTrigger asChild>
@@ -706,6 +769,7 @@ export default function DynamicTable({
         </div>
       </ClientDataTable>
 
+      {/* Modal dialog for tag update */}
       {/* Modal dialog for tag update */}
       <UpdateTagSheet
         open={rowAction?.type === "update"}
@@ -716,21 +780,36 @@ export default function DynamicTable({
         rowData={rowAction?.row.original ?? null}
         formCode={formCode}
         contractItemId={contractItemId}
+        editableFieldsMap={editableFieldsMap} // 새로 추가
         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 => 
+            setTableData(prev =>
+              prev.map(item =>
                 item.TAG_NO === tagNo ? updatedValues : item
               )
             );
           }
         }}
       />
-      
+      <DeleteFormDataDialog
+        formData={deleteTarget}
+        formCode={formCode}
+        contractItemId={contractItemId}
+        open={deleteDialogOpen}
+        onOpenChange={(open) => {
+          if (!open) {
+            setDeleteDialogOpen(false);
+            setDeleteTarget([]);
+          }
+        }}
+        onSuccess={handleDeleteSuccess}
+        showTrigger={false}
+      />
+
       {/* Dialog for adding tags */}
-      <AddFormTagDialog 
+      <AddFormTagDialog
         projectId={projectId}
         formCode={formCode}
         formName={`Form ${formCode}`}
@@ -738,7 +817,7 @@ export default function DynamicTable({
         open={addTagDialogOpen}
         onOpenChange={setAddTagDialogOpen}
       />
-      
+
       {/* SEDP Confirmation Dialog */}
       <SEDPConfirmationDialog
         isOpen={sedpConfirmOpen}
@@ -748,7 +827,7 @@ export default function DynamicTable({
         tagCount={tableData.length}
         isLoading={isSendingSEDP}
       />
-      
+
       {/* SEDP Status Dialog */}
       <SEDPStatusDialog
         isOpen={sedpStatusOpen}
@@ -759,7 +838,7 @@ export default function DynamicTable({
         errorCount={sedpStatusData.errorCount}
         totalCount={sedpStatusData.totalCount}
       />
-      
+
       {/* SEDP Compare Dialog */}
       <SEDPCompareDialog
         isOpen={sedpCompareOpen}
@@ -770,7 +849,7 @@ export default function DynamicTable({
         formCode={formCode}
         fetchTagDataFromSEDP={fetchTagDataFromSEDP}
       />
-      
+
       {/* Other dialogs */}
       {tempUpDialog && (
         <FormDataReportTempUploadDialog
-- 
cgit v1.2.3

