"use client"; /** * TableProperties.tsx — 테이블 컴포넌트 설정 * * - section="data": TableLayoutTabs (컬럼 구성 / 요약 설정 탭) * - section="style": StyleAccordion 패턴 (프리셋 + 헤더 / 셀 / 테두리) */ import { useState, useCallback } 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 { TableLayoutTabs } from "../modals/TableLayoutTabs"; import type { ComponentConfig, GridCell } from "@/types/report"; // ─── 타입 ────────────────────────────────────────────────────────────────────── interface Props { component: ComponentConfig; section?: "style" | "data"; } // ─── 프리셋 ──────────────────────────────────────────────────────────────────── const TABLE_STYLE_PRESETS = { default: { headerBackgroundColor: "#f3f4f6", headerTextColor: "#111827", showBorder: true, rowHeight: 32, }, dark: { headerBackgroundColor: "#1e293b", headerTextColor: "#ffffff", showBorder: true, rowHeight: 32, }, blue: { headerBackgroundColor: "#1d4ed8", headerTextColor: "#ffffff", showBorder: true, rowHeight: 32, }, minimal: { headerBackgroundColor: "#f8fafc", headerTextColor: "#374151", showBorder: false, rowHeight: 28, }, } as const; // ─── StyleAccordion ──────────────────────────────────────────────────────────── function StyleAccordion({ label, isOpen, onToggle, children, }: { label: string; isOpen: boolean; onToggle: () => void; children: React.ReactNode; }) { return (
{isOpen && (
{children}
)}
); } // ─── ColorInput ──────────────────────────────────────────────────────────────── function ColorInput({ value, onChange }: { value: string; onChange: (v: string) => void }) { return (
onChange(e.target.value)} className="h-7 w-7 shrink-0 cursor-pointer rounded border-0 p-0" /> onChange(e.target.value)} className="h-7 border-0 bg-transparent px-1 font-mono text-xs shadow-none focus-visible:ring-0" />
); } // ─── 컴포넌트 ────────────────────────────────────────────────────────────────── export function TableProperties({ component, section }: Props) { const { updateComponent } = useReportDesigner(); const showStyle = !section || section === "style"; const showData = !section || section === "data"; const [openSections, setOpenSections] = useState>(new Set(["preset"])); const toggleSection = (id: string) => { setOpenSections((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }; const update = useCallback( (updates: Partial) => updateComponent(component.id, updates), [component.id, updateComponent], ); const applyPreset = useCallback( (key: keyof typeof TABLE_STYLE_PRESETS) => { const preset = TABLE_STYLE_PRESETS[key]; const updates: Partial = { ...preset }; if (component.gridMode && component.gridCells) { const headerRows = component.gridHeaderRows ?? 1; const headerCols = component.gridHeaderCols ?? 1; const newCells = component.gridCells.map((cell: GridCell) => { if (cell.merged) return cell; const isHeader = cell.row < headerRows || cell.col < headerCols; if (!isHeader) return cell; return { ...cell, backgroundColor: preset.headerBackgroundColor, textColor: preset.headerTextColor, }; }); updates.gridCells = newCells; } update(updates); }, [update, component.gridMode, component.gridCells, component.gridHeaderRows, component.gridHeaderCols], ); return ( <> {showData && } {showStyle && (
{/* 프리셋 */} toggleSection("preset")}>
{(["default", "dark", "blue", "minimal"] as const).map((key) => ( ))}
{/* 헤더 스타일 */} toggleSection("header")}>
update({ headerBackgroundColor: v })} />
update({ headerTextColor: v })} />
{/* 셀 스타일 */} toggleSection("cell")}>
update({ rowHeight: parseInt(e.target.value) || 32 })} className="h-9 text-xs" />
{/* 테두리 */} toggleSection("border")}>
update({ showBorder: checked })} />
)} ); }