ERP-node/frontend/lib/registry/components/related-data-buttons/RelatedDataButtonsConfigPan...

559 lines
22 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState, useEffect, useCallback, useMemo } from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { tableManagementApi, getTableColumns } from "@/lib/api/tableManagement";
import type { RelatedDataButtonsConfig } from "./types";
interface TableInfo {
tableName: string;
displayName?: string;
}
interface ColumnInfo {
columnName: string;
columnLabel?: string;
}
interface RelatedDataButtonsConfigPanelProps {
config: RelatedDataButtonsConfig;
onChange: (config: RelatedDataButtonsConfig) => void;
tables?: TableInfo[];
}
export const RelatedDataButtonsConfigPanel: React.FC<RelatedDataButtonsConfigPanelProps> = ({
config,
onChange,
tables: propTables = [],
}) => {
const [allTables, setAllTables] = useState<TableInfo[]>([]);
const [sourceTableColumns, setSourceTableColumns] = useState<ColumnInfo[]>([]);
const [buttonTableColumns, setButtonTableColumns] = useState<ColumnInfo[]>([]);
// Popover 상태
const [sourceTableOpen, setSourceTableOpen] = useState(false);
const [buttonTableOpen, setButtonTableOpen] = useState(false);
// 전체 테이블 로드
useEffect(() => {
const loadTables = async () => {
try {
const response = await tableManagementApi.getTableList();
if (response.success && response.data) {
setAllTables(response.data.map((t: any) => ({
tableName: t.tableName || t.table_name,
displayName: t.tableLabel || t.table_label || t.displayName,
})));
}
} catch (error) {
console.error("테이블 목록 로드 실패:", error);
}
};
loadTables();
}, []);
// 소스 테이블 컬럼 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.sourceMapping?.sourceTable) {
setSourceTableColumns([]);
return;
}
try {
const response = await getTableColumns(config.sourceMapping.sourceTable);
if (response.success && response.data?.columns) {
setSourceTableColumns(response.data.columns.map((c: any) => ({
columnName: c.columnName || c.column_name,
columnLabel: c.columnLabel || c.column_label || c.displayName,
})));
}
} catch (error) {
console.error("소스 테이블 컬럼 로드 실패:", error);
}
};
loadColumns();
}, [config.sourceMapping?.sourceTable]);
// 버튼 테이블 컬럼 로드
useEffect(() => {
const loadColumns = async () => {
if (!config.buttonDataSource?.tableName) {
setButtonTableColumns([]);
return;
}
try {
const response = await getTableColumns(config.buttonDataSource.tableName);
if (response.success && response.data?.columns) {
setButtonTableColumns(response.data.columns.map((c: any) => ({
columnName: c.columnName || c.column_name,
columnLabel: c.columnLabel || c.column_label || c.displayName,
})));
}
} catch (error) {
console.error("버튼 테이블 컬럼 로드 실패:", error);
}
};
loadColumns();
}, [config.buttonDataSource?.tableName]);
// 설정 업데이트 헬퍼
const updateConfig = useCallback((updates: Partial<RelatedDataButtonsConfig>) => {
onChange({ ...config, ...updates });
}, [config, onChange]);
const updateSourceMapping = useCallback((updates: Partial<RelatedDataButtonsConfig["sourceMapping"]>) => {
onChange({
...config,
sourceMapping: { ...config.sourceMapping, ...updates },
});
}, [config, onChange]);
const updateHeaderDisplay = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["headerDisplay"]>>) => {
onChange({
...config,
headerDisplay: { ...config.headerDisplay, ...updates } as any,
});
}, [config, onChange]);
const updateButtonDataSource = useCallback((updates: Partial<RelatedDataButtonsConfig["buttonDataSource"]>) => {
onChange({
...config,
buttonDataSource: { ...config.buttonDataSource, ...updates },
});
}, [config, onChange]);
const updateButtonStyle = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["buttonStyle"]>>) => {
onChange({
...config,
buttonStyle: { ...config.buttonStyle, ...updates },
});
}, [config, onChange]);
const updateAddButton = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["addButton"]>>) => {
onChange({
...config,
addButton: { ...config.addButton, ...updates },
});
}, [config, onChange]);
const updateEvents = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["events"]>>) => {
onChange({
...config,
events: { ...config.events, ...updates },
});
}, [config, onChange]);
const tables = allTables.length > 0 ? allTables : propTables;
return (
<div className="space-y-6">
{/* 소스 매핑 (좌측 패널 연결) */}
<div className="space-y-3">
<Label className="text-sm font-semibold"> ( )</Label>
<div className="space-y-2">
<Label className="text-xs"></Label>
<Popover open={sourceTableOpen} onOpenChange={setSourceTableOpen}>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" className="w-full justify-between">
{config.sourceMapping?.sourceTable || "테이블 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="테이블 검색..." />
<CommandEmpty> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{tables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
onSelect={() => {
updateSourceMapping({ sourceTable: table.tableName });
setSourceTableOpen(false);
}}
>
<Check className={cn("mr-2 h-4 w-4", config.sourceMapping?.sourceTable === table.tableName ? "opacity-100" : "opacity-0")} />
{table.displayName || table.tableName}
{table.displayName && <span className="ml-2 text-xs text-gray-500">({table.tableName})</span>}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<Label className="text-xs"> ( )</Label>
<Select
value={config.sourceMapping?.sourceColumn || ""}
onValueChange={(value) => updateSourceMapping({ sourceColumn: value })}
>
<SelectTrigger>
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{sourceTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* 헤더 표시 설정 */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm font-semibold"> </Label>
<Switch
checked={config.headerDisplay?.show !== false}
onCheckedChange={(checked) => updateHeaderDisplay({ show: checked })}
/>
</div>
{config.headerDisplay?.show !== false && (
<div className="space-y-2 pl-2 border-l-2 border-gray-200">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.headerDisplay?.titleColumn || ""}
onValueChange={(value) => updateHeaderDisplay({ titleColumn: value })}
>
<SelectTrigger>
<SelectValue placeholder="제목 컬럼 선택" />
</SelectTrigger>
<SelectContent>
{sourceTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs"> ()</Label>
<Select
value={config.headerDisplay?.subtitleColumn || "__none__"}
onValueChange={(value) => updateHeaderDisplay({ subtitleColumn: value === "__none__" ? "" : value })}
>
<SelectTrigger>
<SelectValue placeholder="부제목 컬럼 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__none__"></SelectItem>
{sourceTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
)}
</div>
{/* 버튼 데이터 소스 */}
<div className="space-y-3">
<Label className="text-sm font-semibold"> </Label>
<div className="space-y-2">
<Label className="text-xs"></Label>
<Popover open={buttonTableOpen} onOpenChange={setButtonTableOpen}>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" className="w-full justify-between">
{config.buttonDataSource?.tableName || "테이블 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="테이블 검색..." />
<CommandEmpty> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{tables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
onSelect={() => {
updateButtonDataSource({ tableName: table.tableName });
setButtonTableOpen(false);
}}
>
<Check className={cn("mr-2 h-4 w-4", config.buttonDataSource?.tableName === table.tableName ? "opacity-100" : "opacity-0")} />
{table.displayName || table.tableName}
{table.displayName && <span className="ml-2 text-xs text-gray-500">({table.tableName})</span>}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.buttonDataSource?.filterColumn || ""}
onValueChange={(value) => updateButtonDataSource({ filterColumn: value })}
>
<SelectTrigger>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{buttonTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.buttonDataSource?.displayColumn || ""}
onValueChange={(value) => updateButtonDataSource({ displayColumn: value })}
>
<SelectTrigger>
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{buttonTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
{/* 버튼 스타일 */}
<div className="space-y-3">
<Label className="text-sm font-semibold"> </Label>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.buttonStyle?.variant || "outline"}
onValueChange={(value: any) => updateButtonStyle({ variant: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="default">Default</SelectItem>
<SelectItem value="outline">Outline</SelectItem>
<SelectItem value="secondary">Secondary</SelectItem>
<SelectItem value="ghost">Ghost</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.buttonStyle?.activeVariant || "default"}
onValueChange={(value: any) => updateButtonStyle({ activeVariant: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="default">Default</SelectItem>
<SelectItem value="outline">Outline</SelectItem>
<SelectItem value="secondary">Secondary</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* 기본 표시 설정 */}
<div className="space-y-2">
<Label className="text-xs"> </Label>
<Select
value={config.buttonStyle?.defaultIndicator?.column || "__none__"}
onValueChange={(value) => updateButtonStyle({
defaultIndicator: {
...config.buttonStyle?.defaultIndicator,
column: value === "__none__" ? "" : value,
showStar: config.buttonStyle?.defaultIndicator?.showStar ?? true,
},
})}
>
<SelectTrigger>
<SelectValue placeholder="없음" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__none__"></SelectItem>
{buttonTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{config.buttonStyle?.defaultIndicator?.column && (
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Switch
checked={config.buttonStyle?.defaultIndicator?.showStar ?? true}
onCheckedChange={(checked) => updateButtonStyle({
defaultIndicator: {
...config.buttonStyle?.defaultIndicator,
column: config.buttonStyle?.defaultIndicator?.column || "",
showStar: checked,
},
})}
/>
<Label className="text-xs"> </Label>
</div>
</div>
)}
</div>
{/* 이벤트 설정 (하위 테이블 연동) */}
<div className="space-y-3">
<Label className="text-sm font-semibold"> </Label>
<div className="space-y-2">
<Label className="text-xs"> </Label>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" className="w-full justify-between">
{config.events?.targetTable || "테이블 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="테이블 검색..." />
<CommandEmpty> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{tables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
onSelect={() => {
updateEvents({ targetTable: table.tableName });
}}
>
<Check className={cn("mr-2 h-4 w-4", config.events?.targetTable === table.tableName ? "opacity-100" : "opacity-0")} />
{table.displayName || table.tableName}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<Label className="text-xs"> ( )</Label>
<Input
value={config.events?.targetFilterColumn || ""}
onChange={(e) => updateEvents({ targetFilterColumn: e.target.value })}
placeholder="예: routing_version_id"
/>
</div>
</div>
{/* 추가 버튼 설정 */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm font-semibold"> </Label>
<Switch
checked={config.addButton?.show ?? false}
onCheckedChange={(checked) => updateAddButton({ show: checked })}
/>
</div>
{config.addButton?.show && (
<div className="space-y-2 pl-2 border-l-2 border-gray-200">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={config.addButton?.label || ""}
onChange={(e) => updateAddButton({ label: e.target.value })}
placeholder="+ 버전 추가"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"></Label>
<Select
value={config.addButton?.position || "header"}
onValueChange={(value: any) => updateAddButton({ position: value })}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="header"> </SelectItem>
<SelectItem value="inline"> </SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs"> ID</Label>
<Input
type="number"
value={config.addButton?.modalScreenId || ""}
onChange={(e) => updateAddButton({ modalScreenId: parseInt(e.target.value) || undefined })}
placeholder="화면 ID"
/>
</div>
</div>
)}
</div>
{/* 기타 설정 */}
<div className="space-y-3">
<Label className="text-sm font-semibold"> </Label>
<div className="flex items-center gap-2">
<Switch
checked={config.autoSelectFirst ?? true}
onCheckedChange={(checked) => updateConfig({ autoSelectFirst: checked })}
/>
<Label className="text-xs"> </Label>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={config.emptyMessage || ""}
onChange={(e) => updateConfig({ emptyMessage: e.target.value })}
placeholder="데이터가 없습니다"
/>
</div>
</div>
</div>
);
};
export default RelatedDataButtonsConfigPanel;