From 2c02afd48a4d9276a4f5c132e088540a578d0972 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Tue, 30 Sep 2025 10:08:53 +0000
Subject: (대표님) 폼리스트, spreadjs 관련 변경사항, 벤더문서 뷰 쿼리 수정,
 이메일 템플릿 추가 등
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/form-data/spreadJS-dialog.tsx | 725 ++++++++++++++++---------------
 1 file changed, 381 insertions(+), 344 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 d001463e..91d5672c 100644
--- a/components/form-data/spreadJS-dialog.tsx
+++ b/components/form-data/spreadJS-dialog.tsx
@@ -107,9 +107,9 @@ interface LoadingProgressProps {
 
 const LoadingProgress: React.FC<LoadingProgressProps> = ({ phase, progress, total, isVisible }) => {
   const percentage = total > 0 ? Math.round((progress / total) * 100) : 0;
-  
+
   if (!isVisible) return null;
-  
+
   return (
     <div className="absolute inset-0 bg-white/90 flex items-center justify-center z-50">
       <div className="bg-white rounded-lg shadow-lg border p-6 min-w-[300px]">
@@ -117,11 +117,11 @@ const LoadingProgress: React.FC<LoadingProgressProps> = ({ phase, progress, tota
           <Loader className="h-5 w-5 animate-spin text-blue-600" />
           <span className="font-medium text-gray-900">Loading Template</span>
         </div>
-        
+
         <div className="space-y-2">
           <div className="text-sm text-gray-600">{phase}</div>
           <div className="w-full bg-gray-200 rounded-full h-2">
-            <div 
+            <div
               className="bg-blue-600 h-2 rounded-full transition-all duration-300 ease-out"
               style={{ width: `${percentage}%` }}
             />
@@ -161,7 +161,7 @@ export function TemplateViewDialog({
   const [validationErrors, setValidationErrors] = React.useState<ValidationError[]>([]);
   const [selectedTemplateId, setSelectedTemplateId] = React.useState<string>("");
   const [availableTemplates, setAvailableTemplates] = React.useState<TemplateItem[]>([]);
-  
+
   // 🆕 로딩 상태 추가
   const [loadingProgress, setLoadingProgress] = React.useState<{
     phase: string;
@@ -244,102 +244,102 @@ export function TemplateViewDialog({
       }
       return editableFieldsMap.get(selectedRow.TAG_NO) || [];
     }
-    
+
     // SPREAD_LIST나 GRD_LIST의 경우 전역 editableFields는 사용하지 않음
     return [];
   }, [templateType, selectedRow?.TAG_NO, editableFieldsMap]);
 
-  
-const isFieldEditable = React.useCallback((attId: string, rowData?: GenericData) => {
-  const columnConfig = columnsJSON.find(col => col.key === attId);
-  if (columnConfig?.shi === "OUT" || columnConfig?.shi === null) {
-    return false;
-  }
-
-  if (attId === "TAG_NO" || attId === "TAG_DESC" || attId === "status") {
-    return false;
-  }
 
-  if (templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') {
-    // 각 행의 TAG_NO를 기준으로 편집 가능 여부 판단
-    if (!rowData?.TAG_NO || !editableFieldsMap.has(rowData.TAG_NO)) {
+  const isFieldEditable = React.useCallback((attId: string, rowData?: GenericData) => {
+    const columnConfig = columnsJSON.find(col => col.key === attId);
+    if (columnConfig?.shi === "OUT" || columnConfig?.shi === null) {
       return false;
     }
-    
-    const rowEditableFields = editableFieldsMap.get(rowData.TAG_NO) || [];
-    if (!rowEditableFields.includes(attId)) {
+
+    if (attId === "TAG_NO" || attId === "TAG_DESC" || attId === "status") {
       return false;
     }
-    
-    if (rowData && (rowData.shi === "OUT" || rowData.shi === null)) {
-      return false;
+
+    if (templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') {
+      // 각 행의 TAG_NO를 기준으로 편집 가능 여부 판단
+      if (!rowData?.TAG_NO || !editableFieldsMap.has(rowData.TAG_NO)) {
+        return false;
+      }
+
+      const rowEditableFields = editableFieldsMap.get(rowData.TAG_NO) || [];
+      if (!rowEditableFields.includes(attId)) {
+        return false;
+      }
+
+      if (rowData && (rowData.shi === "OUT" || rowData.shi === null)) {
+        return false;
+      }
+      return true;
     }
+
+    // SPREAD_ITEM의 경우 기존 로직 유지
+    if (templateType === 'SPREAD_ITEM') {
+      return editableFields.includes(attId);
+    }
+
     return true;
-  }
+  }, [templateType, columnsJSON, editableFieldsMap]); // editableFields 의존성 제거
 
-  // SPREAD_ITEM의 경우 기존 로직 유지
-  if (templateType === 'SPREAD_ITEM') {
-    return editableFields.includes(attId);
-  }
+  const editableFieldsCount = React.useMemo(() => {
+    if (templateType === 'SPREAD_ITEM') {
+      // SPREAD_ITEM의 경우 기존 로직 유지
+      return cellMappings.filter(m => m.isEditable).length;
+    }
 
-  return true;
-}, [templateType, columnsJSON, editableFieldsMap]); // editableFields 의존성 제거
+    if (templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') {
+      // 각 행별로 편집 가능한 필드 수를 계산
+      let totalEditableCount = 0;
 
-const editableFieldsCount = React.useMemo(() => {
-  if (templateType === 'SPREAD_ITEM') {
-    // SPREAD_ITEM의 경우 기존 로직 유지
-    return cellMappings.filter(m => m.isEditable).length;
-  }
-  
-  if (templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') {
-    // 각 행별로 편집 가능한 필드 수를 계산
-    let totalEditableCount = 0;
-    
-    tableData.forEach((rowData, rowIndex) => {
-      cellMappings.forEach(mapping => {
-        if (mapping.dataRowIndex === rowIndex) {
-          if (isFieldEditable(mapping.attId, rowData)) {
-            totalEditableCount++;
+      tableData.forEach((rowData, rowIndex) => {
+        cellMappings.forEach(mapping => {
+          if (mapping.dataRowIndex === rowIndex) {
+            if (isFieldEditable(mapping.attId, rowData)) {
+              totalEditableCount++;
+            }
           }
-        }
+        });
       });
-    });
-    
-    return totalEditableCount;
-  }
-  
-  return cellMappings.filter(m => m.isEditable).length;
-}, [cellMappings, templateType, tableData, isFieldEditable]);
+
+      return totalEditableCount;
+    }
+
+    return cellMappings.filter(m => m.isEditable).length;
+  }, [cellMappings, templateType, tableData, isFieldEditable]);
 
   // 🚀 배치 처리 함수들
   const setBatchValues = React.useCallback((
-    activeSheet: any, 
-    valuesToSet: Array<{row: number, col: number, value: any}>
+    activeSheet: any,
+    valuesToSet: Array<{ row: number, col: number, value: any }>
   ) => {
     console.log(`🚀 Setting ${valuesToSet.length} values in batch`);
-    
-    const columnGroups = new Map<number, Array<{row: number, value: any}>>();
-    
-    valuesToSet.forEach(({row, col, value}) => {
+
+    const columnGroups = new Map<number, Array<{ row: number, value: any }>>();
+
+    valuesToSet.forEach(({ row, col, value }) => {
       if (!columnGroups.has(col)) {
         columnGroups.set(col, []);
       }
-      columnGroups.get(col)!.push({row, value});
+      columnGroups.get(col)!.push({ row, value });
     });
-    
+
     columnGroups.forEach((values, col) => {
       values.sort((a, b) => a.row - b.row);
-      
+
       let start = 0;
       while (start < values.length) {
         let end = start;
         while (end + 1 < values.length && values[end + 1].row === values[end].row + 1) {
           end++;
         }
-        
+
         const rangeValues = values.slice(start, end + 1).map(v => v.value);
         const startRow = values[start].row;
-        
+
         try {
           if (rangeValues.length === 1) {
             activeSheet.setValue(startRow, col, rangeValues[0]);
@@ -356,7 +356,7 @@ const editableFieldsCount = React.useMemo(() => {
             }
           }
         }
-        
+
         start = end + 1;
       }
     });
@@ -365,7 +365,7 @@ const editableFieldsCount = React.useMemo(() => {
   const createCellStyle = React.useCallback((activeSheet: any, row: number, col: number, isEditable: boolean) => {
     // 기존 스타일 가져오기 (없으면 새로 생성)
     const existingStyle = activeSheet.getStyle(row, col) || new GC.Spread.Sheets.Style();
-    
+
     // backColor만 수정
     if (isEditable) {
       existingStyle.backColor = "#bbf7d0";
@@ -374,25 +374,25 @@ const editableFieldsCount = React.useMemo(() => {
       // 읽기 전용일 때만 텍스트 색상 변경 (선택사항)
       existingStyle.foreColor = "#4b5563";
     }
-    
+
     return existingStyle;
   }, []);
 
   const setBatchStyles = React.useCallback((
-    activeSheet: any, 
-    stylesToSet: Array<{row: number, col: number, isEditable: boolean}>
+    activeSheet: any,
+    stylesToSet: Array<{ row: number, col: number, isEditable: boolean }>
   ) => {
     console.log(`🎨 Setting ${stylesToSet.length} styles in batch`);
-    
+
     // 🔧 개별 셀별로 스타일과 잠금 상태 설정 (편집 권한 보장)
-    stylesToSet.forEach(({row, col, isEditable}) => {
+    stylesToSet.forEach(({ row, col, isEditable }) => {
       try {
         const cell = activeSheet.getCell(row, col);
         const style = createCellStyle(activeSheet, row, col, isEditable);
-        
+
         activeSheet.setStyle(row, col, style);
         cell.locked(!isEditable); // 편집 가능하면 잠금 해제
-        
+
         // 🆕 편집 가능한 셀에 기본 텍스트 에디터 설정
         if (isEditable) {
           const textCellType = new GC.Spread.Sheets.CellTypes.Text();
@@ -511,7 +511,7 @@ const editableFieldsCount = React.useMemo(() => {
       for (let i = 0; i < rowCount; i++) {
         try {
           const targetRow = cellPos.row + i;
-          
+
           // 🔧 각 셀마다 새로운 ComboBox 인스턴스 생성
           const comboBoxCellType = new GC.Spread.Sheets.CellTypes.ComboBox();
           comboBoxCellType.items(safeOptions);
@@ -525,7 +525,7 @@ const editableFieldsCount = React.useMemo(() => {
           // ComboBox와 Validator 적용
           activeSheet.setCellType(targetRow, cellPos.col, comboBoxCellType);
           activeSheet.setDataValidator(targetRow, cellPos.col, cellValidator);
-          
+
           // 🚀 중요: 셀 잠금 해제 및 편집 가능 설정
           const cell = activeSheet.getCell(targetRow, cellPos.col);
           cell.locked(false);
@@ -546,7 +546,7 @@ const editableFieldsCount = React.useMemo(() => {
 
   const getSafeActiveSheet = React.useCallback((spread: any, functionName: string = 'unknown') => {
     if (!spread) return null;
-    
+
     try {
       let activeSheet = spread.getActiveSheet();
       if (!activeSheet) {
@@ -568,7 +568,7 @@ const editableFieldsCount = React.useMemo(() => {
   const ensureRowCapacity = React.useCallback((activeSheet: any, requiredRowCount: number) => {
     try {
       if (!activeSheet) return false;
-      
+
       const currentRowCount = activeSheet.getRowCount();
       if (requiredRowCount > currentRowCount) {
         const newRowCount = requiredRowCount + 10;
@@ -584,7 +584,7 @@ const editableFieldsCount = React.useMemo(() => {
   const ensureColumnCapacity = React.useCallback((activeSheet: any, requiredColumnCount: number) => {
     try {
       if (!activeSheet) return false;
-      
+
       const currentColumnCount = activeSheet.getColumnCount();
       if (requiredColumnCount > currentColumnCount) {
         const newColumnCount = requiredColumnCount + 10;
@@ -606,206 +606,206 @@ const editableFieldsCount = React.useMemo(() => {
   }, []);
 
   // 🚀 최적화된 GRD_LIST 생성
- // 🚀 최적화된 GRD_LIST 생성 (TAG_DESC 컬럼 틀고정 포함)
-const createGrdListTableOptimized = React.useCallback((activeSheet: any, template: TemplateItem) => {
-  console.log('🚀 Creating optimized GRD_LIST table with TAG_DESC freeze');
-
-  const visibleColumns = columnsJSON
-    .filter(col => col.hidden !== true)
-    .sort((a, b) => (a.seq ?? 999999) - (b.seq ?? 999999));
-
-  if (visibleColumns.length === 0) return [];
-
-  const startCol = 1;
-  const dataStartRow = 1;
-  const mappings: CellMapping[] = [];
-
-  ensureColumnCapacity(activeSheet, startCol + visibleColumns.length);
-  ensureRowCapacity(activeSheet, dataStartRow + tableData.length);
-
-  // 🧊 TAG_DESC 컬럼 위치 찾기 (틀고정용)
-  const tagDescColumnIndex = visibleColumns.findIndex(col => col.key === 'TAG_DESC');
-  let freezeColumnCount = 0;
-  
-  if (tagDescColumnIndex !== -1) {
-    // TAG_DESC 컬럼까지 포함해서 고정 (startCol + tagDescColumnIndex + 1)
-    freezeColumnCount = startCol + tagDescColumnIndex + 1;
-    console.log(`🧊 TAG_DESC found at column index ${tagDescColumnIndex}, freezing ${freezeColumnCount} columns`);
-  } else {
-    // TAG_DESC가 없으면 TAG_NO까지만 고정 (일반적으로 첫 번째 컬럼)
-    const tagNoColumnIndex = visibleColumns.findIndex(col => col.key === 'TAG_NO');
-    if (tagNoColumnIndex !== -1) {
-      freezeColumnCount = startCol + tagNoColumnIndex + 1;
-      console.log(`🧊 TAG_NO found at column index ${tagNoColumnIndex}, freezing ${freezeColumnCount} columns`);
+  // 🚀 최적화된 GRD_LIST 생성 (TAG_DESC 컬럼 틀고정 포함)
+  const createGrdListTableOptimized = React.useCallback((activeSheet: any, template: TemplateItem) => {
+    console.log('🚀 Creating optimized GRD_LIST table with TAG_DESC freeze');
+
+    const visibleColumns = columnsJSON
+      .filter(col => col.hidden !== true)
+      .sort((a, b) => (a.seq ?? 999999) - (b.seq ?? 999999));
+
+    if (visibleColumns.length === 0) return [];
+
+    const startCol = 1;
+    const dataStartRow = 1;
+    const mappings: CellMapping[] = [];
+
+    ensureColumnCapacity(activeSheet, startCol + visibleColumns.length);
+    ensureRowCapacity(activeSheet, dataStartRow + tableData.length);
+
+    // 🧊 TAG_DESC 컬럼 위치 찾기 (틀고정용)
+    const tagDescColumnIndex = visibleColumns.findIndex(col => col.key === 'TAG_DESC');
+    let freezeColumnCount = 0;
+
+    if (tagDescColumnIndex !== -1) {
+      // TAG_DESC 컬럼까지 포함해서 고정 (startCol + tagDescColumnIndex + 1)
+      freezeColumnCount = startCol + tagDescColumnIndex + 1;
+      console.log(`🧊 TAG_DESC found at column index ${tagDescColumnIndex}, freezing ${freezeColumnCount} columns`);
+    } else {
+      // TAG_DESC가 없으면 TAG_NO까지만 고정 (일반적으로 첫 번째 컬럼)
+      const tagNoColumnIndex = visibleColumns.findIndex(col => col.key === 'TAG_NO');
+      if (tagNoColumnIndex !== -1) {
+        freezeColumnCount = startCol + tagNoColumnIndex + 1;
+        console.log(`🧊 TAG_NO found at column index ${tagNoColumnIndex}, freezing ${freezeColumnCount} columns`);
+      }
     }
-  }
 
-  // 헤더 생성
-  const headerStyle = new GC.Spread.Sheets.Style();
-  headerStyle.backColor = "#3b82f6";
-  headerStyle.foreColor = "#ffffff";
-  headerStyle.font = "bold 12px Arial";
-  headerStyle.hAlign = GC.Spread.Sheets.HorizontalAlign.center;
-
-  visibleColumns.forEach((column, colIndex) => {
-    const targetCol = startCol + colIndex;
-    const cell = activeSheet.getCell(0, targetCol);
-    cell.value(column.label);
-    cell.locked(true);
-    activeSheet.setStyle(0, targetCol, headerStyle);
-  });
+    // 헤더 생성
+    const headerStyle = new GC.Spread.Sheets.Style();
+    headerStyle.backColor = "#3b82f6";
+    headerStyle.foreColor = "#ffffff";
+    headerStyle.font = "bold 12px Arial";
+    headerStyle.hAlign = GC.Spread.Sheets.HorizontalAlign.center;
 
-  // 🚀 데이터 배치 처리 준비
-  const allValues: Array<{row: number, col: number, value: any}> = [];
-  const allStyles: Array<{row: number, col: number, isEditable: boolean}> = [];
-
-  // 🔧 편집 가능한 셀 정보 수집 (드롭다운용)
-  const dropdownConfigs: Array<{
-    startRow: number;
-    col: number;
-    rowCount: number;
-    options: string[];
-    editableRows: number[]; // 편집 가능한 행만 추적
-  }> = [];
-
-  visibleColumns.forEach((column, colIndex) => {
-    const targetCol = startCol + colIndex;
-    
-    // 드롭다운 설정을 위한 편집 가능한 행 찾기
-    if (column.type === "LIST" && column.options) {
-      const editableRows: number[] = [];
-      tableData.forEach((rowData, rowIndex) => {
-        if (isFieldEditable(column.key, rowData)) { // rowData 전달
-          editableRows.push(dataStartRow + rowIndex);
+    visibleColumns.forEach((column, colIndex) => {
+      const targetCol = startCol + colIndex;
+      const cell = activeSheet.getCell(0, targetCol);
+      cell.value(column.label);
+      cell.locked(true);
+      activeSheet.setStyle(0, targetCol, headerStyle);
+    });
+
+    // 🚀 데이터 배치 처리 준비
+    const allValues: Array<{ row: number, col: number, value: any }> = [];
+    const allStyles: Array<{ row: number, col: number, isEditable: boolean }> = [];
+
+    // 🔧 편집 가능한 셀 정보 수집 (드롭다운용)
+    const dropdownConfigs: Array<{
+      startRow: number;
+      col: number;
+      rowCount: number;
+      options: string[];
+      editableRows: number[]; // 편집 가능한 행만 추적
+    }> = [];
+
+    visibleColumns.forEach((column, colIndex) => {
+      const targetCol = startCol + colIndex;
+
+      // 드롭다운 설정을 위한 편집 가능한 행 찾기
+      if (column.type === "LIST" && column.options) {
+        const editableRows: number[] = [];
+        tableData.forEach((rowData, rowIndex) => {
+          if (isFieldEditable(column.key, rowData)) { // rowData 전달
+            editableRows.push(dataStartRow + rowIndex);
+          }
+        });
+
+        if (editableRows.length > 0) {
+          dropdownConfigs.push({
+            startRow: dataStartRow,
+            col: targetCol,
+            rowCount: tableData.length,
+            options: column.options,
+            editableRows: editableRows
+          });
         }
-      });
-      
-      if (editableRows.length > 0) {
-        dropdownConfigs.push({
-          startRow: dataStartRow,
+      }
+
+      tableData.forEach((rowData, rowIndex) => {
+        const targetRow = dataStartRow + rowIndex;
+        const cellEditable = isFieldEditable(column.key, rowData); // rowData 전달
+        const value = rowData[column.key];
+
+        mappings.push({
+          attId: column.key,
+          cellAddress: getCellAddress(targetRow, targetCol),
+          isEditable: cellEditable,
+          dataRowIndex: rowIndex
+        });
+
+        allValues.push({
+          row: targetRow,
           col: targetCol,
-          rowCount: tableData.length,
-          options: column.options,
-          editableRows: editableRows
+          value: value ?? null
+        });
+
+        allStyles.push({
+          row: targetRow,
+          col: targetCol,
+          isEditable: cellEditable
         });
-      }
-    }
-    
-    tableData.forEach((rowData, rowIndex) => {
-      const targetRow = dataStartRow + rowIndex;
-      const cellEditable = isFieldEditable(column.key, rowData); // rowData 전달
-      const value = rowData[column.key];
-    
-      mappings.push({
-        attId: column.key,
-        cellAddress: getCellAddress(targetRow, targetCol),
-        isEditable: cellEditable,
-        dataRowIndex: rowIndex
-      });
-    
-      allValues.push({
-        row: targetRow,
-        col: targetCol,
-        value: value ?? null
-      });
-    
-      allStyles.push({
-        row: targetRow,
-        col: targetCol,
-        isEditable: cellEditable
       });
     });
-  });
 
-  // 🚀 배치로 값과 스타일 설정
-  setBatchValues(activeSheet, allValues);
-  setBatchStyles(activeSheet, allStyles);
+    // 🚀 배치로 값과 스타일 설정
+    setBatchValues(activeSheet, allValues);
+    setBatchStyles(activeSheet, allStyles);
 
-  // 🎯 개선된 드롭다운 설정 (편집 가능한 셀에만)
-  dropdownConfigs.forEach(({ col, options, editableRows }) => {
-    try {
-      console.log(`🎯 Setting dropdown for column ${col}, editable rows: ${editableRows.length}`);
-      
-      const safeOptions = options
-        .filter(opt => opt !== null && opt !== undefined && opt !== '')
-        .map(opt => String(opt).trim())
-        .filter(opt => opt.length > 0)
-        .slice(0, 20);
+    // 🎯 개선된 드롭다운 설정 (편집 가능한 셀에만)
+    dropdownConfigs.forEach(({ col, options, editableRows }) => {
+      try {
+        console.log(`🎯 Setting dropdown for column ${col}, editable rows: ${editableRows.length}`);
 
-      if (safeOptions.length === 0) return;
+        const safeOptions = options
+          .filter(opt => opt !== null && opt !== undefined && opt !== '')
+          .map(opt => String(opt).trim())
+          .filter(opt => opt.length > 0)
+          .slice(0, 20);
 
-      // 편집 가능한 행에만 드롭다운 적용
-      editableRows.forEach(targetRow => {
-        try {
-          const comboBoxCellType = new GC.Spread.Sheets.CellTypes.ComboBox();
-          comboBoxCellType.items(safeOptions);
-          comboBoxCellType.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.text);
+        if (safeOptions.length === 0) return;
 
-          const cellValidator = GC.Spread.Sheets.DataValidation.createListValidator(safeOptions.join(','));
-          cellValidator.showInputMessage(false);
-          cellValidator.showErrorMessage(false);
+        // 편집 가능한 행에만 드롭다운 적용
+        editableRows.forEach(targetRow => {
+          try {
+            const comboBoxCellType = new GC.Spread.Sheets.CellTypes.ComboBox();
+            comboBoxCellType.items(safeOptions);
+            comboBoxCellType.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.text);
 
-          activeSheet.setCellType(targetRow, col, comboBoxCellType);
-          activeSheet.setDataValidator(targetRow, col, cellValidator);
-          
-          // 🚀 편집 권한 명시적 설정
-          const cell = activeSheet.getCell(targetRow, col);
-          cell.locked(false);
+            const cellValidator = GC.Spread.Sheets.DataValidation.createListValidator(safeOptions.join(','));
+            cellValidator.showInputMessage(false);
+            cellValidator.showErrorMessage(false);
 
-          console.log(`✅ Dropdown applied to editable cell [${targetRow}, ${col}]`);
-        } catch (cellError) {
-          console.warn(`⚠️ Failed to apply dropdown to [${targetRow}, ${col}]:`, cellError);
-        }
-      });
-    } catch (error) {
-      console.error(`❌ Dropdown config failed for column ${col}:`, error);
-    }
-  });
+            activeSheet.setCellType(targetRow, col, comboBoxCellType);
+            activeSheet.setDataValidator(targetRow, col, cellValidator);
 
-  // 🧊 틀고정 설정
-  if (freezeColumnCount > 0) {
-    try {
-      activeSheet.frozenColumnCount(freezeColumnCount);
-      activeSheet.frozenRowCount(1); // 헤더 행도 고정
-      
-      console.log(`🧊 Freeze applied: ${freezeColumnCount} columns, 1 row (header)`);
-      
-      // 🎨 고정된 컬럼에 특별한 스타일 추가 (선택사항)
-      for (let col = 0; col < freezeColumnCount; col++) {
-        for (let row = 0; row <= tableData.length; row++) {
-          try {
-            const currentStyle = activeSheet.getStyle(row, col) || new GC.Spread.Sheets.Style();
-            
-            if (row === 0) {
-              // 헤더는 기존 스타일 유지
-              continue;
-            } else {
-              // 데이터 셀에 고정 구분선 추가
-              if (col === freezeColumnCount - 1) {
-                currentStyle.borderRight = new GC.Spread.Sheets.LineBorder("#2563eb", GC.Spread.Sheets.LineStyle.medium);
-                activeSheet.setStyle(row, col, currentStyle);
+            // 🚀 편집 권한 명시적 설정
+            const cell = activeSheet.getCell(targetRow, col);
+            cell.locked(false);
+
+            console.log(`✅ Dropdown applied to editable cell [${targetRow}, ${col}]`);
+          } catch (cellError) {
+            console.warn(`⚠️ Failed to apply dropdown to [${targetRow}, ${col}]:`, cellError);
+          }
+        });
+      } catch (error) {
+        console.error(`❌ Dropdown config failed for column ${col}:`, error);
+      }
+    });
+
+    // 🧊 틀고정 설정
+    if (freezeColumnCount > 0) {
+      try {
+        activeSheet.frozenColumnCount(freezeColumnCount);
+        activeSheet.frozenRowCount(1); // 헤더 행도 고정
+
+        console.log(`🧊 Freeze applied: ${freezeColumnCount} columns, 1 row (header)`);
+
+        // 🎨 고정된 컬럼에 특별한 스타일 추가 (선택사항)
+        for (let col = 0; col < freezeColumnCount; col++) {
+          for (let row = 0; row <= tableData.length; row++) {
+            try {
+              const currentStyle = activeSheet.getStyle(row, col) || new GC.Spread.Sheets.Style();
+
+              if (row === 0) {
+                // 헤더는 기존 스타일 유지
+                continue;
+              } else {
+                // 데이터 셀에 고정 구분선 추가
+                if (col === freezeColumnCount - 1) {
+                  currentStyle.borderRight = new GC.Spread.Sheets.LineBorder("#2563eb", GC.Spread.Sheets.LineStyle.medium);
+                  activeSheet.setStyle(row, col, currentStyle);
+                }
               }
+            } catch (styleError) {
+              console.warn(`⚠️ Failed to apply freeze border style to [${row}, ${col}]:`, styleError);
             }
-          } catch (styleError) {
-            console.warn(`⚠️ Failed to apply freeze border style to [${row}, ${col}]:`, styleError);
           }
         }
+      } catch (freezeError) {
+        console.error('❌ Failed to apply freeze:', freezeError);
       }
-    } catch (freezeError) {
-      console.error('❌ Failed to apply freeze:', freezeError);
     }
-  }
 
-  setOptimalColumnWidths(activeSheet, visibleColumns, startCol, tableData);
-  
-  console.log(`✅ Optimized GRD_LIST created with freeze:`);
-  console.log(`   - Total mappings: ${mappings.length}`);
-  console.log(`   - Editable cells: ${mappings.filter(m => m.isEditable).length}`);
-  console.log(`   - Dropdown configs: ${dropdownConfigs.length}`);
-  console.log(`   - Frozen columns: ${freezeColumnCount}`);
-  
-  return mappings;
-}, [tableData, columnsJSON, isFieldEditable, setBatchValues, setBatchStyles, ensureColumnCapacity, ensureRowCapacity, getCellAddress, setOptimalColumnWidths]);
+    setOptimalColumnWidths(activeSheet, visibleColumns, startCol, tableData);
+
+    console.log(`✅ Optimized GRD_LIST created with freeze:`);
+    console.log(`   - Total mappings: ${mappings.length}`);
+    console.log(`   - Editable cells: ${mappings.filter(m => m.isEditable).length}`);
+    console.log(`   - Dropdown configs: ${dropdownConfigs.length}`);
+    console.log(`   - Frozen columns: ${freezeColumnCount}`);
+
+    return mappings;
+  }, [tableData, columnsJSON, isFieldEditable, setBatchValues, setBatchStyles, ensureColumnCapacity, ensureRowCapacity, getCellAddress, setOptimalColumnWidths]);
 
   const setupSheetProtectionAndEvents = React.useCallback((activeSheet: any, mappings: CellMapping[]) => {
     console.log(`🛡️ Setting up protection and events for ${mappings.length} mappings`);
@@ -817,22 +817,22 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
     mappings.forEach((mapping) => {
       const cellPos = parseCellAddress(mapping.cellAddress);
       if (!cellPos) return;
-      
+
       try {
         const cell = activeSheet.getCell(cellPos.row, cellPos.col);
         const columnConfig = columnsJSON.find(col => col.key === mapping.attId);
-        
+
         if (mapping.isEditable) {
           // 🚀 편집 가능한 셀 설정 강화
           cell.locked(false);
-          
+
           if (columnConfig?.type === "LIST" && columnConfig.options) {
             // LIST 타입: 새 ComboBox 인스턴스 생성
             const comboBox = new GC.Spread.Sheets.CellTypes.ComboBox();
             comboBox.items(columnConfig.options);
             comboBox.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.text);
             activeSheet.setCellType(cellPos.row, cellPos.col, comboBox);
-            
+
             // DataValidation도 추가
             const validator = GC.Spread.Sheets.DataValidation.createListValidator(columnConfig.options.join(','));
             activeSheet.setDataValidator(cellPos.row, cellPos.col, validator);
@@ -840,7 +840,7 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
             // NUMBER 타입: 숫자 입력 허용
             const textCellType = new GC.Spread.Sheets.CellTypes.Text();
             activeSheet.setCellType(cellPos.row, cellPos.col, textCellType);
-            
+
             // 숫자 validation 추가 (에러 메시지 없이)
             const numberValidator = GC.Spread.Sheets.DataValidation.createNumberValidator(
               GC.Spread.Sheets.ConditionalFormatting.ComparisonOperators.between,
@@ -854,11 +854,11 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
             const textCellType = new GC.Spread.Sheets.CellTypes.Text();
             activeSheet.setCellType(cellPos.row, cellPos.col, textCellType);
           }
-          
+
           // 편집 가능 스타일 재적용
           const editableStyle = createCellStyle(activeSheet, cellPos.row, cellPos.col, true);
           activeSheet.setStyle(cellPos.row, cellPos.col, editableStyle);
-          
+
           console.log(`🔓 Cell [${cellPos.row}, ${cellPos.col}] ${mapping.attId} set as EDITABLE`);
         } else {
           // 읽기 전용 셀
@@ -930,7 +930,7 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
         const dataRowIndex = exactMapping.dataRowIndex;
         if (dataRowIndex >= 0 && dataRowIndex < tableData.length) {
           const rowData = tableData[dataRowIndex];
-          if (rowData?.shi === "OUT" || rowData?.shi === null ) {
+          if (rowData?.shi === "OUT" || rowData?.shi === null) {
             console.log(`🚫 Row ${dataRowIndex} is in SHI mode`);
             toast.warning(`Row ${dataRowIndex + 1}: ${exactMapping.attId} field is read-only (SHI mode)`);
             info.cancel = true;
@@ -998,7 +998,7 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
       console.log('🚀 Starting optimized spread initialization...');
       setIsInitializing(true);
       updateProgress('Initializing...', 0, 100);
-      
+
       setCurrentSpread(spread);
       setHasChanges(false);
       setValidationErrors([]);
@@ -1007,7 +1007,7 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
       spread.suspendPaint();
       spread.suspendEvent();
       spread.suspendCalcService();
-      
+
       updateProgress('Setting up workspace...', 10, 100);
 
       try {
@@ -1021,19 +1021,19 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
 
         if (templateType === 'GRD_LIST') {
           updateProgress('Creating dynamic table...', 20, 100);
-          
+
           spread.clearSheets();
           spread.addSheet(0);
           const sheet = spread.getSheet(0);
           sheet.name('Data');
           spread.setActiveSheet('Data');
-          
+
           updateProgress('Processing table data...', 50, 100);
           mappings = createGrdListTableOptimized(sheet, workingTemplate);
-          
+
         } else {
           updateProgress('Loading template structure...', 20, 100);
-          
+
           let contentJson = workingTemplate.SPR_LST_SETUP?.CONTENT || workingTemplate.SPR_ITM_LST_SETUP?.CONTENT;
           let dataSheets = workingTemplate.SPR_LST_SETUP?.DATA_SHEETS || workingTemplate.SPR_ITM_LST_SETUP?.DATA_SHEETS;
 
@@ -1042,10 +1042,10 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
           }
 
           const jsonData = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
-          
+
           updateProgress('Loading template layout...', 40, 100);
           spread.fromJSON(jsonData);
-          
+
           activeSheet = getSafeActiveSheet(spread, 'after-fromJSON');
           if (!activeSheet) {
             throw new Error('ActiveSheet became null after loading template');
@@ -1058,15 +1058,24 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
 
             const activeSheetName = workingTemplate.SPR_LST_SETUP?.ACT_SHEET;
 
-            const matchingDataSheets = dataSheets.filter(ds => 
-              ds.SHEET_NAME === activeSheetName
-            );
 
-            if (activeSheetName && spread.getSheetFromName(activeSheetName)) {
-              spread.setActiveSheet(activeSheetName);
-            }
+            // 🔧 각 DATA_SHEET별로 처리
+            dataSheets.forEach(dataSheet => {
+              const sheetName = dataSheet.SHEET_NAME;
+
+              // 해당 시트가 존재하는지 확인
+              const targetSheet = spread.getSheetFromName(sheetName);
+              if (!targetSheet) {
+                console.warn(`⚠️ Sheet '${sheetName}' not found in template`);
+                return;
+              }
+
+              console.log(`📋 Processing sheet: ${sheetName}`);
+
+              // 해당 시트로 전환
+              spread.setActiveSheet(sheetName);
+              const currentSheet = spread.getActiveSheet();
 
-            matchingDataSheets.forEach(dataSheet => {
               if (dataSheet.MAP_CELL_ATT && dataSheet.MAP_CELL_ATT.length > 0) {
                 dataSheet.MAP_CELL_ATT.forEach((mapping: any) => {
                   const { ATT_ID, IN } = mapping;
@@ -1076,11 +1085,11 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
                   if (!cellPos) return;
 
                   const requiredRows = cellPos.row + tableData.length;
-                  if (!ensureRowCapacity(activeSheet, requiredRows)) return;
+                  if (!ensureRowCapacity(currentSheet, requiredRows)) return;
 
                   // 🚀 배치 데이터 준비
-                  const valuesToSet: Array<{row: number, col: number, value: any}> = [];
-                  const stylesToSet: Array<{row: number, col: number, isEditable: boolean}> = [];
+                  const valuesToSet: Array<{ row: number, col: number, value: any }> = [];
+                  const stylesToSet: Array<{ row: number, col: number, isEditable: boolean }> = [];
 
                   tableData.forEach((rowData, index) => {
                     const targetRow = cellPos.row + index;
@@ -1108,67 +1117,95 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
                   });
 
                   // 🚀 배치 처리
-                  setBatchValues(activeSheet, valuesToSet);
-                  setBatchStyles(activeSheet, stylesToSet);
+                  setBatchValues(currentSheet, valuesToSet);
+                  setBatchStyles(currentSheet, stylesToSet);
 
                   // 드롭다운 설정
                   const columnConfig = columnsJSON.find(col => col.key === ATT_ID);
                   if (columnConfig?.type === "LIST" && columnConfig.options) {
                     const hasEditableRows = tableData.some((rowData) => isFieldEditable(ATT_ID, rowData));
                     if (hasEditableRows) {
-                      setupOptimizedListValidation(activeSheet, cellPos, columnConfig.options, tableData.length);
+                      setupOptimizedListValidation(currentSheet, cellPos, columnConfig.options, tableData.length);
                     }
                   }
                 });
               }
             });
-            
+
+            // 🔧 마지막에 activeSheetName으로 다시 전환
+            if (activeSheetName && spread.getSheetFromName(activeSheetName)) {
+              spread.setActiveSheet(activeSheetName);
+              activeSheet = spread.getActiveSheet();
+            }
+
           } else if (templateType === 'SPREAD_ITEM' && selectedRow) {
             updateProgress('Setting up form fields...', 60, 100);
-              const activeSheetName = workingTemplate.SPR_ITM_LST_SETUP?.ACT_SHEET;
+            const activeSheetName = workingTemplate.SPR_ITM_LST_SETUP?.ACT_SHEET;
 
-              const matchingDataSheets = dataSheets.filter(ds => 
-                ds.SHEET_NAME === activeSheetName
-              );
-  
-              if (activeSheetName && spread.getSheetFromName(activeSheetName)) {
-                spread.setActiveSheet(activeSheetName);
+            if (activeSheetName && spread.getSheetFromName(activeSheetName)) {
+              spread.setActiveSheet(activeSheetName);
+            }
+
+            dataSheets.forEach(dataSheet => {
+
+              const sheetName = dataSheet.SHEET_NAME;
+              // 해당 시트가 존재하는지 확인
+              const targetSheet = spread.getSheetFromName(sheetName);
+              if (!targetSheet) {
+                console.warn(`⚠️ Sheet '${sheetName}' not found in template`);
+                return;
               }
 
-              matchingDataSheets.forEach(dataSheet => {
+              console.log(`📋 Processing sheet: ${sheetName}`);
+
+              // 해당 시트로 전환
+              spread.setActiveSheet(sheetName);
+              const currentSheet = spread.getActiveSheet();
+
+
               dataSheet.MAP_CELL_ATT?.forEach((mapping: any) => {
                 const { ATT_ID, IN } = mapping;
                 const cellPos = parseCellAddress(IN);
+
+
                 if (cellPos) {
                   const isEditable = isFieldEditable(ATT_ID);
                   const value = selectedRow[ATT_ID];
-                  
+
                   mappings.push({
                     attId: ATT_ID,
                     cellAddress: IN,
                     isEditable: isEditable,
                     dataRowIndex: 0
                   });
-                  
-                  const cell = activeSheet.getCell(cellPos.row, cellPos.col);
+
+                  const cell = currentSheet.getCell(cellPos.row, cellPos.col);
                   cell.value(value ?? null);
-                  
-                  const style = createCellStyle(activeSheet, cellPos.row, cellPos.col, isEditable);
-                  activeSheet.setStyle(cellPos.row, cellPos.col, style);
-                  
+
+                  const style = createCellStyle(currentSheet, cellPos.row, cellPos.col, isEditable);
+                  currentSheet.setStyle(cellPos.row, cellPos.col, style);
+
                   const columnConfig = columnsJSON.find(col => col.key === ATT_ID);
                   if (columnConfig?.type === "LIST" && columnConfig.options && isEditable) {
-                    setupOptimizedListValidation(activeSheet, cellPos, columnConfig.options, 1);
+                    setupOptimizedListValidation(currentSheet, cellPos, columnConfig.options, 1);
                   }
                 }
               });
+
+                // 🔧 마지막에 activeSheetName으로 다시 전환
+            if (activeSheetName && spread.getSheetFromName(activeSheetName)) {
+              spread.setActiveSheet(activeSheetName);
+              activeSheet = spread.getActiveSheet();
+            }
+
+            
             });
           }
         }
 
         updateProgress('Configuring interactions...', 90, 100);
         setCellMappings(mappings);
-        
+
         const finalActiveSheet = getSafeActiveSheet(spread, 'setupEvents');
         if (finalActiveSheet) {
           setupSheetProtectionAndEvents(finalActiveSheet, mappings);
@@ -1201,20 +1238,20 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
       toast.info("No changes to save");
       return;
     }
-  
+
     const errors = validateAllData();
     if (errors.length > 0) {
       toast.error(`Cannot save: ${errors.length} validation errors found. Please fix them first.`);
       return;
     }
-  
+
     try {
       setIsPending(true);
       const activeSheet = currentSpread.getActiveSheet();
-  
+
       if (templateType === 'SPREAD_ITEM' && selectedRow) {
         const dataToSave = { ...selectedRow };
-  
+
         cellMappings.forEach(mapping => {
           if (mapping.isEditable) {
             const cellPos = parseCellAddress(mapping.cellAddress);
@@ -1224,59 +1261,59 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
             }
           }
         });
-  
+
         dataToSave.TAG_NO = selectedRow.TAG_NO;
-  
+
         const { success, message } = await updateFormDataInDB(
           formCode,
           contractItemId,
           dataToSave
         );
-  
+
         if (!success) {
           toast.error(message);
           return;
         }
-  
+
         toast.success("Changes saved successfully!");
         onUpdateSuccess?.(dataToSave);
-  
+
       } else if ((templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') && tableData.length > 0) {
         console.log('🔍 Starting batch save process...');
-        
+
         const updatedRows: GenericData[] = [];
         let saveCount = 0;
         let checkedCount = 0;
-  
+
         for (let i = 0; i < tableData.length; i++) {
           const originalRow = tableData[i];
           const dataToSave = { ...originalRow };
           let hasRowChanges = false;
-          
+
           console.log(`🔍 Processing row ${i} (TAG_NO: ${originalRow.TAG_NO})`);
-  
+
           cellMappings.forEach(mapping => {
             if (mapping.dataRowIndex === i && mapping.isEditable) {
               checkedCount++;
-              
+
               // 🔧 isFieldEditable과 동일한 로직 사용
               const rowData = tableData[i];
               const fieldEditable = isFieldEditable(mapping.attId, rowData);
-              
+
               console.log(`  📝 Field ${mapping.attId}: fieldEditable=${fieldEditable}, mapping.isEditable=${mapping.isEditable}`);
-              
+
               if (fieldEditable) {
                 const cellPos = parseCellAddress(mapping.cellAddress);
                 if (cellPos) {
                   const cellValue = activeSheet.getValue(cellPos.row, cellPos.col);
                   const originalValue = originalRow[mapping.attId];
-                  
+
                   // 🔧 개선된 값 비교 (타입 변환 및 null/undefined 처리)
                   const normalizedCellValue = cellValue === null || cellValue === undefined ? "" : String(cellValue).trim();
                   const normalizedOriginalValue = originalValue === null || originalValue === undefined ? "" : String(originalValue).trim();
-                  
+
                   console.log(`    🔍 ${mapping.attId}: "${normalizedOriginalValue}" -> "${normalizedCellValue}"`);
-                  
+
                   if (normalizedCellValue !== normalizedOriginalValue) {
                     dataToSave[mapping.attId] = cellValue;
                     hasRowChanges = true;
@@ -1286,18 +1323,18 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
               }
             }
           });
-  
+
           if (hasRowChanges) {
             console.log(`💾 Saving row ${i} with changes`);
             dataToSave.TAG_NO = originalRow.TAG_NO;
-            
+
             try {
               const { success, message } = await updateFormDataInDB(
                 formCode,
                 contractItemId,
                 dataToSave
               );
-  
+
               if (success) {
                 updatedRows.push(dataToSave);
                 saveCount++;
@@ -1317,9 +1354,9 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
             console.log(`ℹ️ No changes in row ${i}`);
           }
         }
-  
+
         console.log(`📊 Save summary: ${saveCount} saved, ${checkedCount} fields checked`);
-  
+
         if (saveCount > 0) {
           toast.success(`${saveCount} rows saved successfully!`);
           onUpdateSuccess?.(updatedRows);
@@ -1328,10 +1365,10 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
           toast.warning("No actual changes were found to save. Please check if the values were properly edited.");
         }
       }
-  
+
       setHasChanges(false);
       setValidationErrors([]);
-  
+
     } catch (error) {
       console.error("Error saving changes:", error);
       toast.error("An unexpected error occurred while saving");
@@ -1339,16 +1376,16 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
       setIsPending(false);
     }
   }, [
-    currentSpread, 
-    hasChanges, 
-    templateType, 
-    selectedRow, 
-    tableData, 
-    formCode, 
-    contractItemId, 
-    onUpdateSuccess, 
-    cellMappings, 
-    columnsJSON, 
+    currentSpread,
+    hasChanges,
+    templateType,
+    selectedRow,
+    tableData,
+    formCode,
+    contractItemId,
+    onUpdateSuccess,
+    cellMappings,
+    columnsJSON,
     validateAllData,
     isFieldEditable  // 🔧 의존성 추가
   ]);
@@ -1357,11 +1394,11 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
 
   const isDataValid = templateType === 'SPREAD_ITEM' ? !!selectedRow : tableData.length > 0;
   const dataCount = templateType === 'SPREAD_ITEM' ? 1 : tableData.length;
-  
+
   return (
     <Dialog open={isOpen} onOpenChange={onClose}>
       <DialogContent
-        className="w-[90vw] max-w-[1400px] h-[85vh] flex flex-col fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-50"
+        className="w-[95vw] max-w-[95vw] h-[90vh] flex flex-col fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-50"
       >
         <DialogHeader className="flex-shrink-0">
           <DialogTitle>SEDP Template - {formCode}</DialogTitle>
@@ -1439,13 +1476,13 @@ const createGrdListTableOptimized = React.useCallback((activeSheet: any, templat
 
         <div className="flex-1 overflow-hidden relative">
           {/* 🆕 로딩 프로그레스 오버레이 */}
-          <LoadingProgress 
+          <LoadingProgress
             phase={loadingProgress?.phase || ''}
             progress={loadingProgress?.progress || 0}
             total={loadingProgress?.total || 100}
             isVisible={isInitializing && !!loadingProgress}
           />
-          
+
           {selectedTemplate && isClient && isDataValid ? (
             <SpreadSheets
               key={`${templateType}-${selectedTemplate.TMPL_ID}-${selectedTemplateId}`}
-- 
cgit v1.2.3

