summaryrefslogtreecommitdiff
path: root/components/form-data/spreadJS-dialog copy 4.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/form-data/spreadJS-dialog copy 4.tsx')
-rw-r--r--components/form-data/spreadJS-dialog copy 4.tsx1491
1 files changed, 0 insertions, 1491 deletions
diff --git a/components/form-data/spreadJS-dialog copy 4.tsx b/components/form-data/spreadJS-dialog copy 4.tsx
deleted file mode 100644
index 14f4d3ea..00000000
--- a/components/form-data/spreadJS-dialog copy 4.tsx
+++ /dev/null
@@ -1,1491 +0,0 @@
-"use client";
-
-import * as React from "react";
-import dynamic from "next/dynamic";
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
-import { Button } from "@/components/ui/button";
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
-import { GenericData } from "./export-excel-form";
-import * as GC from "@mescius/spread-sheets";
-import { toast } from "sonner";
-import { updateFormDataInDB } from "@/lib/forms/services";
-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";
-
-const SpreadSheets = dynamic(
- () => import("@mescius/spread-sheets-react").then(mod => mod.SpreadSheets),
- {
- ssr: false,
- loading: () => (
- <div className="flex items-center justify-center h-full">
- <Loader className="mr-2 h-4 w-4 animate-spin" />
- Loading SpreadSheets...
- </div>
- )
- }
-);
-
-if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_SPREAD_LICENSE) {
- GC.Spread.Sheets.LicenseKey = process.env.NEXT_PUBLIC_SPREAD_LICENSE;
-}
-
-interface TemplateItem {
- TMPL_ID: string;
- NAME: string;
- TMPL_TYPE: string;
- SPR_LST_SETUP: {
- ACT_SHEET: string;
- HIDN_SHEETS: Array<string>;
- CONTENT?: string;
- DATA_SHEETS: Array<{
- SHEET_NAME: string;
- REG_TYPE_ID: string;
- MAP_CELL_ATT: Array<{
- ATT_ID: string;
- IN: string;
- }>;
- }>;
- };
- GRD_LST_SETUP: {
- REG_TYPE_ID: string;
- SPR_ITM_IDS: Array<string>;
- ATTS: Array<{}>;
- };
- SPR_ITM_LST_SETUP: {
- ACT_SHEET: string;
- HIDN_SHEETS: Array<string>;
- CONTENT?: string;
- DATA_SHEETS: Array<{
- SHEET_NAME: string;
- REG_TYPE_ID: string;
- MAP_CELL_ATT: Array<{
- ATT_ID: string;
- IN: string;
- }>;
- }>;
- };
-}
-
-interface ValidationError {
- cellAddress: string;
- attId: string;
- value: any;
- expectedType: ColumnType;
- message: string;
-}
-
-interface CellMapping {
- attId: string;
- cellAddress: string;
- isEditable: boolean;
- dataRowIndex?: number;
-}
-
-interface TemplateViewDialogProps {
- isOpen: boolean;
- onClose: () => void;
- templateData: TemplateItem[] | any;
- selectedRow?: GenericData;
- tableData?: GenericData[];
- formCode: string;
- columnsJSON: DataTableColumnJSON[]
- contractItemId: number;
- editableFieldsMap?: Map<string, string[]>;
- 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,
- templateData,
- selectedRow,
- tableData = [],
- formCode,
- contractItemId,
- columnsJSON,
- editableFieldsMap = new Map(),
- onUpdateSuccess
-}: TemplateViewDialogProps) {
- const [hostStyle, setHostStyle] = React.useState({
- width: '100%',
- height: '100%'
- });
-
- const [isPending, setIsPending] = React.useState(false);
- const [hasChanges, setHasChanges] = React.useState(false);
- const [currentSpread, setCurrentSpread] = React.useState<any>(null);
- const [cellMappings, setCellMappings] = React.useState<CellMapping[]>([]);
- const [isClient, setIsClient] = React.useState(false);
- const [templateType, setTemplateType] = React.useState<'SPREAD_LIST' | 'SPREAD_ITEM' | 'GRD_LIST' | null>(null);
- 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;
-
- let templates: TemplateItem[];
- if (Array.isArray(templateData)) {
- templates = templateData as TemplateItem[];
- } else {
- templates = [templateData as TemplateItem];
- }
-
- const validTemplates = templates.filter(isValidTemplate);
- setAvailableTemplates(validTemplates);
-
- if (validTemplates.length > 0 && !selectedTemplateId) {
- const firstTemplate = validTemplates[0];
- const templateTypeToSet = determineTemplateType(firstTemplate);
- setSelectedTemplateId(firstTemplate.TMPL_ID);
- setTemplateType(templateTypeToSet);
- }
- }, [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);
- setTemplateType(templateTypeToSet);
- setHasChanges(false);
- setValidationErrors([]);
-
- 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์˜ ๊ฒฝ์šฐ์—๋งŒ ์ „์—ญ editableFields ์‚ฌ์šฉ
- if (templateType === 'SPREAD_ITEM' && selectedRow?.TAG_NO) {
- if (!editableFieldsMap.has(selectedRow.TAG_NO)) {
- return [];
- }
- 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)) {
- 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 ์˜์กด์„ฑ ์ œ๊ฑฐ
-
-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++;
- }
- }
- });
- });
-
- 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}>
- ) => {
- 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 createCellStyle = React.useCallback((isEditable: boolean) => {
- const style = new GC.Spread.Sheets.Style();
- if (isEditable) {
- style.backColor = "#bbf7d0";
- } else {
- style.backColor = "#e5e7eb";
- style.foreColor = "#4b5563";
- }
- return style;
- }, []);
-
- 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;
-
- const match = address.match(/^([A-Z]+)(\d+)$/);
- if (!match) return null;
-
- const [, colStr, rowStr] = match;
-
- let col = 0;
- for (let i = 0; i < colStr.length; i++) {
- col = col * 26 + (colStr.charCodeAt(i) - 65 + 1);
- }
- col -= 1;
-
- const row = parseInt(rowStr) - 1;
- return { row, col };
- };
-
- const getCellAddress = (row: number, col: number): string => {
- let colStr = '';
- let colNum = col;
- while (colNum >= 0) {
- colStr = String.fromCharCode((colNum % 26) + 65) + colStr;
- colNum = Math.floor(colNum / 26) - 1;
- }
- return colStr + (row + 1);
- };
-
- const validateCellValue = (value: any, columnType: ColumnType, options?: string[]): string | null => {
- if (value === undefined || value === null || value === "") {
- return null;
- }
-
- switch (columnType) {
- case "NUMBER":
- if (isNaN(Number(value))) {
- return "Value must be a valid number";
- }
- break;
- case "LIST":
- if (options && !options.includes(String(value))) {
- return `Value must be one of: ${options.join(", ")}`;
- }
- break;
- case "STRING":
- break;
- default:
- break;
- }
-
- return null;
- };
-
- const validateAllData = React.useCallback(() => {
- if (!currentSpread || !selectedTemplate) return [];
-
- const activeSheet = currentSpread.getActiveSheet();
- const errors: ValidationError[] = [];
-
- cellMappings.forEach(mapping => {
- const columnConfig = columnsJSON.find(col => col.key === mapping.attId);
- if (!columnConfig) return;
-
- const cellPos = parseCellAddress(mapping.cellAddress);
- if (!cellPos) return;
-
- 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
- });
- }
- });
-
- setValidationErrors(errors);
- return errors;
- }, [currentSpread, selectedTemplate, cellMappings, columnsJSON]);
-
-
-
- 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);
-
- const safeOptions = options
- .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)
- .slice(0, 20);
-
- if (safeOptions.length === 0) {
- console.warn(`โš ๏ธ No valid options found, skipping`);
- return;
- }
-
- const optionsString = safeOptions.join(',');
-
- 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);
- comboBoxCellType.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.text);
-
- // ๐Ÿ”ง DataValidation ์„ค์ •
- const cellValidator = GC.Spread.Sheets.DataValidation.createListValidator(optionsString);
- 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(`โœ… Dropdown applied to [${targetRow}, ${cellPos.col}] with ${safeOptions.length} options`);
-
- } catch (cellError) {
- console.warn(`โš ๏ธ Failed to apply dropdown to row ${cellPos.row + i}:`, cellError);
- }
- }
-
- 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) => {
- 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;
- }
- }, []);
-
- 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;
- }
- }, []);
-
- 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);
- });
- }, []);
-
- // ๐Ÿš€ ์ตœ์ ํ™”๋œ 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`);
- }
- }
-
- // ํ—ค๋” ์ƒ์„ฑ
- 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[]; // ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ ํ–‰๋งŒ ์ถ”์ 
- }> = [];
-
- 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
- });
- }
- }
-
- 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);
-
- // ๐ŸŽฏ ๊ฐœ์„ ๋œ ๋“œ๋กญ๋‹ค์šด ์„ค์ • (ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ ์…€์—๋งŒ)
- 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);
- }
- });
- } 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 (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]);
-
- const setupSheetProtectionAndEvents = React.useCallback((activeSheet: any, mappings: CellMapping[]) => {
- console.log(`๐Ÿ›ก๏ธ Setting up protection and events for ${mappings.length} mappings`);
-
- // ๐Ÿ”ง ์‹œํŠธ ๋ณดํ˜ธ ์™„์ „ ํ•ด์ œ ํ›„ ํŽธ์ง‘ ๊ถŒํ•œ ์„ค์ •
- activeSheet.options.isProtected = false;
-
- // ๐Ÿ”ง ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ ์…€๋“ค์„ ์œ„ํ•œ ๊ฐ•ํ™”๋œ ์„ค์ •
- 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);
- }
- });
-
- // ๐Ÿ›ก๏ธ ์‹œํŠธ ๋ณดํ˜ธ ์žฌ์„ค์ • (ํŽธ์ง‘ ํ—ˆ์šฉ ๋ชจ๋“œ๋กœ)
- activeSheet.options.isProtected = false;
- activeSheet.options.protectionOptions = {
- allowSelectLockedCells: true,
- allowSelectUnlockedCells: true,
- allowSort: false,
- allowFilter: false,
- allowEditObjects: true, // โœ… ํŽธ์ง‘ ๊ฐ์ฒด ํ—ˆ์šฉ
- allowResizeRows: false,
- allowResizeColumns: false,
- allowFormatCells: false,
- allowInsertRows: false,
- allowInsertColumns: false,
- allowDeleteRows: false,
- allowDeleteColumns: false
- };
-
- // ๐ŸŽฏ ๋ณ€๊ฒฝ ๊ฐ์ง€ ์ด๋ฒคํŠธ
- const changeEvents = [
- GC.Spread.Sheets.Events.CellChanged,
- GC.Spread.Sheets.Events.ValueChanged,
- GC.Spread.Sheets.Events.ClipboardPasted
- ];
-
- changeEvents.forEach(eventType => {
- activeSheet.bind(eventType, () => {
- console.log(`๐Ÿ“ ${eventType} detected`);
- setHasChanges(true);
- });
- });
-
- // ๐Ÿšซ ํŽธ์ง‘ ์‹œ์ž‘ ๊ถŒํ•œ ํ™•์ธ
- 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;
- });
-
- if (!exactMapping) {
- console.log(`โ„น๏ธ No mapping found for [${info.row}, ${info.col}] - allowing edit`);
- return; // ๋งคํ•‘์ด ์—†์œผ๋ฉด ํ—ˆ์šฉ
- }
-
- 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`);
- info.cancel = true;
- return;
- }
-
- // SPREAD_LIST/GRD_LIST ๊ฐœ๋ณ„ ํ–‰ SHI ํ™•์ธ
- if ((templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') && exactMapping.dataRowIndex !== undefined) {
- const dataRowIndex = exactMapping.dataRowIndex;
- if (dataRowIndex >= 0 && dataRowIndex < tableData.length) {
- const rowData = tableData[dataRowIndex];
- 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;
- return;
- }
- }
- }
-
- 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}, 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) return;
-
- const columnConfig = columnsJSON.find(col => col.key === exactMapping.attId);
- if (columnConfig) {
- const cellValue = activeSheet.getValue(info.row, info.col);
- const errorMessage = validateCellValue(cellValue, columnConfig.type, columnConfig.options);
- const cell = activeSheet.getCell(info.row, info.col);
-
- if (errorMessage) {
- // ๐Ÿšจ ์—๋Ÿฌ ์Šคํƒ€์ผ ์ ์šฉ
- const errorStyle = new GC.Spread.Sheets.Style();
- errorStyle.backColor = "#fef2f2";
- errorStyle.foreColor = "#dc2626";
- errorStyle.borderLeft = new GC.Spread.Sheets.LineBorder("#dc2626", GC.Spread.Sheets.LineStyle.thick);
- 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}`, { duration: 5000 });
- } else {
- // โœ… ์ •์ƒ ์Šคํƒ€์ผ ๋ณต์›
- const normalStyle = createCellStyle(exactMapping.isEditable);
- activeSheet.setStyle(info.row, info.col, normalStyle);
- cell.locked(!exactMapping.isEditable);
- }
- }
-
- setHasChanges(true);
- });
-
- console.log(`๐Ÿ›ก๏ธ Protection configured. Editable cells: ${mappings.filter(m => m.isEditable).length}`);
- }, [templateType, tableData, createCellStyle, validateCellValue, columnsJSON]);
-
- // ๐Ÿš€ ์ตœ์ ํ™”๋œ initSpread
- const initSpread = React.useCallback(async (spread: any, template?: TemplateItem) => {
- const workingTemplate = template || selectedTemplate;
- 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 {
- let activeSheet = getSafeActiveSheet(spread, 'initSpread');
- if (!activeSheet) {
- throw new Error('Failed to get initial activeSheet');
- }
-
- activeSheet.options.isProtected = false;
- let mappings: CellMapping[] = [];
-
- 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;
-
- if (!contentJson || !dataSheets) {
- throw new Error(`No template content found for ${workingTemplate.NAME}`);
- }
-
- 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 (templateType === 'SPREAD_LIST' && tableData.length > 0) {
- updateProgress('Processing data rows...', 60, 100);
-
- dataSheets.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;
- 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);
-
- 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 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, 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.`);
- 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);
- if (cellPos) {
- const cellValue = activeSheet.getValue(cellPos.row, cellPos.col);
- dataToSave[mapping.attId] = cellValue;
- }
- }
- });
-
- 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;
- console.log(` โœ… Change detected for ${mapping.attId}`);
- }
- }
- }
- }
- });
-
- 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++;
- console.log(`โœ… Row ${i} saved successfully`);
- } else {
- console.error(`โŒ Failed to save row ${i}: ${message}`);
- toast.error(`Failed to save row ${i + 1}: ${message}`);
- updatedRows.push(originalRow); // ์›๋ณธ ๋ฐ์ดํ„ฐ ์œ ์ง€
- }
- } catch (error) {
- console.error(`โŒ Error saving row ${i}:`, error);
- toast.error(`Error saving row ${i + 1}`);
- updatedRows.push(originalRow); // ์›๋ณธ ๋ฐ์ดํ„ฐ ์œ ์ง€
- }
- } else {
- updatedRows.push(originalRow);
- 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);
- } else {
- console.warn(`โš ๏ธ No changes detected despite hasChanges=${hasChanges}`);
- 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");
- } finally {
- setIsPending(false);
- }
- }, [
- currentSpread,
- hasChanges,
- templateType,
- selectedRow,
- tableData,
- formCode,
- contractItemId,
- onUpdateSuccess,
- cellMappings,
- columnsJSON,
- validateAllData,
- isFieldEditable // ๐Ÿ”ง ์˜์กด์„ฑ ์ถ”๊ฐ€
- ]);
-
- 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-[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>
- <Select value={selectedTemplateId} onValueChange={handleTemplateChange}>
- <SelectTrigger className="w-64">
- <SelectValue placeholder="Select a template" />
- </SelectTrigger>
- <SelectContent>
- {availableTemplates.map(template => (
- <SelectItem key={template.TMPL_ID} value={template.TMPL_ID}>
- {template.NAME} ({template.TMPL_TYPE})
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </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)'
- }
- </span>
- {templateType === 'SPREAD_ITEM' && selectedRow && (
- <span>โ€ข Selected TAG_NO: {selectedRow.TAG_NO || 'N/A'}</span>
- )}
- {(templateType === 'SPREAD_LIST' || templateType === 'GRD_LIST') && (
- <span>โ€ข {dataCount} rows</span>
- )}
- {hasChanges && (
- <span className="text-orange-600 font-medium">
- โ€ข Unsaved changes
- </span>
- )}
- {validationErrors.length > 0 && (
- <span className="text-red-600 font-medium flex items-center">
- <AlertTriangle className="w-4 h-4 mr-1" />
- {validationErrors.length} validation errors
- </span>
- )}
- </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>
- Editable fields
- </span>
- <span className="text-muted-foreground">
- <span className="inline-block w-3 h-3 bg-gray-100 border border-gray-300 mr-1"></span>
- Read-only fields
- </span>
- <span className="text-muted-foreground">
- <span className="inline-block w-3 h-3 bg-red-100 border border-red-300 mr-1"></span>
- Validation errors
- </span>
- {cellMappings.length > 0 && (
- <span className="text-blue-600">
- {editableFieldsCount} of {cellMappings.length} fields editable
- </span>
- )}
- </div>
- </div>
- </DialogDescription>
- </DialogHeader>
-
- <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}`}
- workbookInitialized={initSpread}
- hostStyle={hostStyle}
- />
- ) : (
- <div className="flex items-center justify-center h-full text-muted-foreground">
- {!isClient ? (
- <>
- <Loader className="mr-2 h-4 w-4 animate-spin" />
- Loading...
- </>
- ) : !selectedTemplate ? (
- "No template available"
- ) : !isDataValid ? (
- `No ${templateType === 'SPREAD_ITEM' ? 'selected row' : 'data'} available`
- ) : (
- "Template not ready"
- )}
- </div>
- )}
- </div>
-
- <DialogFooter className="flex-shrink-0">
- <div className="flex items-center gap-2">
- <Button variant="outline" onClick={onClose}>
- Close
- </Button>
-
- {hasChanges && (
- <Button
- variant="default"
- onClick={handleSaveChanges}
- disabled={isPending || validationErrors.length > 0}
- >
- {isPending ? (
- <>
- <Loader className="mr-2 h-4 w-4 animate-spin" />
- Saving...
- </>
- ) : (
- <>
- <Save className="mr-2 h-4 w-4" />
- Save Changes
- </>
- )}
- </Button>
- )}
-
- {validationErrors.length > 0 && (
- <Button
- variant="outline"
- onClick={validateAllData}
- className="text-red-600 border-red-300 hover:bg-red-50"
- >
- <AlertTriangle className="mr-2 h-4 w-4" />
- Check Errors ({validationErrors.length})
- </Button>
- )}
- </div>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- );
-} \ No newline at end of file