"use client"; /** * CardProperties.tsx — 카드 컴포넌트 설정 * * - section="data": 모달 내 기능 설정 탭에서 CardLayoutTabs 직접 렌더링 * - section="style": 우측 패널에서 프리셋 + 8개 스타일 섹션 제공 * - onConfigChange: Draft 모드 — 모달에서 저장 전 로컬 변경용 */ import { useMemo, useCallback, useState } from "react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { ChevronRight } from "lucide-react"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; import type { ComponentConfig, CardLayoutConfig } from "@/types/report"; import { CardLayoutTabs } from "../modals/CardLayoutTabs"; interface Props { component: ComponentConfig; section?: "style" | "data"; onConfigChange?: (updates: Partial) => void; } const generateId = () => `row_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`; const DEFAULT_CONFIG: CardLayoutConfig = { tableName: "", primaryKey: "", rows: [{ id: generateId(), gridColumns: 2, elements: [] }], padding: "12px", gap: "8px", borderStyle: "solid", borderColor: "#e5e7eb", backgroundColor: "#ffffff", headerTitleFontSize: 14, headerTitleColor: "#1e40af", labelFontSize: 13, labelColor: "#374151", valueFontSize: 13, valueColor: "#000000", dividerThickness: 1, dividerColor: "#e5e7eb", }; const CARD_STYLE_PRESETS = { info: { backgroundColor: "#ffffff", borderStyle: "solid", borderColor: "#e5e7eb", borderWidth: 1, accentBorderWidth: 0, borderRadius: "12px", headerFontWeight: "bold" as const, headerTitleColor: "#111827", labelColor: "#6b7280", valueFontWeight: "normal" as const, valueColor: "#111827", dividerColor: "#3b82f6", dividerThickness: 1, }, compact: { backgroundColor: "#eff6ff", borderStyle: "none", borderWidth: 0, accentBorderColor: "#3b82f6", accentBorderWidth: 4, borderRadius: "8px", headerFontWeight: "normal" as const, headerTitleColor: "#6b7280", labelColor: "#6b7280", valueFontWeight: "bold" as const, valueColor: "#111827", dividerColor: "#e5e7eb", dividerThickness: 1, }, } as const; function StyleAccordion({ label, isOpen, onToggle, children, }: { label: string; isOpen: boolean; onToggle: () => void; children: React.ReactNode; }) { return (
{isOpen && (
{children}
)}
); } export function CardProperties({ component, section, onConfigChange }: Props) { const { updateComponent } = useReportDesigner(); const showStyle = !section || section === "style"; const showData = !section || section === "data"; const [openStyleSections, setOpenStyleSections] = useState>(new Set(["preset"])); const toggleStyleSection = (id: string) => { setOpenStyleSections((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const config = useMemo(() => { return component.cardLayoutConfig || DEFAULT_CONFIG; }, [component.cardLayoutConfig]); const handleConfigChange = useCallback( (newConfig: CardLayoutConfig) => { const updates = { cardLayoutConfig: newConfig }; if (onConfigChange) onConfigChange(updates); else updateComponent(component.id, updates); }, [component.id, onConfigChange, updateComponent], ); const updateDesignConfig = useCallback( (updates: Partial) => { handleConfigChange({ ...config, ...updates }); }, [config, handleConfigChange], ); const applyPreset = useCallback( (presetKey: keyof typeof CARD_STYLE_PRESETS) => { updateDesignConfig(CARD_STYLE_PRESETS[presetKey]); }, [updateDesignConfig], ); return ( <> {showData && ( )} {showStyle && (
toggleStyleSection("preset")}>
toggleStyleSection("appearance")}>
updateDesignConfig({ backgroundColor: e.target.value })} className="h-7 w-7 shrink-0 cursor-pointer rounded border-0 p-0" /> updateDesignConfig({ backgroundColor: e.target.value })} className="h-7 border-0 bg-transparent px-1 font-mono text-xs shadow-none focus-visible:ring-0" />
toggleStyleSection("accent")}>
0} onCheckedChange={(checked) => updateDesignConfig({ accentBorderWidth: checked ? 4 : 0, accentBorderColor: config.accentBorderColor || "#3b82f6" })} />
{(config.accentBorderWidth ?? 0) > 0 && (
updateDesignConfig({ accentBorderWidth: parseInt(e.target.value) || 4 })} className="h-9 text-xs" />
updateDesignConfig({ accentBorderColor: e.target.value })} className="h-7 w-7 shrink-0 cursor-pointer rounded border-0 p-0" /> updateDesignConfig({ accentBorderColor: e.target.value })} className="h-7 border-0 bg-transparent px-1 font-mono text-xs shadow-none focus-visible:ring-0" />
)}
)} ); }