ERP-node/frontend/lib/registry/components/split-panel-layout/SplitPanelLayoutConfigPanel...

458 lines
19 KiB
TypeScript
Raw Normal View History

2025-10-15 17:25:38 +09:00
"use client";
import React, { useState, useMemo, useEffect } 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 { Slider } from "@/components/ui/slider";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
import { Check, ChevronsUpDown, ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { SplitPanelLayoutConfig } from "./types";
import { TableInfo } from "@/types/screen";
interface SplitPanelLayoutConfigPanelProps {
config: SplitPanelLayoutConfig;
onChange: (config: SplitPanelLayoutConfig) => void;
tables?: TableInfo[]; // 전체 테이블 목록 (선택적)
screenTableName?: string; // 현재 화면의 테이블명 (좌측 패널에서 사용)
}
/**
* SplitPanelLayout
*/
export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelProps> = ({
config,
onChange,
tables = [], // 기본값 빈 배열
screenTableName, // 현재 화면의 테이블명
}) => {
const [rightTableOpen, setRightTableOpen] = useState(false);
const [leftColumnOpen, setLeftColumnOpen] = useState(false);
const [rightColumnOpen, setRightColumnOpen] = useState(false);
// screenTableName이 변경되면 leftPanel.tableName 자동 업데이트
useEffect(() => {
if (screenTableName) {
// 좌측 패널 테이블명 업데이트
if (config.leftPanel?.tableName !== screenTableName) {
updateLeftPanel({ tableName: screenTableName });
}
// 관계 타입이 detail이면 우측 패널도 동일한 테이블 사용
const relationshipType = config.rightPanel?.relation?.type || "detail";
if (relationshipType === "detail" && config.rightPanel?.tableName !== screenTableName) {
updateRightPanel({ tableName: screenTableName });
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [screenTableName]);
console.log("🔧 SplitPanelLayoutConfigPanel 렌더링");
console.log(" - config:", config);
console.log(" - tables:", tables);
console.log(" - tablesCount:", tables.length);
console.log(" - screenTableName:", screenTableName);
console.log(" - leftTable:", config.leftPanel?.tableName);
console.log(" - rightTable:", config.rightPanel?.tableName);
const updateConfig = (updates: Partial<SplitPanelLayoutConfig>) => {
const newConfig = { ...config, ...updates };
console.log("🔄 Config 업데이트:", newConfig);
onChange(newConfig);
};
const updateLeftPanel = (updates: Partial<SplitPanelLayoutConfig["leftPanel"]>) => {
const newConfig = {
...config,
leftPanel: { ...config.leftPanel, ...updates },
};
console.log("🔄 Left Panel 업데이트:", newConfig);
onChange(newConfig);
};
const updateRightPanel = (updates: Partial<SplitPanelLayoutConfig["rightPanel"]>) => {
const newConfig = {
...config,
rightPanel: { ...config.rightPanel, ...updates },
};
console.log("🔄 Right Panel 업데이트:", newConfig);
onChange(newConfig);
};
// 좌측 테이블은 현재 화면의 테이블 (screenTableName) 사용
const leftTableColumns = useMemo(() => {
const tableName = screenTableName || config.leftPanel?.tableName;
const table = tables.find((t) => t.tableName === tableName);
return table?.columns || [];
}, [tables, screenTableName, config.leftPanel?.tableName]);
// 우측 테이블의 컬럼 목록 가져오기
const rightTableColumns = useMemo(() => {
const table = tables.find((t) => t.tableName === config.rightPanel?.tableName);
return table?.columns || [];
}, [tables, config.rightPanel?.tableName]);
// 테이블 데이터 로딩 상태 확인
if (!tables || tables.length === 0) {
return (
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4">
<p className="text-sm text-yellow-800"> .</p>
<p className="mt-1 text-xs text-yellow-600">
.
</p>
</div>
);
}
// 관계 타입에 따라 우측 테이블을 자동으로 설정
const relationshipType = config.rightPanel?.relation?.type || "detail";
return (
<div className="space-y-6">
{/* 테이블 정보 표시 */}
<div className="rounded-lg bg-blue-50 p-3">
<p className="text-xs text-blue-600">📊 : {tables.length}</p>
</div>
{/* 관계 타입 선택 (최상단) */}
<div className="space-y-3 rounded-lg border-2 border-indigo-200 bg-indigo-50 p-4">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-indigo-600 text-white">
<span className="text-sm font-bold">1</span>
</div>
<h3 className="text-sm font-semibold text-indigo-900"> </h3>
</div>
<p className="text-xs text-indigo-700"> </p>
<Select
value={relationshipType}
onValueChange={(value: "join" | "detail" | "custom") => {
// 상세 모드로 변경 시 우측 테이블을 현재 화면 테이블로 설정
if (value === "detail" && screenTableName) {
updateRightPanel({
relation: { ...config.rightPanel?.relation, type: value },
tableName: screenTableName,
});
} else {
updateRightPanel({
relation: { ...config.rightPanel?.relation, type: value },
});
}
}}
>
<SelectTrigger className="bg-white">
<SelectValue placeholder="관계 타입 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="detail">
<div className="flex flex-col">
<span className="font-medium"> (DETAIL)</span>
<span className="text-xs text-gray-500"> ( )</span>
</div>
</SelectItem>
<SelectItem value="join">
<div className="flex flex-col">
<span className="font-medium"> (JOIN)</span>
<span className="text-xs text-gray-500"> ( )</span>
</div>
</SelectItem>
<SelectItem value="custom">
<div className="flex flex-col">
<span className="font-medium"> (CUSTOM)</span>
<span className="text-xs text-gray-500"> </span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
{/* 좌측 패널 설정 (마스터) */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-600 text-white">
<span className="text-sm font-bold">2</span>
</div>
<h3 className="text-sm font-semibold text-gray-900"> ()</h3>
</div>
<div className="space-y-2">
<Label> </Label>
<Input
value={config.leftPanel?.title || ""}
onChange={(e) => updateLeftPanel({ title: e.target.value })}
placeholder="좌측 패널 제목"
/>
</div>
<div className="space-y-2">
<Label> ( )</Label>
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3">
<p className="text-sm font-medium text-gray-900">{screenTableName || "테이블이 지정되지 않음"}</p>
<p className="mt-1 text-xs text-gray-500"> </p>
</div>
</div>
<div className="flex items-center justify-between">
<Label> </Label>
<Switch
checked={config.leftPanel?.showSearch ?? true}
onCheckedChange={(checked) => updateLeftPanel({ showSearch: checked })}
/>
</div>
<div className="flex items-center justify-between">
<Label> </Label>
<Switch
checked={config.leftPanel?.showAdd ?? false}
onCheckedChange={(checked) => updateLeftPanel({ showAdd: checked })}
/>
</div>
</div>
{/* 우측 패널 설정 */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-600 text-white">
<span className="text-sm font-bold">3</span>
</div>
<h3 className="text-sm font-semibold text-gray-900">
({relationshipType === "detail" ? "상세" : relationshipType === "join" ? "조인" : "커스텀"})
</h3>
</div>
<div className="space-y-2">
<Label> </Label>
<Input
value={config.rightPanel?.title || ""}
onChange={(e) => updateRightPanel({ title: e.target.value })}
placeholder="우측 패널 제목"
/>
</div>
{/* 관계 타입에 따라 테이블 선택 UI 변경 */}
{relationshipType === "detail" ? (
// 상세 모드: 좌측과 동일한 테이블 (비활성화)
<div className="space-y-2">
<Label> ( )</Label>
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3">
<p className="text-sm font-medium text-gray-900">{screenTableName || "테이블이 지정되지 않음"}</p>
<p className="mt-1 text-xs text-gray-500"> </p>
</div>
</div>
) : (
// 조인/커스텀 모드: 전체 테이블에서 선택 가능
<div className="space-y-2">
<Label> ( )</Label>
<Popover open={rightTableOpen} onOpenChange={setRightTableOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={rightTableOpen}
className="w-full justify-between"
>
{config.rightPanel?.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.tableName}
onSelect={(value) => {
updateRightPanel({ tableName: value });
setRightTableOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.rightPanel?.tableName === table.tableName ? "opacity-100" : "opacity-0",
)}
/>
{table.tableName}
<span className="ml-2 text-xs text-gray-500">({table.tableLabel || ""})</span>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
)}
{/* 컬럼 매핑 - 조인/커스텀 모드에서만 표시 */}
{relationshipType !== "detail" && (
<div className="space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
<Label className="text-sm font-semibold"> ( )</Label>
<p className="text-xs text-gray-600"> </p>
<div className="space-y-2">
<Label className="text-xs"> </Label>
<Popover open={leftColumnOpen} onOpenChange={setLeftColumnOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={leftColumnOpen}
className="w-full justify-between"
disabled={!config.leftPanel?.tableName}
>
{config.rightPanel?.relation?.leftColumn || "좌측 컬럼 선택"}
<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">
{leftTableColumns.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
updateRightPanel({
relation: { ...config.rightPanel?.relation, leftColumn: value },
});
setLeftColumnOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.rightPanel?.relation?.leftColumn === column.columnName
? "opacity-100"
: "opacity-0",
)}
/>
{column.columnName}
<span className="ml-2 text-xs text-gray-500">({column.columnLabel || ""})</span>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex items-center justify-center">
<ArrowRight className="h-4 w-4 text-gray-400" />
</div>
<div className="space-y-2">
<Label className="text-xs"> ()</Label>
<Popover open={rightColumnOpen} onOpenChange={setRightColumnOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={rightColumnOpen}
className="w-full justify-between"
disabled={!config.rightPanel?.tableName}
>
{config.rightPanel?.relation?.foreignKey || "우측 컬럼 선택"}
<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">
{rightTableColumns.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
updateRightPanel({
relation: { ...config.rightPanel?.relation, foreignKey: value },
});
setRightColumnOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.rightPanel?.relation?.foreignKey === column.columnName
? "opacity-100"
: "opacity-0",
)}
/>
{column.columnName}
<span className="ml-2 text-xs text-gray-500">({column.columnLabel || ""})</span>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
</div>
)}
<div className="flex items-center justify-between">
<Label> </Label>
<Switch
checked={config.rightPanel?.showSearch ?? true}
onCheckedChange={(checked) => updateRightPanel({ showSearch: checked })}
/>
</div>
<div className="flex items-center justify-between">
<Label> </Label>
<Switch
checked={config.rightPanel?.showAdd ?? false}
onCheckedChange={(checked) => updateRightPanel({ showAdd: checked })}
/>
</div>
</div>
{/* 레이아웃 설정 */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-purple-600 text-white">
<span className="text-sm font-bold">4</span>
</div>
<h3 className="text-sm font-semibold text-gray-900"> </h3>
</div>
<div className="space-y-2">
<Label> : {config.splitRatio || 30}%</Label>
<Slider
value={[config.splitRatio || 30]}
onValueChange={(value) => updateConfig({ splitRatio: value[0] })}
min={20}
max={80}
step={5}
/>
</div>
<div className="flex items-center justify-between">
<Label> </Label>
<Switch
checked={config.resizable ?? true}
onCheckedChange={(checked) => updateConfig({ resizable: checked })}
/>
</div>
<div className="flex items-center justify-between">
<Label> </Label>
<Switch
checked={config.autoLoad ?? true}
onCheckedChange={(checked) => updateConfig({ autoLoad: checked })}
/>
</div>
</div>
</div>
);
};