ERP-node/frontend/components/v2/config-panels/V2ListConfigPanel.tsx

334 lines
12 KiB
TypeScript

"use client";
/**
* V2List 설정 패널
* 토스식 단계별 UX: 테이블 정보 표시 -> 기본 옵션(Switch) -> 상세 설정(Collapsible)
* 컬럼/필터 등 복잡한 설정은 TableListConfigPanel에 위임하여 기능 누락 방지
*/
import React, { useState, useMemo } from "react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Table2, Settings, ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { TableListConfigPanel } from "@/lib/registry/components/table-list/TableListConfigPanel";
import { TableListConfig } from "@/lib/registry/components/table-list/types";
interface V2ListConfigPanelProps {
config: Record<string, any>;
onChange: (config: Record<string, any>) => void;
currentTableName?: string;
}
export const V2ListConfigPanel: React.FC<V2ListConfigPanelProps> = ({
config,
onChange,
currentTableName,
}) => {
const [detailOpen, setDetailOpen] = useState(false);
const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value });
};
const tableName = config.tableName || config.dataSource?.table || currentTableName || "";
const columnCount = (config.columns || []).length;
// ─── V2List config → TableListConfig 변환 (기존 로직 100% 유지) ───
const tableListConfig: TableListConfig = useMemo(() => {
const columns = (config.columns || []).map((col: any, index: number) => ({
columnName: col.key || col.columnName || col.field || "",
displayName: col.title || col.header || col.displayName || col.key || col.columnName || col.field || "",
width: col.width ? parseInt(col.width, 10) : undefined,
visible: col.visible !== false,
sortable: col.sortable !== false,
searchable: col.searchable !== false,
align: col.align || "left",
order: index,
isEntityJoin: col.isJoinColumn || col.isEntityJoin || false,
thousandSeparator: col.thousandSeparator,
editable: col.editable,
entityDisplayConfig: col.entityDisplayConfig,
}));
return {
selectedTable: config.tableName || config.dataSource?.table || currentTableName,
tableName: config.tableName || config.dataSource?.table || currentTableName,
columns,
useCustomTable: config.useCustomTable,
customTableName: config.customTableName,
isReadOnly: config.isReadOnly !== false,
displayMode: "table",
showHeader: true,
showFooter: false,
pagination: config.pagination !== false ? {
enabled: true,
pageSize: config.pageSize || 10,
showSizeSelector: true,
showPageInfo: true,
pageSizeOptions: [5, 10, 20, 50, 100],
} : {
enabled: false,
pageSize: 10,
showSizeSelector: false,
showPageInfo: false,
pageSizeOptions: [10],
},
filter: config.filter || { enabled: false, filters: [] },
dataFilter: config.dataFilter,
actions: config.actions || {
showActions: false,
actions: [],
bulkActions: false,
bulkActionList: [],
},
tableStyle: config.tableStyle || {
theme: "default",
headerStyle: "default",
rowHeight: "normal",
alternateRows: false,
hoverEffect: true,
borderStyle: "light",
},
checkbox: {
enabled: true,
multiple: true,
position: "left",
selectAll: true,
},
height: "auto",
autoWidth: true,
stickyHeader: true,
autoLoad: true,
horizontalScroll: {
enabled: true,
minColumnWidth: 100,
maxColumnWidth: 300,
},
toolbar: config.toolbar,
linkedFilters: config.linkedFilters,
excludeFilter: config.excludeFilter,
defaultSort: config.defaultSort,
};
}, [config, currentTableName]);
// ─── TableListConfig 변경 → V2List config 변환 (기존 로직 100% 유지) ───
const handleConfigChange = (partialConfig: Partial<TableListConfig>) => {
const newConfig: Record<string, any> = { ...config };
if (partialConfig.selectedTable !== undefined) {
newConfig.tableName = partialConfig.selectedTable;
if (!newConfig.dataSource) newConfig.dataSource = {};
newConfig.dataSource.table = partialConfig.selectedTable;
}
if (partialConfig.tableName !== undefined) {
newConfig.tableName = partialConfig.tableName;
if (!newConfig.dataSource) newConfig.dataSource = {};
newConfig.dataSource.table = partialConfig.tableName;
}
if (partialConfig.useCustomTable !== undefined) {
newConfig.useCustomTable = partialConfig.useCustomTable;
}
if (partialConfig.customTableName !== undefined) {
newConfig.customTableName = partialConfig.customTableName;
}
if (partialConfig.isReadOnly !== undefined) {
newConfig.isReadOnly = partialConfig.isReadOnly;
}
if (partialConfig.columns !== undefined) {
newConfig.columns = partialConfig.columns.map((col: any) => ({
key: col.columnName,
field: col.columnName,
title: col.displayName,
header: col.displayName,
width: col.width ? String(col.width) : undefined,
visible: col.visible,
sortable: col.sortable,
searchable: col.searchable,
align: col.align,
isJoinColumn: col.isEntityJoin,
isEntityJoin: col.isEntityJoin,
thousandSeparator: col.thousandSeparator,
editable: col.editable,
entityDisplayConfig: col.entityDisplayConfig,
}));
}
if (partialConfig.pagination !== undefined) {
newConfig.pagination = partialConfig.pagination?.enabled;
newConfig.pageSize = partialConfig.pagination?.pageSize || 10;
}
if (partialConfig.filter !== undefined) {
newConfig.filter = partialConfig.filter;
}
if (partialConfig.dataFilter !== undefined) {
newConfig.dataFilter = partialConfig.dataFilter;
}
if (partialConfig.actions !== undefined) {
newConfig.actions = partialConfig.actions;
}
if (partialConfig.tableStyle !== undefined) {
newConfig.tableStyle = partialConfig.tableStyle;
}
if (partialConfig.toolbar !== undefined) {
newConfig.toolbar = partialConfig.toolbar;
}
if (partialConfig.linkedFilters !== undefined) {
newConfig.linkedFilters = partialConfig.linkedFilters;
}
if (partialConfig.excludeFilter !== undefined) {
newConfig.excludeFilter = partialConfig.excludeFilter;
}
if (partialConfig.defaultSort !== undefined) {
newConfig.defaultSort = partialConfig.defaultSort;
}
onChange(newConfig);
};
return (
<div className="space-y-4">
{/* ─── 1단계: 테이블 정보 ─── */}
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<Table2 className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> </span>
</div>
{tableName ? (
<div className="rounded-md border bg-background p-3">
<p className="text-xs text-muted-foreground"> </p>
<p className="mt-0.5 text-sm font-medium">{tableName}</p>
{columnCount > 0 && (
<p className="mt-1 text-[11px] text-muted-foreground">
{columnCount}
</p>
)}
</div>
) : (
<div className="rounded-md border-2 border-dashed p-4 text-center">
<Table2 className="mx-auto mb-2 h-8 w-8 opacity-30 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
</p>
<p className="mt-1 text-xs text-muted-foreground">
</p>
</div>
)}
</div>
{/* ─── 2단계: 기본 옵션 (Switch + 설명) ─── */}
<div className="space-y-2">
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.isReadOnly !== false}
onCheckedChange={(checked) => updateConfig("isReadOnly", checked)}
/>
</div>
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"></p>
<p className="text-[11px] text-muted-foreground">
</p>
</div>
<Switch
checked={config.pagination !== false}
onCheckedChange={(checked) => {
updateConfig("pagination", checked);
}}
/>
</div>
{config.pagination !== false && (
<div className="ml-4 border-l-2 border-primary/20 pl-3">
<div className="flex items-center justify-between py-1">
<span className="text-xs text-muted-foreground"> </span>
<Select
value={String(config.pageSize || 10)}
onValueChange={(v) => updateConfig("pageSize", Number(v))}
>
<SelectTrigger className="h-8 w-[180px] text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="5">5</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
</div>
{/* ─── 3단계: 상세 설정 (컬럼, 필터, 테이블 선택 등) ─── */}
<Collapsible open={detailOpen} onOpenChange={setDetailOpen}>
<CollapsibleTrigger asChild>
<button
type="button"
className="flex w-full items-center justify-between rounded-lg border bg-muted/30 px-4 py-2.5 text-left transition-colors hover:bg-muted/50"
>
<div className="flex items-center gap-2">
<Settings className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium"> </span>
</div>
<ChevronDown
className={cn(
"h-4 w-4 text-muted-foreground transition-transform duration-200",
detailOpen && "rotate-180",
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="rounded-b-lg border border-t-0 p-2">
<p className="text-xs text-muted-foreground px-2 pb-2">
, ,
</p>
<TableListConfigPanel
config={tableListConfig}
onChange={handleConfigChange}
screenTableName={currentTableName}
/>
</div>
</CollapsibleContent>
</Collapsible>
</div>
);
};
V2ListConfigPanel.displayName = "V2ListConfigPanel";
export default V2ListConfigPanel;