From 33be47506f0aa62b969d82521580a29e95080268 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 13 Aug 2025 11:05:09 +0000
Subject: (대표님) 입찰, 법무검토, EDP 변경사항 대응, dolce 개선, form-data
 개선, 정규업체 등록관리 추가 (최겸) pq 미사용 컴포넌트 및 페이지 제거, 파일
 라우트에 pq 적용
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/form-data/spreadJS-dialog.tsx | 1157 ++++++++++++++++--------------
 1 file changed, 621 insertions(+), 536 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 11d37911..54a70d9d 100644
--- a/components/form-data/spreadJS-dialog.tsx
+++ b/components/form-data/spreadJS-dialog.tsx
@@ -13,7 +13,6 @@ import { Loader, Save, AlertTriangle } from "lucide-react";
 import '@mescius/spread-sheets/styles/gc.spread.sheets.excel2016colorful.css';
 import { DataTableColumnJSON, ColumnType } from "./form-data-table-columns";
 
-// SpreadSheets를 동적으로 import (SSR 비활성화)
 const SpreadSheets = dynamic(
   () => import("@mescius/spread-sheets-react").then(mod => mod.SpreadSheets),
   {
@@ -27,7 +26,6 @@ const SpreadSheets = dynamic(
   }
 );
 
-// 라이센스 키 설정을 클라이언트에서만 실행
 if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_SPREAD_LICENSE) {
   GC.Spread.Sheets.LicenseKey = process.env.NEXT_PUBLIC_SPREAD_LICENSE;
 }
@@ -88,8 +86,8 @@ interface TemplateViewDialogProps {
   isOpen: boolean;
   onClose: () => void;
   templateData: TemplateItem[] | any;
-  selectedRow?: GenericData; // SPREAD_ITEM용
-  tableData?: GenericData[]; // SPREAD_LIST용
+  selectedRow?: GenericData;
+  tableData?: GenericData[];
   formCode: string;
   columnsJSON: DataTableColumnJSON[]
   contractItemId: number;
@@ -97,6 +95,44 @@ interface TemplateViewDialogProps {
   onUpdateSuccess?: (updatedValues: Record<string, any> | GenericData[]) => void;
 }
 
+// 🚀 로딩 프로그레스 컴포넌트
+interface LoadingProgressProps {
+  phase: string;
+  progress: number;
+  total: number;
+  isVisible: boolean;
+}
+
+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]">
+        <div className="flex items-center space-x-3 mb-4">
+          <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 
+              className="bg-blue-600 h-2 rounded-full transition-all duration-300 ease-out"
+              style={{ width: `${percentage}%` }}
+            />
+          </div>
+          <div className="text-xs text-gray-500 text-right">
+            {progress.toLocaleString()} / {total.toLocaleString()} ({percentage}%)
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
 export function TemplateViewDialog({
   isOpen,
   onClose,
@@ -123,13 +159,41 @@ 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;
+    progress: number;
+    total: number;
+  } | null>(null);
+  const [isInitializing, setIsInitializing] = React.useState(false);
+
+  // 🔄 진행상황 업데이트 함수
+  const updateProgress = React.useCallback((phase: string, progress: number, total: number) => {
+    setLoadingProgress({ phase, progress, total });
+  }, []);
+
+  const determineTemplateType = React.useCallback((template: TemplateItem): 'SPREAD_LIST' | 'SPREAD_ITEM' | 'GRD_LIST' | null => {
+    if (template.TMPL_TYPE === "SPREAD_LIST" && (template.SPR_LST_SETUP?.CONTENT || template.SPR_ITM_LST_SETUP?.CONTENT)) {
+      return 'SPREAD_LIST';
+    }
+    if (template.TMPL_TYPE === "SPREAD_ITEM" && (template.SPR_LST_SETUP?.CONTENT || template.SPR_ITM_LST_SETUP?.CONTENT)) {
+      return 'SPREAD_ITEM';
+    }
+    if (template.GRD_LST_SETUP && columnsJSON.length > 0) {
+      return 'GRD_LIST';
+    }
+    return null;
+  }, [columnsJSON]);
+
+  const isValidTemplate = React.useCallback((template: TemplateItem): boolean => {
+    return determineTemplateType(template) !== null;
+  }, [determineTemplateType]);
 
-  // 클라이언트 사이드에서만 렌더링되도록 보장
   React.useEffect(() => {
     setIsClient(true);
   }, []);
 
-  // 사용 가능한 템플릿들을 필터링하고 설정
   React.useEffect(() => {
     if (!templateData) return;
 
@@ -140,134 +204,158 @@ export function TemplateViewDialog({
       templates = [templateData as TemplateItem];
     }
 
-    // 유효한 템플릿들 필터링
-    const validTemplates = templates.filter(template => {
-      const hasSpreadListContent = template.SPR_LST_SETUP?.CONTENT;
-      const hasSpreadItemContent = template.SPR_ITM_LST_SETUP?.CONTENT;
-      const hasGrdListSetup = template.GRD_LST_SETUP && columnsJSON.length > 0; // GRD_LIST 조건: GRD_LST_SETUP 존재 + columnsJSON 있음
-      
-      const isValidType = template.TMPL_TYPE === "SPREAD_LIST" || 
-                         template.TMPL_TYPE === "SPREAD_ITEM" || 
-                         template.TMPL_TYPE === "GRD_LIST"; // GRD_LIST 타입 추가
-
-      return isValidType && (hasSpreadListContent || hasSpreadItemContent || hasGrdListSetup);
-    });
-
+    const validTemplates = templates.filter(isValidTemplate);
     setAvailableTemplates(validTemplates);
 
-    // 첫 번째 유효한 템플릿을 기본으로 선택
     if (validTemplates.length > 0 && !selectedTemplateId) {
       const firstTemplate = validTemplates[0];
+      const templateTypeToSet = determineTemplateType(firstTemplate);
       setSelectedTemplateId(firstTemplate.TMPL_ID);
-      
-      // 템플릿 타입 결정
-      let templateTypeToSet: 'SPREAD_LIST' | 'SPREAD_ITEM' | 'GRD_LIST';
-      if (firstTemplate.GRD_LST_SETUP && columnsJSON.length > 0) {
-        templateTypeToSet = 'GRD_LIST';
-      } else {
-        templateTypeToSet = firstTemplate.TMPL_TYPE as 'SPREAD_LIST' | 'SPREAD_ITEM';
-      }
-      
       setTemplateType(templateTypeToSet);
     }
-  }, [templateData, selectedTemplateId]);
+  }, [templateData, selectedTemplateId, isValidTemplate, determineTemplateType]);
 
-  // 선택된 템플릿 변경 처리
   const handleTemplateChange = (templateId: string) => {
     const template = availableTemplates.find(t => t.TMPL_ID === templateId);
     if (template) {
+      const templateTypeToSet = determineTemplateType(template);
       setSelectedTemplateId(templateId);
-      
-      // 템플릿 타입 결정
-      let templateTypeToSet: 'SPREAD_LIST' | 'SPREAD_ITEM' | 'GRD_LIST';
-      if (template.GRD_LST_SETUP && columnsJSON.length > 0) {
-        templateTypeToSet = 'GRD_LIST';
-      } else {
-        templateTypeToSet = template.TMPL_TYPE as 'SPREAD_LIST' | 'SPREAD_ITEM';
-      }
-      
       setTemplateType(templateTypeToSet);
       setHasChanges(false);
       setValidationErrors([]);
 
-      // SpreadSheets 재초기화
-      if (currentSpread) {
-        const template = availableTemplates.find(t => t.TMPL_ID === templateId);
-        if (template) {
-          initSpread(currentSpread, template);
-        }
+      if (currentSpread && template) {
+        initSpread(currentSpread, template);
       }
     }
   };
 
-  // 현재 선택된 템플릿 가져오기
   const selectedTemplate = React.useMemo(() => {
     return availableTemplates.find(t => t.TMPL_ID === selectedTemplateId);
   }, [availableTemplates, selectedTemplateId]);
 
-  // 편집 가능한 필드 목록 계산
   const editableFields = React.useMemo(() => {
-    // SPREAD_ITEM인 경우: selectedRow의 TAG_NO로 확인
     if (templateType === 'SPREAD_ITEM' && selectedRow?.TAG_NO) {
       if (!editableFieldsMap.has(selectedRow.TAG_NO)) {
         return [];
       }
       return editableFieldsMap.get(selectedRow.TAG_NO) || [];
     }
-    
-    // SPREAD_LIST 또는 GRD_LIST인 경우: 첫 번째 행의 TAG_NO를 기준으로 처리
+
     if ((templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') && tableData.length > 0) {
       const firstRowTagNo = tableData[0]?.TAG_NO;
       if (firstRowTagNo && editableFieldsMap.has(firstRowTagNo)) {
         return editableFieldsMap.get(firstRowTagNo) || [];
       }
     }
-    
+
     return [];
   }, [templateType, selectedRow?.TAG_NO, tableData, editableFieldsMap]);
 
-  // 필드가 편집 가능한지 판별하는 함수
   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이면 편집 불가
+      return false;
     }
 
-    // TAG_NO와 TAG_DESC는 기본적으로 편집 가능 (columnsJSON의 shi가 false인 경우)
-    if (attId === "TAG_NO" || attId === "TAG_DESC") {
-      return true;
+    if (attId === "TAG_NO" || attId === "TAG_DESC" || attId === "status") {
+      return false;
     }
 
-    // SPREAD_ITEM인 경우: editableFields 체크
-    if (templateType === 'SPREAD_ITEM') {
-      return editableFields.includes(attId);
-    }
-    
-    // SPREAD_LIST 또는 GRD_LIST인 경우: 개별 행의 편집 가능성도 고려
     if (templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') {
-      // 기본적으로 editableFields에 포함되어야 함
-      if (!editableFields.includes(attId)) {
-        return false;
-      }
-      
-      // rowData가 제공된 경우 해당 행의 shi 상태도 확인
       if (rowData && rowData.shi === true) {
         return false;
       }
-      
       return true;
     }
-    
+
     return true;
-  }, [templateType, columnsJSON, editableFields]);
+  }, [templateType, columnsJSON]);
 
-  // 편집 가능한 필드 개수 계산
   const editableFieldsCount = React.useMemo(() => {
     return cellMappings.filter(m => m.isEditable).length;
   }, [cellMappings]);
 
-  // 셀 주소를 행과 열로 변환하는 함수
+  // 🚀 배치 처리 함수들
+  const setBatchValues = React.useCallback((
+    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}) => {
+      if (!columnGroups.has(col)) {
+        columnGroups.set(col, []);
+      }
+      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]);
+          } else {
+            const dataArray = rangeValues.map(v => [v]);
+            activeSheet.setArray(startRow, col, dataArray);
+          }
+        } catch (error) {
+          for (let i = start; i <= end; i++) {
+            try {
+              activeSheet.setValue(values[i].row, col, values[i].value);
+            } catch (cellError) {
+              console.warn(`⚠️ Individual value setting failed [${values[i].row}, ${col}]:`, cellError);
+            }
+          }
+        }
+        
+        start = end + 1;
+      }
+    });
+  }, []);
+
+  const setBatchStyles = React.useCallback((
+    activeSheet: any, 
+    stylesToSet: Array<{row: number, col: number, isEditable: boolean}>
+  ) => {
+    console.log(`🎨 Setting ${stylesToSet.length} styles in batch`);
+    
+    const editableStyle = createCellStyle(true);
+    const readonlyStyle = createCellStyle(false);
+    
+    // 🔧 개별 셀별로 스타일과 잠금 상태 설정 (편집 권한 보장)
+    stylesToSet.forEach(({row, col, isEditable}) => {
+      try {
+        const cell = activeSheet.getCell(row, col);
+        const style = isEditable ? editableStyle : readonlyStyle;
+        
+        activeSheet.setStyle(row, col, style);
+        cell.locked(!isEditable); // 편집 가능하면 잠금 해제
+        
+        // 🆕 편집 가능한 셀에 기본 텍스트 에디터 설정
+        if (isEditable) {
+          const textCellType = new GC.Spread.Sheets.CellTypes.Text();
+          activeSheet.setCellType(row, col, textCellType);
+        }
+      } catch (error) {
+        console.warn(`⚠️ Failed to set style for cell [${row}, ${col}]:`, error);
+      }
+    });
+  }, [createCellStyle]);
+
   const parseCellAddress = (address: string): { row: number, col: number } | null => {
     if (!address || address.trim() === "") return null;
 
@@ -283,11 +371,9 @@ export function TemplateViewDialog({
     col -= 1;
 
     const row = parseInt(rowStr) - 1;
-
     return { row, col };
   };
 
-  // 행과 열을 셀 주소로 변환하는 함수 (GRD_LIST용)
   const getCellAddress = (row: number, col: number): string => {
     let colStr = '';
     let colNum = col;
@@ -298,10 +384,9 @@ export function TemplateViewDialog({
     return colStr + (row + 1);
   };
 
-  // 데이터 타입 검증 함수
   const validateCellValue = (value: any, columnType: ColumnType, options?: string[]): string | null => {
     if (value === undefined || value === null || value === "") {
-      return null; // 빈 값은 별도 required 검증에서 처리
+      return null;
     }
 
     switch (columnType) {
@@ -316,17 +401,14 @@ export function TemplateViewDialog({
         }
         break;
       case "STRING":
-        // STRING 타입은 대부분의 값을 허용
         break;
       default:
-        // 커스텀 타입의 경우 추가 검증 로직이 필요할 수 있음
         break;
     }
 
     return null;
   };
 
-  // 전체 데이터 검증 함수
   const validateAllData = React.useCallback(() => {
     if (!currentSpread || !selectedTemplate) return [];
 
@@ -340,68 +422,44 @@ export function TemplateViewDialog({
       const cellPos = parseCellAddress(mapping.cellAddress);
       if (!cellPos) return;
 
-      if (templateType === 'SPREAD_ITEM') {
-        // 단일 행 검증
-        const cellValue = activeSheet.getValue(cellPos.row, cellPos.col);
-        const errorMessage = validateCellValue(cellValue, columnConfig.type, columnConfig.options);
-
-        if (errorMessage) {
-          errors.push({
-            cellAddress: mapping.cellAddress,
-            attId: mapping.attId,
-            value: cellValue,
-            expectedType: columnConfig.type,
-            message: errorMessage
-          });
-        }
-      } else if (templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') {
-        // 복수 행 검증 - 각 매핑은 이미 개별 행을 가리킴
-        const cellValue = activeSheet.getValue(cellPos.row, cellPos.col);
-        const errorMessage = validateCellValue(cellValue, columnConfig.type, columnConfig.options);
+      const cellValue = activeSheet.getValue(cellPos.row, cellPos.col);
+      const errorMessage = validateCellValue(cellValue, columnConfig.type, columnConfig.options);
 
-        if (errorMessage) {
-          errors.push({
-            cellAddress: mapping.cellAddress,
-            attId: mapping.attId,
-            value: cellValue,
-            expectedType: columnConfig.type,
-            message: errorMessage
-          });
-        }
+      if (errorMessage) {
+        errors.push({
+          cellAddress: mapping.cellAddress,
+          attId: mapping.attId,
+          value: cellValue,
+          expectedType: columnConfig.type,
+          message: errorMessage
+        });
       }
     });
 
     setValidationErrors(errors);
     return errors;
-  }, [currentSpread, selectedTemplate, cellMappings, columnsJSON, templateType]);
-
-  // ═══════════════════════════════════════════════════════════════════════════════
-  // 🛠️ 헬퍼 함수들
-  // ═══════════════════════════════════════════════════════════════════════════════
+  }, [currentSpread, selectedTemplate, cellMappings, columnsJSON]);
 
-  // 🎨 셀 스타일 생성
   const createCellStyle = React.useCallback((isEditable: boolean) => {
     const style = new GC.Spread.Sheets.Style();
     if (isEditable) {
-      style.backColor = "#f0fdf4"; // 연한 초록 (편집 가능)
+      style.backColor = "#f0fdf4";
     } else {
-      style.backColor = "#f9fafb"; // 연한 회색 (읽기 전용)
+      style.backColor = "#f9fafb";
       style.foreColor = "#6b7280";
     }
     return style;
   }, []);
 
-  // 🎯 드롭다운 설정
   const setupOptimizedListValidation = React.useCallback((activeSheet: any, cellPos: { row: number, col: number }, options: string[], rowCount: number) => {
     try {
       console.log(`🎯 Setting up dropdown for ${rowCount} rows with options:`, options);
 
-      // ✅ options 정규화
       const safeOptions = options
-        .filter(opt => opt !== null && opt !== undefined && opt !== '') 
+        .filter(opt => opt !== null && opt !== undefined && opt !== '')
         .map(opt => String(opt).trim())
         .filter(opt => opt.length > 0)
-        .filter((opt, index, arr) => arr.indexOf(opt) === index) 
+        .filter((opt, index, arr) => arr.indexOf(opt) === index)
         .slice(0, 20);
 
       if (safeOptions.length === 0) {
@@ -409,280 +467,333 @@ export function TemplateViewDialog({
         return;
       }
 
-      console.log(`📋 Safe options:`, safeOptions);
-
-      // ✅ DataValidation용 문자열 준비
       const optionsString = safeOptions.join(',');
 
-      // 🔑 핵심 수정: 각 셀마다 개별 ComboBox 인스턴스 생성!
       for (let i = 0; i < rowCount; i++) {
         try {
           const targetRow = cellPos.row + i;
           
-          // ✅ 각 셀마다 새로운 ComboBox 인스턴스 생성
+          // 🔧 각 셀마다 새로운 ComboBox 인스턴스 생성
           const comboBoxCellType = new GC.Spread.Sheets.CellTypes.ComboBox();
-          comboBoxCellType.items(safeOptions); // 배열로 전달
+          comboBoxCellType.items(safeOptions);
           comboBoxCellType.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.text);
-          
-          // ✅ 각 셀마다 새로운 DataValidation 인스턴스 생성
+
+          // 🔧 DataValidation 설정
           const cellValidator = GC.Spread.Sheets.DataValidation.createListValidator(optionsString);
-          
-          // ComboBox + DataValidation 둘 다 적용
+          cellValidator.showInputMessage(false);
+          cellValidator.showErrorMessage(false);
+
+          // ComboBox와 Validator 적용
           activeSheet.setCellType(targetRow, cellPos.col, comboBoxCellType);
           activeSheet.setDataValidator(targetRow, cellPos.col, cellValidator);
           
-          // 셀 잠금 해제
+          // 🚀 중요: 셀 잠금 해제 및 편집 가능 설정
           const cell = activeSheet.getCell(targetRow, cellPos.col);
           cell.locked(false);
-          
-          console.log(`✅ Individual dropdown applied to [${targetRow}, ${cellPos.col}]`);
-          
+
+          console.log(`✅ Dropdown applied to [${targetRow}, ${cellPos.col}] with ${safeOptions.length} options`);
+
         } catch (cellError) {
-          console.warn(`⚠️ Failed to apply to row ${cellPos.row + i}:`, cellError);
+          console.warn(`⚠️ Failed to apply dropdown to row ${cellPos.row + i}:`, cellError);
         }
       }
 
-      console.log(`✅ Safe dropdown setup completed for ${rowCount} cells`);
+      console.log(`✅ Dropdown setup completed for ${rowCount} cells`);
 
     } catch (error) {
       console.error('❌ Dropdown setup failed:', error);
     }
   }, []);
 
-  // 🚀 행 용량 확보
+  const getSafeActiveSheet = React.useCallback((spread: any, functionName: string = 'unknown') => {
+    if (!spread) return null;
+    
+    try {
+      let activeSheet = spread.getActiveSheet();
+      if (!activeSheet) {
+        const sheetCount = spread.getSheetCount();
+        if (sheetCount > 0) {
+          activeSheet = spread.getSheet(0);
+          if (activeSheet) {
+            spread.setActiveSheetIndex(0);
+          }
+        }
+      }
+      return activeSheet;
+    } catch (error) {
+      console.error(`❌ Error getting activeSheet in ${functionName}:`, error);
+      return null;
+    }
+  }, []);
+
   const ensureRowCapacity = React.useCallback((activeSheet: any, requiredRowCount: number) => {
-    const currentRowCount = activeSheet.getRowCount();
-    if (requiredRowCount > currentRowCount) {
-      activeSheet.setRowCount(requiredRowCount + 10); // 여유분 추가
-      console.log(`📈 Expanded sheet to ${requiredRowCount + 10} rows`);
+    try {
+      if (!activeSheet) return false;
+      
+      const currentRowCount = activeSheet.getRowCount();
+      if (requiredRowCount > currentRowCount) {
+        const newRowCount = requiredRowCount + 10;
+        activeSheet.setRowCount(newRowCount);
+      }
+      return true;
+    } catch (error) {
+      console.error('❌ Error in ensureRowCapacity:', error);
+      return false;
     }
   }, []);
 
-  // 🆕 GRD_LIST용 동적 테이블 생성 함수
-  const createGrdListTable = React.useCallback((activeSheet: any, template: TemplateItem) => {
-    console.log('🏗️ Creating GRD_LIST table');
-    
-    // columnsJSON의 visible 컬럼들을 seq 순서로 정렬하여 사용
-    const visibleColumns = columnsJSON
-      .filter(col => col.hidden !== true) // hidden이 true가 아닌 것들만
-      .sort((a, b) => {
-        // seq가 없는 경우 999999로 처리하여 맨 뒤로 보냄
-        const seqA = a.seq !== undefined ? a.seq : 999999;
-        const seqB = b.seq !== undefined ? b.seq : 999999;
-        return seqA - seqB;
-      });
+  const ensureColumnCapacity = React.useCallback((activeSheet: any, requiredColumnCount: number) => {
+    try {
+      if (!activeSheet) return false;
+      
+      const currentColumnCount = activeSheet.getColumnCount();
+      if (requiredColumnCount > currentColumnCount) {
+        const newColumnCount = requiredColumnCount + 10;
+        activeSheet.setColumnCount(newColumnCount);
+      }
+      return true;
+    } catch (error) {
+      console.error('❌ Error in ensureColumnCapacity:', error);
+      return false;
+    }
+  }, []);
 
-    console.log('📊 Using columns:', visibleColumns.map(c => `${c.key}(seq:${c.seq})`));
+  const setOptimalColumnWidths = React.useCallback((activeSheet: any, columns: any[], startCol: number, tableData: any[]) => {
+    columns.forEach((column, colIndex) => {
+      const targetCol = startCol + colIndex;
+      const optimalWidth = column.type === 'NUMBER' ? 100 : column.type === 'STRING' ? 150 : 120;
+      activeSheet.setColumnWidth(targetCol, optimalWidth);
+    });
+  }, []);
 
-    if (visibleColumns.length === 0) {
-      console.warn('❌ No visible columns found in columnsJSON');
-      return [];
-    }
+  // 🚀 최적화된 GRD_LIST 생성
+  const createGrdListTableOptimized = React.useCallback((activeSheet: any, template: TemplateItem) => {
+    console.log('🚀 Creating optimized GRD_LIST table');
 
-    // 테이블 생성 시작
+    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[] = [];
-    const startCol = 1; // A열 제외하고 B열부터 시작
-    
-    // 🔍 그룹 헤더 분석
-    const groupInfo = analyzeColumnGroups(visibleColumns);
-    const hasGroups = groupInfo.groups.length > 0;
-    
-    // 헤더 행 계산: 그룹이 있으면 2행, 없으면 1행
-    const groupHeaderRow = 0;
-    const columnHeaderRow = hasGroups ? 1 : 0;
-    const dataStartRow = hasGroups ? 2 : 1;
-
-    // 🎨 헤더 스타일 생성
-    const groupHeaderStyle = new GC.Spread.Sheets.Style();
-    groupHeaderStyle.backColor = "#1e40af"; // 더 진한 파란색
-    groupHeaderStyle.foreColor = "#ffffff";
-    groupHeaderStyle.font = "bold 13px Arial";
-    groupHeaderStyle.hAlign = GC.Spread.Sheets.HorizontalAlign.center;
-    groupHeaderStyle.vAlign = GC.Spread.Sheets.VerticalAlign.center;
-
-    const columnHeaderStyle = new GC.Spread.Sheets.Style();
-    columnHeaderStyle.backColor = "#3b82f6"; // 기본 파란색
-    columnHeaderStyle.foreColor = "#ffffff";
-    columnHeaderStyle.font = "bold 12px Arial";
-    columnHeaderStyle.hAlign = GC.Spread.Sheets.HorizontalAlign.center;
-    columnHeaderStyle.vAlign = GC.Spread.Sheets.VerticalAlign.center;
-
-    let currentCol = startCol;
-
-    // 🏗️ 그룹 헤더 및 컬럼 헤더 생성
-    if (hasGroups) {
-      // 그룹 헤더가 있는 경우
-      groupInfo.groups.forEach(group => {
-        if (group.isGroup) {
-          // 그룹 헤더 생성 및 병합
-          const groupStartCol = currentCol;
-          const groupEndCol = currentCol + group.columns.length - 1;
-          
-          // 그룹 헤더 셀 설정
-          const groupHeaderCell = activeSheet.getCell(groupHeaderRow, groupStartCol);
-          groupHeaderCell.value(group.head);
-          
-          // 그룹 헤더 병합
-          if (group.columns.length > 1) {
-            activeSheet.addSpan(groupHeaderRow, groupStartCol, 1, group.columns.length);
-          }
-          
-          // 그룹 헤더 스타일 적용
-          for (let col = groupStartCol; col <= groupEndCol; col++) {
-            activeSheet.setStyle(groupHeaderRow, col, groupHeaderStyle);
-            activeSheet.getCell(groupHeaderRow, col).locked(true);
-          }
-          
-          console.log(`📝 Group Header [${groupHeaderRow}, ${groupStartCol}-${groupEndCol}]: ${group.head}`);
-          
-          // 그룹 내 개별 컬럼 헤더 생성
-          group.columns.forEach((column, index) => {
-            const colIndex = groupStartCol + index;
-            const columnHeaderCell = activeSheet.getCell(columnHeaderRow, colIndex);
-            columnHeaderCell.value(column.label);
-            activeSheet.setStyle(columnHeaderRow, colIndex, columnHeaderStyle);
-            columnHeaderCell.locked(true);
-            
-            console.log(`📝 Column Header [${columnHeaderRow}, ${colIndex}]: ${column.label}`);
-          });
-          
-          currentCol += group.columns.length;
-        } else {
-          // 그룹이 아닌 단일 컬럼
-          const column = group.columns[0];
-          
-          // 그룹 헤더 행에는 빈 셀 (개별 컬럼이므로)
-          const groupHeaderCell = activeSheet.getCell(groupHeaderRow, currentCol);
-          groupHeaderCell.value("");
-          activeSheet.setStyle(groupHeaderRow, currentCol, groupHeaderStyle);
-          groupHeaderCell.locked(true);
-          
-          // 컬럼 헤더 생성
-          const columnHeaderCell = activeSheet.getCell(columnHeaderRow, currentCol);
-          columnHeaderCell.value(column.label);
-          activeSheet.setStyle(columnHeaderRow, currentCol, columnHeaderStyle);
-          columnHeaderCell.locked(true);
-          
-          console.log(`📝 Single Column [${columnHeaderRow}, ${currentCol}]: ${column.label}`);
-          currentCol++;
-        }
-      });
-    } else {
-      // 그룹이 없는 경우 - 기존 로직
-      visibleColumns.forEach((column, colIndex) => {
-        const headerCol = startCol + colIndex;
-        const headerCell = activeSheet.getCell(columnHeaderRow, headerCol);
-        headerCell.value(column.label);
-        activeSheet.setStyle(columnHeaderRow, headerCol, columnHeaderStyle);
-        headerCell.locked(true);
-        
-        console.log(`📝 Header [${columnHeaderRow}, ${headerCol}]: ${column.label}`);
-      });
-    }
 
-    // 데이터 행 생성
-    const dataRowCount = tableData.length;
-    ensureRowCapacity(activeSheet, dataStartRow + dataRowCount);
+    ensureColumnCapacity(activeSheet, startCol + visibleColumns.length);
+    ensureRowCapacity(activeSheet, dataStartRow + tableData.length);
+
+    // 헤더 생성
+    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 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[]; // 편집 가능한 행만 추적
+    }> = [];
 
-    tableData.forEach((rowData, rowIndex) => {
-      const targetRow = dataStartRow + rowIndex;
+    visibleColumns.forEach((column, colIndex) => {
+      const targetCol = startCol + colIndex;
       
-      visibleColumns.forEach((column, colIndex) => {
-        const targetCol = startCol + colIndex;
-        const cellAddress = getCellAddress(targetRow, targetCol);
-        const isEditable = isFieldEditable(column.key, rowData);
+      // 드롭다운 설정을 위한 편집 가능한 행 찾기
+      if (column.type === "LIST" && column.options) {
+        const editableRows: number[] = [];
+        tableData.forEach((rowData, rowIndex) => {
+          if (isFieldEditable(column.key, rowData)) {
+            editableRows.push(dataStartRow + rowIndex);
+          }
+        });
         
-        // 매핑 정보 추가
+        if (editableRows.length > 0) {
+          dropdownConfigs.push({
+            startRow: dataStartRow,
+            col: targetCol,
+            rowCount: tableData.length,
+            options: column.options,
+            editableRows: editableRows
+          });
+        }
+      }
+      
+      tableData.forEach((rowData, rowIndex) => {
+        const targetRow = dataStartRow + rowIndex;
+        const cellEditable = isFieldEditable(column.key, rowData);
+        const value = rowData[column.key];
+
         mappings.push({
           attId: column.key,
-          cellAddress: cellAddress,
-          isEditable: isEditable,
+          cellAddress: getCellAddress(targetRow, targetCol),
+          isEditable: cellEditable,
           dataRowIndex: rowIndex
         });
 
-        // 셀 값 설정
-        const cell = activeSheet.getCell(targetRow, targetCol);
-        const value = rowData[column.key];
-        cell.value(value ?? null);
-
-        // 스타일 적용
-        const style = createCellStyle(isEditable);
-        activeSheet.setStyle(targetRow, targetCol, style);
-        cell.locked(!isEditable);
-
-        console.log(`📝 Data [${targetRow}, ${targetCol}]: ${column.key} = "${value}" (${isEditable ? 'Editable' : 'ReadOnly'})`);
+        allValues.push({
+          row: targetRow,
+          col: targetCol,
+          value: value ?? null
+        });
 
-        // LIST 타입 드롭다운 설정
-        if (column.type === "LIST" && column.options && isEditable) {
-          setupOptimizedListValidation(activeSheet, { row: targetRow, col: targetCol }, column.options, 1);
-        }
+        allStyles.push({
+          row: targetRow,
+          col: targetCol,
+          isEditable: cellEditable
+        });
       });
     });
 
-    console.log(`🏗️ GRD_LIST table created with ${mappings.length} mappings, hasGroups: ${hasGroups}`);
-    return mappings;
-  }, [tableData, columnsJSON, isFieldEditable, createCellStyle, ensureRowCapacity, setupOptimizedListValidation]);
-
-  // 🔍 컬럼 그룹 분석 함수
-  const analyzeColumnGroups = React.useCallback((columns: DataTableColumnJSON[]) => {
-    const groups: Array<{
-      head: string;
-      isGroup: boolean;
-      columns: DataTableColumnJSON[];
-    }> = [];
+    // 🚀 배치로 값과 스타일 설정
+    setBatchValues(activeSheet, allValues);
+    setBatchStyles(activeSheet, allStyles);
 
-    let i = 0;
-    while (i < columns.length) {
-      const currentCol = columns[i];
-      
-      // head가 없거나 빈 문자열인 경우 단일 컬럼으로 처리
-      if (!currentCol.head || !currentCol.head.trim()) {
-        groups.push({
-          head: '',
-          isGroup: false,
-          columns: [currentCol]
+    // 🎯 개선된 드롭다운 설정 (편집 가능한 셀에만)
+    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);
+
+        if (safeOptions.length === 0) return;
+
+        // 편집 가능한 행에만 드롭다운 적용
+        editableRows.forEach(targetRow => {
+          try {
+            const comboBoxCellType = new GC.Spread.Sheets.CellTypes.ComboBox();
+            comboBoxCellType.items(safeOptions);
+            comboBoxCellType.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.text);
+
+            const cellValidator = GC.Spread.Sheets.DataValidation.createListValidator(safeOptions.join(','));
+            cellValidator.showInputMessage(false);
+            cellValidator.showErrorMessage(false);
+
+            activeSheet.setCellType(targetRow, col, comboBoxCellType);
+            activeSheet.setDataValidator(targetRow, col, cellValidator);
+            
+            // 🚀 편집 권한 명시적 설정
+            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);
+          }
         });
-        i++;
-        continue;
+      } catch (error) {
+        console.error(`❌ Dropdown config failed for column ${col}:`, error);
       }
+    });
 
-      // 같은 head를 가진 연속된 컬럼들을 찾기
-      const groupHead = currentCol.head.trim();
-      const groupColumns: DataTableColumnJSON[] = [currentCol];
-      let j = i + 1;
-
-      while (j < columns.length && columns[j].head && columns[j].head.trim() === groupHead) {
-        groupColumns.push(columns[j]);
-        j++;
-      }
+    setOptimalColumnWidths(activeSheet, visibleColumns, startCol, tableData);
+    
+    console.log(`✅ Optimized GRD_LIST created:`);
+    console.log(`   - Total mappings: ${mappings.length}`);
+    console.log(`   - Editable cells: ${mappings.filter(m => m.isEditable).length}`);
+    console.log(`   - Dropdown configs: ${dropdownConfigs.length}`);
+    
+    return mappings;
+  }, [tableData, columnsJSON, isFieldEditable, setBatchValues, setBatchStyles, ensureColumnCapacity, ensureRowCapacity, getCellAddress, setOptimalColumnWidths]);
 
-      // 그룹 추가
-      groups.push({
-        head: groupHead,
-        isGroup: groupColumns.length > 1,
-        columns: groupColumns
-      });
+  const setupSheetProtectionAndEvents = React.useCallback((activeSheet: any, mappings: CellMapping[]) => {
+    console.log(`🛡️ Setting up protection and events for ${mappings.length} mappings`);
 
-      i = j; // 다음 그룹으로 이동
-    }
+    // 🔧 시트 보호 완전 해제 후 편집 권한 설정
+    activeSheet.options.isProtected = false;
 
-    return { groups };
-  }, []);
+    // 🔧 편집 가능한 셀들을 위한 강화된 설정
+    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);
+          } else if (columnConfig?.type === "NUMBER") {
+            // 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,
+              -999999999, 999999999, true
+            );
+            numberValidator.showInputMessage(false);
+            numberValidator.showErrorMessage(false);
+            activeSheet.setDataValidator(cellPos.row, cellPos.col, numberValidator);
+          } else {
+            // 기본 TEXT 타입: 자유 텍스트 입력
+            const textCellType = new GC.Spread.Sheets.CellTypes.Text();
+            activeSheet.setCellType(cellPos.row, cellPos.col, textCellType);
+          }
+          
+          // 편집 가능 스타일 재적용
+          const editableStyle = createCellStyle(true);
+          activeSheet.setStyle(cellPos.row, cellPos.col, editableStyle);
+          
+          console.log(`🔓 Cell [${cellPos.row}, ${cellPos.col}] ${mapping.attId} set as EDITABLE`);
+        } else {
+          // 읽기 전용 셀
+          cell.locked(true);
+          const readonlyStyle = createCellStyle(false);
+          activeSheet.setStyle(cellPos.row, cellPos.col, readonlyStyle);
+        }
+      } catch (error) {
+        console.error(`❌ Error processing cell ${mapping.cellAddress}:`, error);
+      }
+    });
 
-  // 🛡️ 시트 보호 및 이벤트 설정
-  const setupSheetProtectionAndEvents = React.useCallback((activeSheet: any, mappings: CellMapping[]) => {
-    console.log(`🛡️ Setting up protection and events for ${mappings.length} mappings`);
-    
-    // 시트 보호 설정
+    // 🛡️ 시트 보호 재설정 (편집 허용 모드로)
     activeSheet.options.isProtected = true;
     activeSheet.options.protectionOptions = {
       allowSelectLockedCells: true,
       allowSelectUnlockedCells: true,
       allowSort: false,
       allowFilter: false,
-      allowEditObjects: false,
+      allowEditObjects: true,        // ✅ 편집 객체 허용
       allowResizeRows: false,
-      allowResizeColumns: false
+      allowResizeColumns: false,
+      allowFormatCells: false,
+      allowInsertRows: false,
+      allowInsertColumns: false,
+      allowDeleteRows: false,
+      allowDeleteColumns: false
     };
 
     // 🎯 변경 감지 이벤트
@@ -699,11 +810,10 @@ export function TemplateViewDialog({
       });
     });
 
-    // 🚫 편집 시작 권한 확인 (수정됨)
+    // 🚫 편집 시작 권한 확인
     activeSheet.bind(GC.Spread.Sheets.Events.EditStarting, (event: any, info: any) => {
       console.log(`🎯 EditStarting: Row ${info.row}, Col ${info.col}`);
-      
-      // ✅ 정확한 매핑 찾기 (행/열 정확히 일치)
+
       const exactMapping = mappings.find(m => {
         const cellPos = parseCellAddress(m.cellAddress);
         return cellPos && cellPos.row === info.row && cellPos.col === info.col;
@@ -711,12 +821,11 @@ export function TemplateViewDialog({
 
       if (!exactMapping) {
         console.log(`ℹ️ No mapping found for [${info.row}, ${info.col}] - allowing edit`);
-        return; // 매핑이 없으면 허용 (템플릿 영역 밖)
+        return; // 매핑이 없으면 허용
       }
 
-      console.log(`📋 Found mapping: ${exactMapping.attId} at ${exactMapping.cellAddress}`);
+      console.log(`📋 Found mapping: ${exactMapping.attId}, isEditable: ${exactMapping.isEditable}`);
 
-      // 기본 편집 권한 확인
       if (!exactMapping.isEditable) {
         console.log(`🚫 Field ${exactMapping.attId} is not editable`);
         toast.warning(`${exactMapping.attId} field is read-only`);
@@ -724,12 +833,9 @@ export function TemplateViewDialog({
         return;
       }
 
-      // SPREAD_LIST 또는 GRD_LIST 개별 행 SHI 확인
+      // SPREAD_LIST/GRD_LIST 개별 행 SHI 확인
       if ((templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') && exactMapping.dataRowIndex !== undefined) {
         const dataRowIndex = exactMapping.dataRowIndex;
-        
-        console.log(`🔍 Checking SHI for data row ${dataRowIndex}`);
-        
         if (dataRowIndex >= 0 && dataRowIndex < tableData.length) {
           const rowData = tableData[dataRowIndex];
           if (rowData?.shi === true) {
@@ -738,41 +844,31 @@ export function TemplateViewDialog({
             info.cancel = true;
             return;
           }
-        } else {
-          console.warn(`⚠️ Invalid dataRowIndex: ${dataRowIndex} (tableData.length: ${tableData.length})`);
         }
       }
 
       console.log(`✅ Edit allowed for ${exactMapping.attId}`);
     });
 
-    // ✅ 편집 완료 검증 (수정됨)
+    // ✅ 편집 완료 검증
     activeSheet.bind(GC.Spread.Sheets.Events.EditEnded, (event: any, info: any) => {
-      console.log(`🏁 EditEnded: Row ${info.row}, Col ${info.col}`);
-      
-      // ✅ 정확한 매핑 찾기
+      console.log(`🏁 EditEnded: Row ${info.row}, Col ${info.col}, New value: ${activeSheet.getValue(info.row, info.col)}`);
+
       const exactMapping = mappings.find(m => {
         const cellPos = parseCellAddress(m.cellAddress);
         return cellPos && cellPos.row === info.row && cellPos.col === info.col;
       });
 
-      if (!exactMapping) {
-        console.log(`ℹ️ No mapping found for [${info.row}, ${info.col}] - skipping validation`);
-        return;
-      }
+      if (!exactMapping) return;
 
       const columnConfig = columnsJSON.find(col => col.key === exactMapping.attId);
       if (columnConfig) {
         const cellValue = activeSheet.getValue(info.row, info.col);
-        console.log(`🔍 Validating ${exactMapping.attId}: "${cellValue}"`);
-        
         const errorMessage = validateCellValue(cellValue, columnConfig.type, columnConfig.options);
         const cell = activeSheet.getCell(info.row, info.col);
 
         if (errorMessage) {
-          console.log(`❌ Validation failed: ${errorMessage}`);
-          
-          // 🚨 에러 스타일 적용 (편집 가능 상태 유지)
+          // 🚨 에러 스타일 적용
           const errorStyle = new GC.Spread.Sheets.Style();
           errorStyle.backColor = "#fef2f2";
           errorStyle.foreColor = "#dc2626";
@@ -780,222 +876,221 @@ export function TemplateViewDialog({
           errorStyle.borderRight = new GC.Spread.Sheets.LineBorder("#dc2626", GC.Spread.Sheets.LineStyle.thick);
           errorStyle.borderTop = new GC.Spread.Sheets.LineBorder("#dc2626", GC.Spread.Sheets.LineStyle.thick);
           errorStyle.borderBottom = new GC.Spread.Sheets.LineBorder("#dc2626", GC.Spread.Sheets.LineStyle.thick);
-          
+
           activeSheet.setStyle(info.row, info.col, errorStyle);
-          cell.locked(!exactMapping.isEditable);
-          
-          toast.warning(`Invalid value in ${exactMapping.attId}: ${errorMessage}. Please correct the value.`, { duration: 5000 });
+          cell.locked(!exactMapping.isEditable); // 편집 가능 상태 유지
+          toast.warning(`Invalid value in ${exactMapping.attId}: ${errorMessage}`, { duration: 5000 });
         } else {
-          console.log(`✅ Validation passed`);
-          
           // ✅ 정상 스타일 복원
           const normalStyle = createCellStyle(exactMapping.isEditable);
           activeSheet.setStyle(info.row, info.col, normalStyle);
           cell.locked(!exactMapping.isEditable);
         }
       }
+
+      setHasChanges(true);
     });
 
-    console.log(`🛡️ Protection and events configured for ${mappings.length} mappings`);
+    console.log(`🛡️ Protection configured. Editable cells: ${mappings.filter(m => m.isEditable).length}`);
   }, [templateType, tableData, createCellStyle, validateCellValue, columnsJSON]);
 
-  // ═══════════════════════════════════════════════════════════════════════════════
-  // 🏗️ 메인 SpreadSheets 초기화 함수
-  // ═══════════════════════════════════════════════════════════════════════════════
-
-  const initSpread = React.useCallback((spread: any, template?: TemplateItem) => {
+  // 🚀 최적화된 initSpread
+  const initSpread = React.useCallback(async (spread: any, template?: TemplateItem) => {
     const workingTemplate = template || selectedTemplate;
-    if (!spread || !workingTemplate) return;
+    if (!spread || !workingTemplate) {
+      console.error('❌ Invalid spread or template');
+      return;
+    }
 
     try {
-      // 🔄 초기 설정
+      console.log('🚀 Starting optimized spread initialization...');
+      setIsInitializing(true);
+      updateProgress('Initializing...', 0, 100);
+      
       setCurrentSpread(spread);
       setHasChanges(false);
       setValidationErrors([]);
 
-      // 성능을 위한 렌더링 일시 중단
+      // 🚀 핵심 최적화: 모든 렌더링과 이벤트 중단
       spread.suspendPaint();
+      spread.suspendEvent();
+      spread.suspendCalcService();
+      
+      updateProgress('Setting up workspace...', 10, 100);
 
       try {
-        const activeSheet = spread.getActiveSheet();
-        
-        // 시트 보호 해제 (편집을 위해)
-        activeSheet.options.isProtected = false;
+        let activeSheet = getSafeActiveSheet(spread, 'initSpread');
+        if (!activeSheet) {
+          throw new Error('Failed to get initial activeSheet');
+        }
 
+        activeSheet.options.isProtected = false;
         let mappings: CellMapping[] = [];
 
-        // 🆕 GRD_LIST 처리
-        if (templateType === 'GRD_LIST' && workingTemplate.GRD_LST_SETUP) {
-          console.log('🏗️ Processing GRD_LIST template');
+        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');
           
-          // 동적 테이블 생성
-          mappings = createGrdListTable(sheet, workingTemplate);
+          updateProgress('Processing table data...', 50, 100);
+          mappings = createGrdListTableOptimized(sheet, workingTemplate);
           
         } else {
-          // 기존 SPREAD_LIST 및 SPREAD_ITEM 처리
-          let contentJson = null;
-          let dataSheets = null;
-
-          // SPR_LST_SETUP.CONTENT 우선 사용
-          if (workingTemplate.SPR_LST_SETUP?.CONTENT) {
-            contentJson = workingTemplate.SPR_LST_SETUP.CONTENT;
-            dataSheets = workingTemplate.SPR_LST_SETUP.DATA_SHEETS;
-            console.log('✅ Using SPR_LST_SETUP.CONTENT for template:', workingTemplate.NAME);
-          }
-          // SPR_ITM_LST_SETUP.CONTENT 대안 사용
-          else if (workingTemplate.SPR_ITM_LST_SETUP?.CONTENT) {
-            contentJson = workingTemplate.SPR_ITM_LST_SETUP.CONTENT;
-            dataSheets = workingTemplate.SPR_ITM_LST_SETUP.DATA_SHEETS;
-            console.log('✅ Using SPR_ITM_LST_SETUP.CONTENT for template:', workingTemplate.NAME);
-          }
+          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;
 
-          if (!contentJson) {
-            console.warn('❌ No CONTENT found in template:', workingTemplate.NAME);
-            return;
+          if (!contentJson || !dataSheets) {
+            throw new Error(`No template content found for ${workingTemplate.NAME}`);
           }
 
-          // 🏗️ SpreadSheets 초기화
           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');
+          }
+
+          activeSheet.options.isProtected = false;
 
-          // 📊 셀 매핑 및 데이터 처리
-          if (dataSheets && dataSheets.length > 0) {
+          if (templateType === 'SPREAD_LIST' && tableData.length > 0) {
+            updateProgress('Processing data rows...', 60, 100);
             
-            // 🔄 각 데이터 시트의 매핑 정보 처리
             dataSheets.forEach(dataSheet => {
-              if (dataSheet.MAP_CELL_ATT) {
-                dataSheet.MAP_CELL_ATT.forEach(mapping => {
+              if (dataSheet.MAP_CELL_ATT && dataSheet.MAP_CELL_ATT.length > 0) {
+                dataSheet.MAP_CELL_ATT.forEach((mapping: any) => {
                   const { ATT_ID, IN } = mapping;
-
-                  if (IN && IN.trim() !== "") {
-                    const cellPos = parseCellAddress(IN);
-                    if (cellPos) {
-                      const columnConfig = columnsJSON.find(col => col.key === ATT_ID);
-
-                      // 🎯 템플릿 타입별 데이터 처리
-                      if (templateType === 'SPREAD_ITEM' && selectedRow) {
-                        // 📝 단일 행 처리 (SPREAD_ITEM)
-                        const isEditable = isFieldEditable(ATT_ID);
-                        
-                        // 매핑 정보 저장
-                        mappings.push({
-                          attId: ATT_ID,
-                          cellAddress: IN,
-                          isEditable: isEditable,
-                          dataRowIndex: 0
-                        });
-
-                        const cell = activeSheet.getCell(cellPos.row, cellPos.col);
-                        const value = selectedRow[ATT_ID];
-                        
-                        // 값 설정
-                        cell.value(value ?? null);
-
-                        // 🎨 스타일 및 편집 권한 설정
-                        cell.locked(!isEditable);
-                        const style = createCellStyle(isEditable);
-                        activeSheet.setStyle(cellPos.row, cellPos.col, style);
-
-                        // 📋 LIST 타입 드롭다운 설정
-                        if (columnConfig?.type === "LIST" && columnConfig.options && isEditable) {
-                          setupOptimizedListValidation(activeSheet, cellPos, columnConfig.options, 1);
-                        }
-
-                      } else if (templateType === 'SPREAD_LIST' && tableData.length > 0) {
-                        // 📊 복수 행 처리 (SPREAD_LIST) - 성능 최적화됨
-                        console.log(`🔄 Processing SPREAD_LIST for ${ATT_ID} with ${tableData.length} rows`);
-                        
-                        // 🚀 행 확장 (필요시)
-                        ensureRowCapacity(activeSheet, cellPos.row + tableData.length);
-
-                        // 📋 각 행마다 개별 매핑 생성
-                        tableData.forEach((rowData, index) => {
-                          const targetRow = cellPos.row + index;
-                          const targetCellAddress = `${String.fromCharCode(65 + cellPos.col)}${targetRow + 1}`;
-                          const cellEditable = isFieldEditable(ATT_ID, rowData);
-                          
-                          // 개별 매핑 추가
-                          mappings.push({
-                            attId: ATT_ID,
-                            cellAddress: targetCellAddress, // 각 행마다 다른 주소
-                            isEditable: cellEditable,
-                            dataRowIndex: index // 원본 데이터 인덱스
-                          });
-                          
-                          console.log(`📝 Mapping ${ATT_ID} Row ${index}: ${targetCellAddress} (${cellEditable ? 'Editable' : 'ReadOnly'})`);
-                        });
-
-                        // 📋 LIST 타입 드롭다운 설정 (조건부)
-                        if (columnConfig?.type === "LIST" && columnConfig.options) {
-                          // 편집 가능한 행이 하나라도 있으면 드롭다운 설정
-                          const hasEditableRows = tableData.some((rowData) => isFieldEditable(ATT_ID, rowData));
-                          if (hasEditableRows) {
-                            setupOptimizedListValidation(activeSheet, cellPos, columnConfig.options, tableData.length);
-                          }
-                        }
-
-                        // 🎨 개별 셀 데이터 및 스타일 설정
-                        tableData.forEach((rowData, index) => {
-                          const targetRow = cellPos.row + index;
-                          const cell = activeSheet.getCell(targetRow, cellPos.col);
-                          const value = rowData[ATT_ID];
-                          
-                          // 값 설정
-                          cell.value(value ?? null);
-                          // console.log(`📝 Row ${targetRow}: ${ATT_ID} = "${value}"`);
-                          
-                          // 편집 권한 및 스타일 설정
-                          const cellEditable = isFieldEditable(ATT_ID, rowData);
-                          cell.locked(!cellEditable);
-                          const style = createCellStyle(cellEditable);
-                          activeSheet.setStyle(targetRow, cellPos.col, style);
-                        });
-                      }
-
-                      // console.log(`📌 Mapped ${ATT_ID} → ${IN} (${templateType})`);
+                  if (!ATT_ID || !IN || IN.trim() === "") return;
+
+                  const cellPos = parseCellAddress(IN);
+                  if (!cellPos) return;
+
+                  const requiredRows = cellPos.row + tableData.length;
+                  if (!ensureRowCapacity(activeSheet, requiredRows)) return;
+
+                  // 🚀 배치 데이터 준비
+                  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;
+                    const cellEditable = isFieldEditable(ATT_ID, rowData);
+                    const value = rowData[ATT_ID];
+
+                    mappings.push({
+                      attId: ATT_ID,
+                      cellAddress: getCellAddress(targetRow, cellPos.col),
+                      isEditable: cellEditable,
+                      dataRowIndex: index
+                    });
+
+                    valuesToSet.push({
+                      row: targetRow,
+                      col: cellPos.col,
+                      value: value ?? null
+                    });
+
+                    stylesToSet.push({
+                      row: targetRow,
+                      col: cellPos.col,
+                      isEditable: cellEditable
+                    });
+                  });
+
+                  // 🚀 배치 처리
+                  setBatchValues(activeSheet, valuesToSet);
+                  setBatchStyles(activeSheet, 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);
                     }
                   }
                 });
               }
             });
+            
+          } else if (templateType === 'SPREAD_ITEM' && selectedRow) {
+            updateProgress('Setting up form fields...', 60, 100);
+            
+            dataSheets.forEach(dataSheet => {
+              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);
+                  cell.value(value ?? null);
+                  
+                  const style = createCellStyle(isEditable);
+                  activeSheet.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);
+                  }
+                }
+              });
+            });
           }
         }
 
-        // 💾 매핑 정보 저장 및 이벤트 설정
+        updateProgress('Configuring interactions...', 90, 100);
         setCellMappings(mappings);
-        setupSheetProtectionAndEvents(activeSheet, mappings);
+        
+        const finalActiveSheet = getSafeActiveSheet(spread, 'setupEvents');
+        if (finalActiveSheet) {
+          setupSheetProtectionAndEvents(finalActiveSheet, mappings);
+        }
+
+        updateProgress('Finalizing...', 100, 100);
+        console.log(`✅ Optimized initialization completed with ${mappings.length} mappings`);
 
       } finally {
-        // 렌더링 재개
+        // 🚀 올바른 순서로 재개
+        spread.resumeCalcService();
+        spread.resumeEvent();
         spread.resumePaint();
       }
 
     } catch (error) {
-      console.error('❌ Error initializing spread:', error);
-      toast.error('Failed to load template');
-      if (spread?.resumePaint) {
-        spread.resumePaint();
-      }
+      console.error('❌ Error in optimized spread initialization:', error);
+      if (spread?.resumeCalcService) spread.resumeCalcService();
+      if (spread?.resumeEvent) spread.resumeEvent();
+      if (spread?.resumePaint) spread.resumePaint();
+      toast.error(`Template loading failed: ${error.message}`);
+    } finally {
+      setIsInitializing(false);
+      setLoadingProgress(null);
     }
-  }, [selectedTemplate, templateType, selectedRow, tableData, isFieldEditable, columnsJSON, createCellStyle, setupOptimizedListValidation, ensureRowCapacity, setupSheetProtectionAndEvents, createGrdListTable]);
+  }, [selectedTemplate, templateType, selectedRow, tableData, updateProgress, getSafeActiveSheet, createGrdListTableOptimized, setBatchValues, setBatchStyles, setupSheetProtectionAndEvents, setCellMappings]);
 
-  // 변경사항 저장 함수
   const handleSaveChanges = React.useCallback(async () => {
     if (!currentSpread || !hasChanges) {
       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.`);
@@ -1004,11 +1099,9 @@ export function TemplateViewDialog({
 
     try {
       setIsPending(true);
-
       const activeSheet = currentSpread.getActiveSheet();
 
       if (templateType === 'SPREAD_ITEM' && selectedRow) {
-        // 단일 행 저장
         const dataToSave = { ...selectedRow };
 
         cellMappings.forEach(mapping => {
@@ -1038,7 +1131,6 @@ export function TemplateViewDialog({
         onUpdateSuccess?.(dataToSave);
 
       } else if ((templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') && tableData.length > 0) {
-        // 복수 행 저장 (SPREAD_LIST와 GRD_LIST 동일 처리)
         const updatedRows: GenericData[] = [];
         let saveCount = 0;
 
@@ -1047,7 +1139,6 @@ export function TemplateViewDialog({
           const dataToSave = { ...originalRow };
           let hasRowChanges = false;
 
-          // 각 매핑에 대해 해당 행의 값 확인
           cellMappings.forEach(mapping => {
             if (mapping.dataRowIndex === i && mapping.isEditable) {
               const columnConfig = columnsJSON.find(col => col.key === mapping.attId);
@@ -1058,8 +1149,6 @@ export function TemplateViewDialog({
                 const cellPos = parseCellAddress(mapping.cellAddress);
                 if (cellPos) {
                   const cellValue = activeSheet.getValue(cellPos.row, cellPos.col);
-
-                  // 값이 변경되었는지 확인
                   if (cellValue !== originalRow[mapping.attId]) {
                     dataToSave[mapping.attId] = cellValue;
                     hasRowChanges = true;
@@ -1069,10 +1158,8 @@ export function TemplateViewDialog({
             }
           });
 
-          // 변경사항이 있는 행만 저장
           if (hasRowChanges) {
-            dataToSave.TAG_NO = originalRow.TAG_NO; // TAG_NO는 절대 변경되지 않도록
-
+            dataToSave.TAG_NO = originalRow.TAG_NO;
             const { success } = await updateFormDataInDB(
               formCode,
               contractItemId,
@@ -1084,7 +1171,7 @@ export function TemplateViewDialog({
               saveCount++;
             }
           } else {
-            updatedRows.push(originalRow); // 변경사항이 없으면 원본 유지
+            updatedRows.push(originalRow);
           }
         }
 
@@ -1109,21 +1196,18 @@ export function TemplateViewDialog({
 
   if (!isOpen) return null;
 
-  // 데이터 유효성 검사
   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-[80%] max-w-none h-[80vh] flex flex-col"
-        style={{ maxWidth: "80vw" }}
+        className="w-[90vw] max-w-[1400px] h-[85vh] 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>
           <DialogDescription>
             <div className="space-y-3">
-              {/* 템플릿 선택 */}
               {availableTemplates.length > 1 && (
                 <div className="flex items-center gap-4">
                   <span className="text-sm font-medium">Template:</span>
@@ -1134,11 +1218,7 @@ export function TemplateViewDialog({
                     <SelectContent>
                       {availableTemplates.map(template => (
                         <SelectItem key={template.TMPL_ID} value={template.TMPL_ID}>
-                          {template.NAME} ({
-                            template.GRD_LST_SETUP && columnsJSON.length > 0
-                              ? 'GRD_LIST' 
-                              : template.TMPL_TYPE
-                          })
+                          {template.NAME} ({template.TMPL_TYPE})
                         </SelectItem>
                       ))}
                     </SelectContent>
@@ -1146,14 +1226,13 @@ export function TemplateViewDialog({
                 </div>
               )}
 
-              {/* 템플릿 정보 */}
               {selectedTemplate && (
                 <div className="flex items-center gap-4 text-sm">
                   <span className="font-medium text-blue-600">
                     Template Type: {
-                      templateType === 'SPREAD_LIST' ? 'List View (SPREAD_LIST)' : 
-                      templateType === 'SPREAD_ITEM' ? 'Item View (SPREAD_ITEM)' :
-                      'Grid List View (GRD_LIST)'
+                      templateType === 'SPREAD_LIST' ? 'List View (SPREAD_LIST)' :
+                        templateType === 'SPREAD_ITEM' ? 'Item View (SPREAD_ITEM)' :
+                          'Grid List View (GRD_LIST)'
                     }
                   </span>
                   {templateType === 'SPREAD_ITEM' && selectedRow && (
@@ -1176,7 +1255,6 @@ export function TemplateViewDialog({
                 </div>
               )}
 
-              {/* 범례 */}
               <div className="flex items-center gap-4 text-xs">
                 <span className="text-muted-foreground">
                   <span className="inline-block w-3 h-3 bg-green-100 border border-green-400 mr-1"></span>
@@ -1200,8 +1278,15 @@ export function TemplateViewDialog({
           </DialogDescription>
         </DialogHeader>
 
-        {/* SpreadSheets 컴포넌트 영역 */}
-        <div className="flex-1 overflow-hidden">
+        <div className="flex-1 overflow-hidden relative">
+          {/* 🆕 로딩 프로그레스 오버레이 */}
+          <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

