diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-20 11:37:31 +0000 |
| commit | aa86729f9a2ab95346a2851e3837de1c367aae17 (patch) | |
| tree | b601b18b6724f2fb449c7fa9ea50cbd652a8077d /components/form-data/spreadJS-dialog.tsx | |
| parent | 95bbe9c583ff841220da1267630e7b2025fc36dc (diff) | |
(대표님) 20250620 작업사항
Diffstat (limited to 'components/form-data/spreadJS-dialog.tsx')
| -rw-r--r-- | components/form-data/spreadJS-dialog.tsx | 375 |
1 files changed, 323 insertions, 52 deletions
diff --git a/components/form-data/spreadJS-dialog.tsx b/components/form-data/spreadJS-dialog.tsx index 69232508..4a8550cb 100644 --- a/components/form-data/spreadJS-dialog.tsx +++ b/components/form-data/spreadJS-dialog.tsx @@ -1,69 +1,340 @@ +"use client"; + import * as React from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { GenericData } from "./export-excel-form"; import { SpreadSheets, Worksheet, Column } from "@mescius/spread-sheets-react"; 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"; +interface TemplateItem { + TMPL_ID: string; + NAME: string; + TMPL_TYPE: string; + SPR_LST_SETUP: { + ACT_SHEET: string; + HIDN_SHEETS: Array<string>; + CONTENT?: string; // SpreadSheets JSON + 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; // SpreadSheets JSON + 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: any; - selectedRow: GenericData; - formCode: string; - } + isOpen: boolean; + onClose: () => void; + templateData: TemplateItem[] | any; // 배열 또는 기존 형태 + selectedRow: GenericData; + formCode: string; + contractItemId: number; + /** 업데이트 성공 시 호출될 콜백 */ + onUpdateSuccess?: (updatedValues: Record<string, any>) => void; +} export function TemplateViewDialog({ - isOpen, - onClose, - templateData, - selectedRow, - formCode - }: TemplateViewDialogProps) { - 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'}`} - </DialogDescription> - </DialogHeader> - - {/* 스크롤 가능한 콘텐츠 영역 */} - <div className="flex-1 overflow-y-auto space-y-4 py-4"> - {/* 여기에 템플릿 데이터나 다른 콘텐츠가 들어갈 예정 */} - <div className="space-y-4"> - <p className="text-sm text-muted-foreground"> - Template content will be displayed here... - </p> - - {/* 임시로 templateData 표시 */} - {templateData && ( - <pre className="bg-muted p-4 rounded text-sm overflow-auto"> - {JSON.stringify(templateData, null, 2)} - </pre> - )} + isOpen, + onClose, + templateData, + selectedRow, + formCode, + contractItemId, + 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>(""); + + // 템플릿 데이터를 배열로 정규화하고 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]; + } + + // CONTENT가 있는 템플릿만 필터링 + 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]); + + // 템플릿 변경 시 기본 선택 + React.useEffect(() => { + if (normalizedTemplates.length > 0 && !selectedTemplateId) { + setSelectedTemplateId(normalizedTemplates[0].TMPL_ID); + } + }, [normalizedTemplates, selectedTemplateId]); + + const initSpread = React.useCallback((spread: any) => { + if (!spread || !selectedTemplate) return; + + try { + setCurrentSpread(spread); + setHasChanges(false); // 템플릿 로드 시 변경사항 초기화 + + // CONTENT 찾기 (SPR_LST_SETUP 또는 SPR_ITM_LST_SETUP 중 하나) + let contentJson = null; + if (selectedTemplate.SPR_LST_SETUP?.CONTENT) { + contentJson = selectedTemplate.SPR_LST_SETUP.CONTENT; + 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; + console.log('Using SPR_ITM_LST_SETUP.CONTENT for template:', selectedTemplate.NAME); + } + + if (contentJson) { + console.log('Loading template content for:', selectedTemplate.NAME); + + const jsonData = typeof contentJson === 'string' + ? JSON.parse(contentJson) + : contentJson; + + // fromJSON으로 템플릿 구조 로드 + spread.fromJSON(jsonData); + } else { + console.warn('No CONTENT found in template:', selectedTemplate.NAME); + return; + } + + // 값 변경 이벤트 리스너 추가 (간단한 변경사항 감지만) + const activeSheet = spread.getActiveSheet(); + + 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); + }); + + } catch (error) { + console.error('Error initializing spread:', error); + toast.error('Failed to load template'); + } + }, [selectedTemplate]); + + // 템플릿 변경 핸들러 + const handleTemplateChange = (templateId: string) => { + setSelectedTemplateId(templateId); + setHasChanges(false); // 템플릿 변경 시 변경사항 초기화 + + // SpreadSheets 재초기화는 useCallback 의존성에 의해 자동으로 처리됨 + if (currentSpread) { + // 강제로 재초기화 + setTimeout(() => { + initSpread(currentSpread); + }, 100); + } + }; + + // 변경사항 저장 함수 + const handleSaveChanges = React.useCallback(async () => { + if (!currentSpread || !hasChanges) { + toast.info("No changes to save"); + return; + } + + try { + setIsPending(true); + + // SpreadSheets에서 현재 데이터를 JSON으로 추출 + const spreadJson = currentSpread.toJSON(); + console.log('Current spread data:', spreadJson); + + // 실제 데이터 추출 방법은 SpreadSheets 구조에 따라 달라질 수 있음 + // 여기서는 기본적인 예시만 제공 + const activeSheet = currentSpread.getActiveSheet(); + + // 간단한 예시: 특정 범위의 데이터를 추출하여 selectedRow 형태로 변환 + // 실제 구현에서는 템플릿의 구조에 맞춰 데이터를 추출해야 함 + const dataToSave = { + ...selectedRow, // 기본값으로 원본 데이터 사용 + // 여기에 SpreadSheets에서 변경된 값들을 추가 + // 예: TAG_DESC: activeSheet.getValue(특정행, 특정열) + }; + + // 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]); + + 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 /> + <span className="text-xs text-muted-foreground"> + Template content will be loaded directly. Manual data entry may be required. + </span> + </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> - - <DialogFooter className="flex-shrink-0"> - <Button variant="outline" onClick={onClose}> - Close - </Button> + )} + + {/* SpreadSheets 컴포넌트 영역 */} + <div className="flex-1 overflow-hidden"> + {selectedTemplate ? ( + <SpreadSheets + key={selectedTemplateId} // 템플릿 변경 시 컴포넌트 재생성 + workbookInitialized={initSpread} + hostStyle={hostStyle} + /> + ) : ( + <div className="flex items-center justify-center h-full text-muted-foreground"> + No template available + </div> + )} + </div> + + <DialogFooter className="flex-shrink-0"> + <Button variant="outline" onClick={onClose}> + Close + </Button> + + {hasChanges && ( <Button variant="default" - onClick={() => { - // 여기에 Template 적용 로직 추가 가능 - console.log('Apply template logic here'); - onClose(); - }} + onClick={handleSaveChanges} + disabled={isPending} > - Apply Template + {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 + )} + + </DialogFooter> + </DialogContent> + </Dialog> + ); +}
\ No newline at end of file |
