diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-19 07:51:27 +0000 |
| commit | 9ecdfb23fe3df6a5df86782385002c562dfc1198 (patch) | |
| tree | 4188cb7e6bf2c862d9c86a59d79946bd41217227 /components/form-data/spreadJS-dialog copy.tsx | |
| parent | b67861fbb424c7ad47ad1538f75e2945bd8890c5 (diff) | |
(대표님) rfq 히스토리, swp 등
Diffstat (limited to 'components/form-data/spreadJS-dialog copy.tsx')
| -rw-r--r-- | components/form-data/spreadJS-dialog copy.tsx | 539 |
1 files changed, 0 insertions, 539 deletions
diff --git a/components/form-data/spreadJS-dialog copy.tsx b/components/form-data/spreadJS-dialog copy.tsx deleted file mode 100644 index 5a51c2b5..00000000 --- a/components/form-data/spreadJS-dialog copy.tsx +++ /dev/null @@ -1,539 +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 { 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 } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import '@mescius/spread-sheets/styles/gc.spread.sheets.excel2016colorful.css'; - -// SpreadSheets를 동적으로 import (SSR 비활성화) -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 TemplateViewDialogProps { - isOpen: boolean; - onClose: () => void; - templateData: TemplateItem[] | any; - selectedRow: GenericData; - formCode: string; - contractItemId: number; - editableFieldsMap?: Map<string, string[]>; // 편집 가능 필드 정보 - onUpdateSuccess?: (updatedValues: Record<string, any>) => void; -} - -export function TemplateViewDialog({ - isOpen, - onClose, - templateData, - selectedRow, - formCode, - contractItemId, - 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 [selectedTemplateId, setSelectedTemplateId] = React.useState<string>(""); - const [cellMappings, setCellMappings] = React.useState<Array<{attId: string, cellAddress: string, isEditable: boolean}>>([]); - const [isClient, setIsClient] = React.useState(false); - - // 클라이언트 사이드에서만 렌더링되도록 보장 - React.useEffect(() => { - setIsClient(true); - }, []); - - // 템플릿 데이터를 배열로 정규화하고 CONTENT가 있는 것만 필터링 - const normalizedTemplates = React.useMemo((): TemplateItem[] => { - if (!templateData) return []; - - let templates: TemplateItem[]; - if (Array.isArray(templateData)) { - templates = templateData as TemplateItem[]; - } else { - templates = [templateData as TemplateItem]; - } - - return templates.filter(template => { - const sprContent = template.SPR_LST_SETUP?.CONTENT; - const sprItmContent = template.SPR_ITM_LST_SETUP?.CONTENT; - return sprContent || sprItmContent; - }); - }, [templateData]); - - // 선택된 템플릿 가져오기 - const selectedTemplate = React.useMemo(() => { - if (!selectedTemplateId) return normalizedTemplates[0]; - return normalizedTemplates.find(t => t.TMPL_ID === selectedTemplateId) || normalizedTemplates[0]; - }, [normalizedTemplates, selectedTemplateId]); - - // 현재 TAG의 편집 가능한 필드 목록 가져오기 - const editableFields = React.useMemo(() => { - if (!selectedRow?.TAG_NO || !editableFieldsMap.has(selectedRow.TAG_NO)) { - return []; - } - return editableFieldsMap.get(selectedRow.TAG_NO) || []; - }, [selectedRow?.TAG_NO, editableFieldsMap]); - - // 필드가 편집 가능한지 판별하는 함수 - const isFieldEditable = React.useCallback((attId: string) => { - // TAG_NO와 TAG_DESC는 기본적으로 편집 가능 - if (attId === "TAG_NO" || attId === "TAG_DESC") { - return true; - } - - // editableFieldsMap이 있으면 해당 리스트에 있는지 확인 - if (selectedRow?.TAG_NO && editableFieldsMap.has(selectedRow.TAG_NO)) { - return editableFields.includes(attId); - } - - return false; - }, [selectedRow?.TAG_NO, editableFieldsMap, editableFields]); - - // 셀 주소를 행과 열로 변환하는 함수 (예: "M1" -> {row: 0, col: 12}) - 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; - - // 열 문자를 숫자로 변환 (A=0, B=1, ..., Z=25, AA=26, ...) - let col = 0; - for (let i = 0; i < colStr.length; i++) { - col = col * 26 + (colStr.charCodeAt(i) - 65 + 1); - } - col -= 1; // 0-based index로 변환 - - const row = parseInt(rowStr) - 1; // 0-based index로 변환 - - return { row, col }; - }; - - // 템플릿 변경 시 기본 선택 - React.useEffect(() => { - if (normalizedTemplates.length > 0 && !selectedTemplateId) { - setSelectedTemplateId(normalizedTemplates[0].TMPL_ID); - } - }, [normalizedTemplates, selectedTemplateId]); - - const initSpread = React.useCallback((spread: any) => { - if (!spread || !selectedTemplate || !selectedRow) return; - - try { - setCurrentSpread(spread); - setHasChanges(false); - - // CONTENT 찾기 - let contentJson = null; - let dataSheets = null; - - if (selectedTemplate.SPR_LST_SETUP?.CONTENT) { - contentJson = selectedTemplate.SPR_LST_SETUP.CONTENT; - dataSheets = selectedTemplate.SPR_LST_SETUP.DATA_SHEETS; - console.log('Using SPR_LST_SETUP.CONTENT for template:', selectedTemplate.NAME); - } else if (selectedTemplate.SPR_ITM_LST_SETUP?.CONTENT) { - contentJson = selectedTemplate.SPR_ITM_LST_SETUP.CONTENT; - dataSheets = selectedTemplate.SPR_ITM_LST_SETUP.DATA_SHEETS; - console.log('Using SPR_ITM_LST_SETUP.CONTENT for template:', selectedTemplate.NAME); - } - - if (!contentJson) { - console.warn('No CONTENT found in template:', selectedTemplate.NAME); - return; - } - - console.log('Loading template content for:', selectedTemplate.NAME); - - const jsonData = typeof contentJson === 'string' - ? JSON.parse(contentJson) - : contentJson; - - // 렌더링 일시 중단 (성능 향상) - spread.suspendPaint(); - - try { - // fromJSON으로 템플릿 구조 로드 - spread.fromJSON(jsonData); - - // 활성 시트 가져오기 - const activeSheet = spread.getActiveSheet(); - - // 시트 보호 먼저 해제 - activeSheet.options.isProtected = false; - - // MAP_CELL_ATT 정보를 사용해서 셀에 데이터 매핑과 스타일을 한번에 처리 - if (dataSheets && dataSheets.length > 0) { - const mappings: Array<{attId: string, cellAddress: string, isEditable: boolean}> = []; - - dataSheets.forEach(dataSheet => { - if (dataSheet.MAP_CELL_ATT) { - dataSheet.MAP_CELL_ATT.forEach(mapping => { - const { ATT_ID, IN } = mapping; - - // 셀 주소가 비어있지 않은 경우만 처리 - if (IN && IN.trim() !== "") { - const cellPos = parseCellAddress(IN); - if (cellPos) { - const isEditable = isFieldEditable(ATT_ID); - mappings.push({ - attId: ATT_ID, - cellAddress: IN, - isEditable: isEditable - }); - - // 셀 객체 가져오기 - const cell = activeSheet.getCell(cellPos.row, cellPos.col); - - // selectedRow에서 해당 값 가져와서 셀에 설정 - const value = selectedRow[ATT_ID]; - if (value !== undefined && value !== null) { - cell.value(value); - } - - // 편집 권한 설정 - cell.locked(!isEditable); - - // 즉시 스타일 적용 (기존 스타일 보존하면서) - const existingStyle = activeSheet.getStyle(cellPos.row, cellPos.col); - if (existingStyle) { - // 기존 스타일 복사 - const newStyle = Object.assign(new GC.Spread.Sheets.Style(), existingStyle); - - // 편집 권한에 따라 배경색만 변경 - if (isEditable) { - newStyle.backColor = "#f0fdf4"; // 연한 녹색 - } else { - newStyle.backColor = "#f9fafb"; // 연한 회색 - newStyle.foreColor = "#6b7280"; // 회색 글자 - } - - // 스타일 적용 - activeSheet.setStyle(cellPos.row, cellPos.col, newStyle); - } else { - // 기존 스타일이 없는 경우 새로운 스타일 생성 - const newStyle = new GC.Spread.Sheets.Style(); - if (isEditable) { - newStyle.backColor = "#f0fdf4"; - } else { - newStyle.backColor = "#f9fafb"; - newStyle.foreColor = "#6b7280"; - } - activeSheet.setStyle(cellPos.row, cellPos.col, newStyle); - } - - console.log(`Mapped ${ATT_ID} (${value}) to cell ${IN} - ${isEditable ? 'Editable' : 'Read-only'}`); - } - } - }); - } - }); - - setCellMappings(mappings); - - // 시트 보호 설정 - activeSheet.options.isProtected = true; - activeSheet.options.protectionOptions = { - allowSelectLockedCells: true, - allowSelectUnlockedCells: true, - allowSort: false, - allowFilter: false, - allowEditObjects: false, - allowResizeRows: false, - allowResizeColumns: false - }; - - // 이벤트 리스너 추가 - activeSheet.bind(GC.Spread.Sheets.Events.CellChanged, (event: any, info: any) => { - console.log('Cell changed:', info); - setHasChanges(true); - }); - - activeSheet.bind(GC.Spread.Sheets.Events.ValueChanged, (event: any, info: any) => { - console.log('Value changed:', info); - setHasChanges(true); - }); - - // 편집 시작 시 읽기 전용 셀 확인 - activeSheet.bind(GC.Spread.Sheets.Events.EditStarting, (event: any, info: any) => { - const mapping = mappings.find(m => { - const cellPos = parseCellAddress(m.cellAddress); - return cellPos && cellPos.row === info.row && cellPos.col === info.col; - }); - - if (mapping && !mapping.isEditable) { - toast.warning(`${mapping.attId} field is read-only`); - info.cancel = true; - } - }); - } - } finally { - // 렌더링 재개 (모든 변경사항이 한번에 화면에 표시됨) - spread.resumePaint(); - } - - } catch (error) { - console.error('Error initializing spread:', error); - toast.error('Failed to load template'); - // 에러 발생 시에도 렌더링 재개 - if (spread && spread.resumePaint) { - spread.resumePaint(); - } - } - }, [selectedTemplate, selectedRow, isFieldEditable]); - - // 템플릿 변경 핸들러 - const handleTemplateChange = (templateId: string) => { - setSelectedTemplateId(templateId); - setHasChanges(false); - - if (currentSpread) { - setTimeout(() => { - initSpread(currentSpread); - }, 100); - } - }; - - // 변경사항 저장 함수 - const handleSaveChanges = React.useCallback(async () => { - if (!currentSpread || !hasChanges || !selectedRow) { - toast.info("No changes to save"); - return; - } - - try { - setIsPending(true); - - const activeSheet = currentSpread.getActiveSheet(); - const dataToSave = { ...selectedRow }; - - // cellMappings를 사용해서 편집 가능한 셀의 값만 추출 - 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; - } - } - }); - - // TAG_NO는 절대 변경되지 않도록 원본 값으로 강제 설정 - dataToSave.TAG_NO = selectedRow.TAG_NO; - - console.log('Data to save (TAG_NO preserved):', dataToSave); - - const { success, message } = await updateFormDataInDB( - formCode, - contractItemId, - dataToSave - ); - - if (!success) { - toast.error(message); - return; - } - - toast.success("Changes saved successfully!"); - - const updatedData = { - ...selectedRow, - ...dataToSave, - }; - - onUpdateSuccess?.(updatedData); - setHasChanges(false); - - } catch (error) { - console.error("Error saving changes:", error); - toast.error("An unexpected error occurred while saving"); - } finally { - setIsPending(false); - } - }, [currentSpread, hasChanges, formCode, contractItemId, selectedRow, onUpdateSuccess, cellMappings]); - - if (!isOpen) return null; - - return ( - <Dialog open={isOpen} onOpenChange={onClose}> - <DialogContent - className="w-[80%] max-w-none h-[80vh] flex flex-col" - style={{maxWidth:"80vw"}} - > - <DialogHeader className="flex-shrink-0"> - <DialogTitle>SEDP Template - {formCode}</DialogTitle> - <DialogDescription> - {selectedRow && `Selected TAG_NO: ${selectedRow.TAG_NO || 'N/A'}`} - {hasChanges && ( - <span className="ml-2 text-orange-600 font-medium"> - • Unsaved changes - </span> - )} - <br /> - <div className="flex items-center gap-4 mt-2"> - <span className="text-xs 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-xs 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> - {cellMappings.length > 0 && ( - <span className="text-xs text-blue-600"> - {cellMappings.filter(m => m.isEditable).length} of {cellMappings.length} fields editable - </span> - )} - </div> - </DialogDescription> - </DialogHeader> - - {/* 템플릿 선택 UI */} - {normalizedTemplates.length > 1 && ( - <div className="flex-shrink-0 px-4 py-2 border-b"> - <div className="flex items-center gap-2"> - <label className="text-sm font-medium">Template:</label> - <Select value={selectedTemplateId} onValueChange={handleTemplateChange}> - <SelectTrigger className="w-64"> - <SelectValue placeholder="Select a template" /> - </SelectTrigger> - <SelectContent> - {normalizedTemplates.map((template) => ( - <SelectItem key={template.TMPL_ID} value={template.TMPL_ID}> - <div className="flex flex-col"> - <span>{template.NAME || `Template ${template.TMPL_ID.slice(0, 8)}`}</span> - <span className="text-xs text-muted-foreground">{template.TMPL_TYPE}</span> - </div> - </SelectItem> - ))} - </SelectContent> - </Select> - <span className="text-xs text-muted-foreground"> - ({normalizedTemplates.length} templates available) - </span> - </div> - </div> - )} - - {/* SpreadSheets 컴포넌트 영역 */} - <div className="flex-1 overflow-hidden"> - {selectedTemplate && isClient ? ( - <SpreadSheets - key={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... - </> - ) : ( - "No template available" - )} - </div> - )} - </div> - - <DialogFooter className="flex-shrink-0"> - <Button variant="outline" onClick={onClose}> - Close - </Button> - - {hasChanges && ( - <Button - variant="default" - onClick={handleSaveChanges} - disabled={isPending} - > - {isPending ? ( - <> - <Loader className="mr-2 h-4 w-4 animate-spin" /> - Saving... - </> - ) : ( - <> - <Save className="mr-2 h-4 w-4" /> - Save Changes - </> - )} - </Button> - )} - - </DialogFooter> - </DialogContent> - </Dialog> - ); -}
\ No newline at end of file |
