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

458 lines
19 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
};