From fb276ed3db86fe4fc0c0fcd870fd3d085b034be0 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 17 Jul 2025 10:50:28 +0000
Subject: (대표님) 벤더데이터 S-EDP 변경사항 대응(seperator), 정기평가
 점수오류, dim 준비
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/form-data/spreadJS-dialog.tsx | 166 ++++++++++++++++++++++---------
 1 file changed, 121 insertions(+), 45 deletions(-)

(limited to 'components/form-data/spreadJS-dialog.tsx')

diff --git a/components/form-data/spreadJS-dialog.tsx b/components/form-data/spreadJS-dialog.tsx
index c106f926..1cf23369 100644
--- a/components/form-data/spreadJS-dialog.tsx
+++ b/components/form-data/spreadJS-dialog.tsx
@@ -10,6 +10,7 @@ import { toast } from "sonner";
 import { updateFormDataInDB } from "@/lib/forms/services";
 import { Loader, Save } from "lucide-react";
 import '@mescius/spread-sheets/styles/gc.spread.sheets.excel2016colorful.css';
+import { DataTableColumnJSON } from "./form-data-table-columns";
 
 // SpreadSheets를 동적으로 import (SSR 비활성화)
 const SpreadSheets = dynamic(
@@ -71,9 +72,10 @@ interface TemplateViewDialogProps {
   isOpen: boolean;
   onClose: () => void;
   templateData: TemplateItem[] | any;
-  selectedRow?: GenericData; // SPR_ITM_LST_SETUP용
-  tableData?: GenericData[]; // SPR_LST_SETUP용
+  selectedRow?: GenericData; // SPREAD_ITEM용
+  tableData?: GenericData[]; // SPREAD_LIST용
   formCode: string;
+  columnsJSON: DataTableColumnJSON[]
   contractItemId: number;
   editableFieldsMap?: Map<string, string[]>;
   onUpdateSuccess?: (updatedValues: Record<string, any> | GenericData[]) => void;
@@ -87,6 +89,7 @@ export function TemplateViewDialog({
   tableData = [],
   formCode,
   contractItemId,
+  columnsJSON,
   editableFieldsMap = new Map(),
   onUpdateSuccess
 }: TemplateViewDialogProps) {
@@ -100,14 +103,14 @@ export function TemplateViewDialog({
   const [currentSpread, setCurrentSpread] = React.useState<any>(null);
   const [cellMappings, setCellMappings] = React.useState<Array<{attId: string, cellAddress: string, isEditable: boolean}>>([]);
   const [isClient, setIsClient] = React.useState(false);
-  const [templateType, setTemplateType] = React.useState<'SPR_LST_SETUP' | 'SPR_ITM_LST_SETUP' | null>(null);
+  const [templateType, setTemplateType] = React.useState<'SPREAD_LIST' | 'SPREAD_ITEM' | null>(null);
 
   // 클라이언트 사이드에서만 렌더링되도록 보장
   React.useEffect(() => {
     setIsClient(true);
   }, []);
 
-  // 템플릿 데이터를 배열로 정규화하고 CONTENT가 있는 것 찾기
+  // 템플릿 데이터를 배열로 정규화하고 TMPL_TYPE에 따라 템플릿 타입 결정
   const { normalizedTemplate, detectedTemplateType } = React.useMemo(() => {
     if (!templateData) return { normalizedTemplate: null, detectedTemplateType: null };
     
@@ -118,13 +121,16 @@ export function TemplateViewDialog({
       templates = [templateData as TemplateItem];
     }
     
-    // CONTENT가 있는 템플릿 찾기
+    // TMPL_TYPE이 SPREAD_LIST 또는 SPREAD_ITEM인 템플릿 찾기
     for (const template of templates) {
-      if (template.SPR_LST_SETUP?.CONTENT) {
-        return { normalizedTemplate: template, detectedTemplateType: 'SPR_LST_SETUP' as const };
-      }
-      if (template.SPR_ITM_LST_SETUP?.CONTENT) {
-        return { normalizedTemplate: template, detectedTemplateType: 'SPR_ITM_LST_SETUP' as const };
+      if (template.TMPL_TYPE === "SPREAD_LIST" || template.TMPL_TYPE === "SPREAD_ITEM") {
+        // SPR_LST_SETUP.CONTENT 또는 SPR_ITM_LST_SETUP.CONTENT 중 하나라도 있으면 해당 템플릿 사용
+        if (template.SPR_LST_SETUP?.CONTENT || template.SPR_ITM_LST_SETUP?.CONTENT) {
+          return { 
+            normalizedTemplate: template, 
+            detectedTemplateType: template.TMPL_TYPE as 'SPREAD_LIST' | 'SPREAD_ITEM'
+          };
+        }
       }
     }
     
@@ -136,21 +142,64 @@ export function TemplateViewDialog({
     setTemplateType(detectedTemplateType);
   }, [detectedTemplateType]);
 
+  const editableFields = React.useMemo(() => {
+    if (!selectedRow?.TAG_NO || !editableFieldsMap.has(selectedRow.TAG_NO)) {
+      return [];
+    }
+    return editableFieldsMap.get(selectedRow.TAG_NO) || [];
+  }, [selectedRow?.TAG_NO, editableFieldsMap]);
+
   // 필드가 편집 가능한지 판별하는 함수
-  const isFieldEditable = React.useCallback((attId: string) => {
-    // TAG_NO와 TAG_DESC는 기본적으로 편집 가능
+  // const isFieldEditable = React.useCallback((attId: string) => {
+  //   // columnsJSON에서 해당 attId의 shi 값 확인
+  //   const columnConfig = columnsJSON.find(col => col.key === attId);
+  //   if (columnConfig?.shi === true) {
+  //     return false; // columnsJSON에서 shi가 true이면 편집 불가
+  //   }
+    
+  //   // TAG_NO와 TAG_DESC는 기본적으로 편집 가능 (columnsJSON의 shi가 false인 경우)
+  //   if (attId === "TAG_NO" || attId === "TAG_DESC") {
+  //     return true;
+  //   }
+
+  //   if (selectedRow?.TAG_NO && editableFieldsMap.has(selectedRow.TAG_NO)) {
+  //     return editableFields.includes(attId);
+  //   }
+    
+    
+      
+  //   // SPREAD_LIST인 경우는 기본적으로 편집 가능 (개별 행의 shi 상태는 저장시 확인)
+  //   return true;
+  // }, [templateType, selectedRow, columnsJSON, editableFields]);
+
+  const isFieldEditable = React.useCallback((attId: string, rowData?: GenericData) => {
+    // columnsJSON에서 해당 attId의 shi 값 확인
+    const columnConfig = columnsJSON.find(col => col.key === attId);
+    if (columnConfig?.shi === true) {
+      return false; // columnsJSON에서 shi가 true이면 편집 불가
+    }
+    
+    // TAG_NO와 TAG_DESC는 기본적으로 편집 가능 (columnsJSON의 shi가 false인 경우)
     if (attId === "TAG_NO" || attId === "TAG_DESC") {
       return true;
     }
+  
+    // SPREAD_ITEM 모드일 때는 selectedRow 사용
+    // if (templateType === 'SPREAD_ITEM' && selectedRow?.TAG_NO && editableFieldsMap.has(selectedRow.TAG_NO)) {
+    //   const editableFields = editableFieldsMap.get(selectedRow.TAG_NO) || [];
+    //   return editableFields.includes(attId);
+    // }
     
-    // SPR_ITM_LST_SETUP인 경우 selectedRow.shi 확인
-    if (templateType === 'SPR_ITM_LST_SETUP' && selectedRow) {
-      return selectedRow.shi !== true;
-    }
+    // // SPREAD_LIST 모드일 때는 각 행의 데이터 사용
+    // if (templateType === 'SPREAD_LIST' && rowData?.TAG_NO && editableFieldsMap.has(rowData.TAG_NO)) {
+    //   const editableFields = editableFieldsMap.get(rowData.TAG_NO) || [];
+    //   return editableFields.includes(attId);
+    // }
     
-    // SPR_LST_SETUP인 경우는 기본적으로 편집 가능 (개별 행의 shi 상태는 저장시 확인)
+    // SPREAD_LIST인 경우는 기본적으로 편집 가능 (개별 행의 shi 상태는 저장시 확인)
     return true;
-  }, [templateType, selectedRow]);
+  }, [templateType, selectedRow, columnsJSON, editableFieldsMap]);
+  
 
   // 편집 가능한 필드 개수 계산
   const editableFieldsCount = React.useMemo(() => {
@@ -184,18 +233,21 @@ export function TemplateViewDialog({
       setCurrentSpread(spread);
       setHasChanges(false);
 
-      // 템플릿 타입에 따라 CONTENT와 DATA_SHEETS 가져오기
+      // SPR_LST_SETUP.CONTENT와 SPR_ITM_LST_SETUP.CONTENT 중에서 값이 있는 것을 찾아서 사용
       let contentJson = null;
       let dataSheets = null;
       
-      if (templateType === 'SPR_LST_SETUP') {
+      // SPR_LST_SETUP.CONTENT가 있으면 우선 사용
+      if (normalizedTemplate.SPR_LST_SETUP?.CONTENT) {
         contentJson = normalizedTemplate.SPR_LST_SETUP.CONTENT;
         dataSheets = normalizedTemplate.SPR_LST_SETUP.DATA_SHEETS;
-        console.log('Using SPR_LST_SETUP.CONTENT for template:', normalizedTemplate.NAME);
-      } else if (templateType === 'SPR_ITM_LST_SETUP') {
+        console.log('Using SPR_LST_SETUP.CONTENT for template:', normalizedTemplate.NAME, '(TMPL_TYPE:', normalizedTemplate.TMPL_TYPE, ')');
+      } 
+      // SPR_ITM_LST_SETUP.CONTENT가 있으면 사용
+      else if (normalizedTemplate.SPR_ITM_LST_SETUP?.CONTENT) {
         contentJson = normalizedTemplate.SPR_ITM_LST_SETUP.CONTENT;
         dataSheets = normalizedTemplate.SPR_ITM_LST_SETUP.DATA_SHEETS;
-        console.log('Using SPR_ITM_LST_SETUP.CONTENT for template:', normalizedTemplate.NAME);
+        console.log('Using SPR_ITM_LST_SETUP.CONTENT for template:', normalizedTemplate.NAME, '(TMPL_TYPE:', normalizedTemplate.TMPL_TYPE, ')');
       }
 
       if (!contentJson) {
@@ -203,7 +255,7 @@ export function TemplateViewDialog({
         return;
       }
 
-      console.log(`Loading template content for: ${normalizedTemplate.NAME} (Type: ${templateType})`);
+      console.log(`Loading template content for: ${normalizedTemplate.NAME} (Type: ${normalizedTemplate.TMPL_TYPE})`);
       
       const jsonData = typeof contentJson === 'string' 
         ? JSON.parse(contentJson) 
@@ -242,7 +294,7 @@ export function TemplateViewDialog({
                     });
                     
                     // 템플릿 타입에 따라 다른 데이터 처리
-                    if (templateType === 'SPR_ITM_LST_SETUP' && selectedRow) {
+                    if (templateType === 'SPREAD_ITEM' && selectedRow) {
                       // 단일 행 처리 (기존 로직)
                       const cell = activeSheet.getCell(cellPos.row, cellPos.col);
                       const value = selectedRow[ATT_ID];
@@ -250,6 +302,10 @@ export function TemplateViewDialog({
                         cell.value(value);
                       }
                       
+                      if (value === undefined || value === null) {
+                        cell.value(null);
+                      }
+
                       // 스타일 적용
                       cell.locked(!isEditable);
                       const existingStyle = activeSheet.getStyle(cellPos.row, cellPos.col);
@@ -264,7 +320,7 @@ export function TemplateViewDialog({
                       
                       activeSheet.setStyle(cellPos.row, cellPos.col, newStyle);
                       
-                    } else if (templateType === 'SPR_LST_SETUP' && tableData.length > 0) {
+                    } else if (templateType === 'SPREAD_LIST' && tableData.length > 0) {
                       // 복수 행 처리 - 첫 번째 행부터 시작해서 아래로 채움
                       tableData.forEach((rowData, index) => {
                         const targetRow = cellPos.row + index;
@@ -274,16 +330,23 @@ export function TemplateViewDialog({
                         if (value !== undefined && value !== null) {
                           cell.value(value);
                         }
+
+                        if (value === undefined || value === null) {
+                          cell.value(null);
+                        }
                         
-                        // 개별 행의 편집 가능 여부 확인 (shi 필드 기준)
-                        const rowEditable = isEditable && (rowData.shi !== true);
-                        cell.locked(!rowEditable);
+                        // 개별 행의 편집 가능 여부 확인 (행의 shi + columnsJSON의 shi 모두 확인)
+                        // const columnConfig = columnsJSON.find(col => col.key === ATT_ID);
+                        // const cellEditable = columnConfig?.shi !== true; // columnsJSON에서 shi가 true이면 편집 불가
+                        
+                        const cellEditable = isFieldEditable(ATT_ID, rowData); // 각 행의 데이터를 전달
+                        cell.locked(!cellEditable);
                         
                         // 스타일 적용
                         const existingStyle = activeSheet.getStyle(targetRow, cellPos.col);
                         const newStyle = existingStyle ? Object.assign(new GC.Spread.Sheets.Style(), existingStyle) : new GC.Spread.Sheets.Style();
                         
-                        if (rowEditable) {
+                        if (cellEditable) {
                           newStyle.backColor = "#f0fdf4";
                         } else {
                           newStyle.backColor = "#f9fafb";
@@ -334,8 +397,16 @@ export function TemplateViewDialog({
             });
             
             if (mapping) {
-              // SPR_LST_SETUP인 경우 해당 행의 데이터에서 shi 확인
-              if (templateType === 'SPR_LST_SETUP') {
+              // columnsJSON에서 해당 필드의 shi 확인
+              const columnConfig = columnsJSON.find(col => col.key === mapping.attId);
+              if (columnConfig?.shi === true) {
+                toast.warning(`${mapping.attId} field is read-only (Column configuration)`);
+                info.cancel = true;
+                return;
+              }
+              
+              // SPREAD_LIST인 경우 해당 행의 데이터에서 shi 확인
+              if (templateType === 'SPREAD_LIST') {
                 const dataRowIndex = info.row - parseCellAddress(mapping.cellAddress)!.row;
                 const rowData = tableData[dataRowIndex];
                 if (rowData && rowData.shi === true) {
@@ -363,7 +434,7 @@ export function TemplateViewDialog({
         spread.resumePaint();
       }
     }
-  }, [normalizedTemplate, templateType, selectedRow, tableData, isFieldEditable]);
+  }, [normalizedTemplate, templateType, selectedRow, tableData, isFieldEditable, columnsJSON]);
 
   // 변경사항 저장 함수
   const handleSaveChanges = React.useCallback(async () => {
@@ -377,7 +448,7 @@ export function TemplateViewDialog({
 
       const activeSheet = currentSpread.getActiveSheet();
 
-      if (templateType === 'SPR_ITM_LST_SETUP' && selectedRow) {
+      if (templateType === 'SPREAD_ITEM' && selectedRow) {
         // 단일 행 저장 (기존 로직)
         const dataToSave = { ...selectedRow };
 
@@ -407,7 +478,7 @@ export function TemplateViewDialog({
         toast.success("Changes saved successfully!");
         onUpdateSuccess?.(dataToSave);
 
-      } else if (templateType === 'SPR_LST_SETUP' && tableData.length > 0) {
+      } else if (templateType === 'SPREAD_LIST' && tableData.length > 0) {
         // 복수 행 저장
         const updatedRows: GenericData[] = [];
         let saveCount = 0;
@@ -419,7 +490,12 @@ export function TemplateViewDialog({
 
           // 각 매핑에 대해 해당 행의 값 확인
           cellMappings.forEach(mapping => {
-            if (mapping.isEditable && originalRow.shi !== true) { // shi가 true인 행은 편집 불가
+            // columnsJSON에서 해당 필드의 shi 확인
+            const columnConfig = columnsJSON.find(col => col.key === mapping.attId);
+            const isColumnEditable = columnConfig?.shi !== true;
+            const isRowEditable = originalRow.shi !== true;
+            
+            if (mapping.isEditable && isColumnEditable && isRowEditable) {
               const cellPos = parseCellAddress(mapping.cellAddress);
               if (cellPos) {
                 const targetRow = cellPos.row + i;
@@ -469,13 +545,13 @@ export function TemplateViewDialog({
     } finally {
       setIsPending(false);
     }
-  }, [currentSpread, hasChanges, templateType, selectedRow, tableData, formCode, contractItemId, onUpdateSuccess, cellMappings]);
+  }, [currentSpread, hasChanges, templateType, selectedRow, tableData, formCode, contractItemId, onUpdateSuccess, cellMappings, columnsJSON]);
 
   if (!isOpen) return null;
 
   // 데이터 유효성 검사
-  const isDataValid = templateType === 'SPR_ITM_LST_SETUP' ? !!selectedRow : tableData.length > 0;
-  const dataCount = templateType === 'SPR_ITM_LST_SETUP' ? 1 : tableData.length;
+  const isDataValid = templateType === 'SPREAD_ITEM' ? !!selectedRow : tableData.length > 0;
+  const dataCount = templateType === 'SPREAD_ITEM' ? 1 : tableData.length;
 
   return (
     <Dialog open={isOpen} onOpenChange={onClose}>
@@ -486,15 +562,15 @@ export function TemplateViewDialog({
         <DialogHeader className="flex-shrink-0">
           <DialogTitle>SEDP Template - {formCode}</DialogTitle>
           <DialogDescription>
-            {templateType && (
+            {normalizedTemplate && (
               <span className="font-medium text-blue-600">
-                Template Type: {templateType === 'SPR_LST_SETUP' ? 'List View' : 'Item View'}
+                Template Type: {normalizedTemplate.TMPL_TYPE === 'SPREAD_LIST' ? 'List View (SPREAD_LIST)' : 'Item View (SPREAD_ITEM)'}
               </span>
             )}
-            {templateType === 'SPR_ITM_LST_SETUP' && selectedRow && (
+            {templateType === 'SPREAD_ITEM' && selectedRow && (
               <span className="ml-2">• Selected TAG_NO: {selectedRow.TAG_NO || 'N/A'}</span>
             )}
-            {templateType === 'SPR_LST_SETUP' && (
+            {templateType === 'SPREAD_LIST' && (
               <span className="ml-2">• {dataCount} rows</span>
             )}
             {hasChanges && (
@@ -525,7 +601,7 @@ export function TemplateViewDialog({
         <div className="flex-1 overflow-hidden">
           {normalizedTemplate && isClient && isDataValid ? (
             <SpreadSheets 
-              key={`${templateType}-${normalizedTemplate.TMPL_ID}`}
+              key={`${normalizedTemplate.TMPL_TYPE}-${normalizedTemplate.TMPL_ID}`}
               workbookInitialized={initSpread}
               hostStyle={hostStyle}
             />
@@ -539,7 +615,7 @@ export function TemplateViewDialog({
               ) : !normalizedTemplate ? (
                 "No template available"
               ) : !isDataValid ? (
-                `No ${templateType === 'SPR_ITM_LST_SETUP' ? 'selected row' : 'data'} available`
+                `No ${templateType === 'SPREAD_ITEM' ? 'selected row' : 'data'} available`
               ) : (
                 "Template not ready"
               )}
-- 
cgit v1.2.3

