summaryrefslogtreecommitdiff
path: root/components/form-data/spreadJS-dialog copy.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-19 07:51:27 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-19 07:51:27 +0000
commit9ecdfb23fe3df6a5df86782385002c562dfc1198 (patch)
tree4188cb7e6bf2c862d9c86a59d79946bd41217227 /components/form-data/spreadJS-dialog copy.tsx
parentb67861fbb424c7ad47ad1538f75e2945bd8890c5 (diff)
(대표님) rfq 히스토리, swp 등
Diffstat (limited to 'components/form-data/spreadJS-dialog copy.tsx')
-rw-r--r--components/form-data/spreadJS-dialog copy.tsx539
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