"use client"; /** * ComponentSettingsModal.tsx * * 인캔버스 설정 모달 — 기능 설정 / 표시 조건 / 미리보기 3탭 구조. * SettingsModalShell 모듈을 사용하여 모든 컴포넌트가 동일한 모달 형식을 유지. * card 타입은 Draft 기반 저장/취소 지원. */ import { useState, useCallback, useEffect, useRef } from "react"; import { Eye as EyeIcon, Sliders, Layers } from "lucide-react"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; import { SettingsModalShell, useModalAlert } from "./SettingsModalShell"; import type { ModalTabDef } from "./SettingsModalShell"; import { useUnsavedChangesGuard, UnsavedChangesDialog } from "@/components/common/UnsavedChangesGuard"; import { ConditionalSettingsTab } from "./ConditionalSettingsTab"; import { ComponentPreviewPanel } from "./ComponentPreviewPanel"; import { TextProperties } from "../properties/TextProperties"; import { ImageProperties } from "../properties/ImageProperties"; import { TableProperties } from "../properties/TableProperties"; import { CardProperties } from "../properties/CardProperties"; import { CalculationProperties } from "../properties/CalculationProperties"; import { BarcodeProperties } from "../properties/BarcodeProperties"; import { CheckboxProperties } from "../properties/CheckboxProperties"; import { SignatureProperties } from "../properties/SignatureProperties"; import { PageNumberProperties } from "../properties/PageNumberProperties"; import type { ComponentConfig } from "@/types/report"; const TYPE_LABELS: Record = { text: "텍스트", label: "레이블", table: "테이블", image: "이미지", divider: "구분선", signature: "서명", stamp: "도장", pageNumber: "페이지 번호", card: "카드", calculation: "계산", barcode: "바코드", checkbox: "체크박스", }; interface DataTabProps { component: ComponentConfig; onConfigChange?: (updates: Partial) => void; } function DataTab({ component, onConfigChange }: DataTabProps) { switch (component.type) { case "text": case "label": return ; case "table": return ; case "image": return ; case "signature": case "stamp": return ; case "pageNumber": return ; case "card": return ( ); case "calculation": return ; case "barcode": return ; case "checkbox": return ; default: return

이 타입은 추가 기능 설정이 없습니다.

; } } const TYPES_WITH_DATA_TAB = new Set([ "text", "label", "table", "image", "signature", "stamp", "pageNumber", "card", "calculation", "barcode", "checkbox", ]); export function ComponentSettingsModal() { const { componentModalTargetId, closeComponentModal, components, updateComponent } = useReportDesigner(); const component = components.find((c) => c.id === componentModalTargetId) ?? null; const isDivider = component?.type === "divider"; const isOpen = componentModalTargetId !== null && component !== null && !isDivider; const [activeTab, setActiveTab] = useState("content"); const [localDraft, setLocalDraft] = useState(null); const { alert, clearAlert } = useModalAlert(); const initialSnapshotRef = useRef(""); useEffect(() => { if (component) { setLocalDraft(component); initialSnapshotRef.current = JSON.stringify(component); clearAlert(); const hasData = TYPES_WITH_DATA_TAB.has(component.type); setActiveTab(hasData ? "content" : "preview"); } }, [componentModalTargetId, clearAlert]); const hasChanges = useCallback(() => { if (component?.type === "card") { if (!localDraft) return false; return JSON.stringify(localDraft) !== initialSnapshotRef.current; } if (!component) return false; return JSON.stringify(component) !== initialSnapshotRef.current; }, [localDraft, component]); const isSavingRef = useRef(false); const guard = useUnsavedChangesGuard({ hasChanges, onClose: () => { if (!isSavingRef.current && initialSnapshotRef.current && component) { const original = JSON.parse(initialSnapshotRef.current) as ComponentConfig; updateComponent(component.id, original); } isSavingRef.current = false; setLocalDraft(null); clearAlert(); closeComponentModal(); }, }); const handleSave = useCallback(() => { if (component?.type === "card" && localDraft) { updateComponent(localDraft.id, localDraft); } isSavingRef.current = true; initialSnapshotRef.current = component?.type === "card" && localDraft ? JSON.stringify(localDraft) : JSON.stringify(component); guard.doClose(); }, [component, localDraft, updateComponent, guard]); const handleDraftChange = useCallback( (updates: Partial) => { setLocalDraft((prev) => { if (!prev && component) return { ...component, ...updates }; if (!prev) return null; return { ...prev, ...updates }; }); }, [component], ); if (!component) return null; const hasDataTab = TYPES_WITH_DATA_TAB.has(component.type); const typeLabel = TYPE_LABELS[component.type] ?? component.type; const isCard = component.type === "card"; const isTable = component.type === "table"; const isText = component.type === "text" || component.type === "label"; const isImage = component.type === "image"; const hideConditionTab = isText || isImage || isDivider; const hasInternalConditionTab = isCard || isTable || hideConditionTab; const displayComponent = isCard && localDraft ? localDraft : component; const tabs: ModalTabDef[] = [ hasDataTab && { key: "content", icon: , label: "기능 설정", }, !hasInternalConditionTab && { key: "conditional", icon: , label: "표시 조건", }, { key: "preview", icon: , label: "미리보기", }, ].filter(Boolean) as ModalTabDef[]; return ( <> } tabs={tabs} activeTab={activeTab} onTabChange={setActiveTab} onSave={handleSave} onClose={guard.tryClose} alert={alert} > {activeTab === "content" && hasDataTab && ( )} {activeTab === "conditional" && !hasInternalConditionTab && ( )} {activeTab === "preview" && ( )} ); }