계층 구조 트리 뷰

This commit is contained in:
dohyeons
2025-11-07 15:21:44 +09:00
parent efaa267d78
commit 672aba8404
3 changed files with 587 additions and 56 deletions

View File

@@ -90,6 +90,29 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config.leftPanel?.tableName, screenTableName, loadedTableColumns, config.leftPanel?.showAdd]);
// 좌측 패널 하위 항목 추가 모달 PK 자동 추가
useEffect(() => {
const leftTableName = config.leftPanel?.tableName || screenTableName;
if (leftTableName && loadedTableColumns[leftTableName] && config.leftPanel?.showItemAddButton) {
const currentAddModalColumns = config.leftPanel?.itemAddConfig?.addModalColumns || [];
const updatedColumns = ensurePrimaryKeysInAddModal(leftTableName, currentAddModalColumns);
// PK가 추가되었으면 업데이트
if (updatedColumns.length !== currentAddModalColumns.length) {
console.log(`🔄 좌측 패널 하위 항목 추가: PK 컬럼 자동 추가 (${leftTableName})`);
updateLeftPanel({
itemAddConfig: {
...config.leftPanel?.itemAddConfig,
addModalColumns: updatedColumns,
parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "",
sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "",
}
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config.leftPanel?.tableName, screenTableName, loadedTableColumns, config.leftPanel?.showItemAddButton]);
// 우측 패널 테이블 컬럼 로드 완료 시 PK 자동 추가
useEffect(() => {
const rightTableName = config.rightPanel?.tableName;
@@ -340,6 +363,307 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
/>
</div>
<div className="flex items-center justify-between">
<Label> + </Label>
<Switch
checked={config.leftPanel?.showItemAddButton ?? false}
onCheckedChange={(checked) => updateLeftPanel({ showItemAddButton: checked })}
/>
</div>
{/* 항목별 + 버튼 설정 (하위 항목 추가) */}
{config.leftPanel?.showItemAddButton && (
<div className="space-y-3 rounded-lg border border-blue-200 bg-blue-50 p-3">
<Label className="text-sm font-semibold"> </Label>
<p className="text-xs text-gray-600">
+ (: 부서 )
</p>
{/* 현재 항목의 값을 가져올 컬럼 (sourceColumn) */}
<div>
<Label className="text-xs"> ID </Label>
<p className="mb-2 text-[10px] text-gray-500">
(: dept_code)
</p>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="h-8 w-full justify-between text-xs"
>
{config.leftPanel?.itemAddConfig?.sourceColumn || "컬럼 선택"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="컬럼 검색..." className="text-xs" />
<CommandEmpty className="text-xs"> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{leftTableColumns
.filter((column) => !['company_code', 'company_name'].includes(column.columnName))
.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
updateLeftPanel({
itemAddConfig: {
...config.leftPanel?.itemAddConfig,
sourceColumn: value,
parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "",
}
});
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
config.leftPanel?.itemAddConfig?.sourceColumn === column.columnName ? "opacity-100" : "opacity-0"
)}
/>
{column.columnLabel || column.columnName}
<span className="ml-2 text-[10px] text-gray-500">
({column.columnName})
</span>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
{/* 상위 항목 ID를 저장할 컬럼 (parentColumn) */}
<div>
<Label className="text-xs"> </Label>
<p className="mb-2 text-[10px] text-gray-500">
ID를 (: parent_dept_code)
</p>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="h-8 w-full justify-between text-xs"
>
{config.leftPanel?.itemAddConfig?.parentColumn || "컬럼 선택"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="컬럼 검색..." className="text-xs" />
<CommandEmpty className="text-xs"> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{leftTableColumns
.filter((column) => !['company_code', 'company_name'].includes(column.columnName))
.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
updateLeftPanel({
itemAddConfig: {
...config.leftPanel?.itemAddConfig,
parentColumn: value,
sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "",
}
});
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
config.leftPanel?.itemAddConfig?.parentColumn === column.columnName ? "opacity-100" : "opacity-0"
)}
/>
{column.columnLabel || column.columnName}
<span className="ml-2 text-[10px] text-gray-500">
({column.columnName})
</span>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
{/* 하위 항목 추가 모달 컬럼 설정 */}
<div className="space-y-2 rounded border border-blue-300 bg-white p-2">
<div className="flex items-center justify-between">
<Label className="text-xs font-semibold"> </Label>
<Button
size="sm"
variant="outline"
onClick={() => {
const currentColumns = config.leftPanel?.itemAddConfig?.addModalColumns || [];
const newColumns = [
...currentColumns,
{ name: "", label: "", required: false },
];
updateLeftPanel({
itemAddConfig: {
...config.leftPanel?.itemAddConfig,
addModalColumns: newColumns,
parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "",
sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "",
}
});
}}
className="h-6 text-[10px]"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
<p className="text-[10px] text-gray-600">
</p>
<div className="space-y-2">
{(config.leftPanel?.itemAddConfig?.addModalColumns || []).length === 0 ? (
<div className="rounded-md border border-dashed border-gray-300 bg-gray-50 p-2 text-center">
<p className="text-[10px] text-gray-500"> </p>
</div>
) : (
(config.leftPanel?.itemAddConfig?.addModalColumns || []).map((col, index) => {
const column = leftTableColumns.find(c => c.columnName === col.name);
const isPK = column?.isPrimaryKey || false;
return (
<div
key={index}
className={cn(
"flex items-center gap-2 rounded-md border p-2",
isPK ? "border-yellow-300 bg-yellow-50" : "bg-white"
)}
>
{isPK && (
<span className="rounded bg-yellow-200 px-1.5 py-0.5 text-[10px] font-semibold text-yellow-700">
PK
</span>
)}
<div className="flex-1">
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
disabled={isPK}
className="h-7 w-full justify-between text-[10px]"
>
{col.name || "컬럼 선택"}
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput placeholder="컬럼 검색..." className="text-xs" />
<CommandEmpty className="text-xs"> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{leftTableColumns
.filter((column) => !['company_code', 'company_name'].includes(column.columnName))
.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
const newColumns = [...(config.leftPanel?.itemAddConfig?.addModalColumns || [])];
newColumns[index] = {
...newColumns[index],
name: value,
label: column.columnLabel || value,
};
updateLeftPanel({
itemAddConfig: {
...config.leftPanel?.itemAddConfig,
addModalColumns: newColumns,
parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "",
sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "",
}
});
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
col.name === column.columnName ? "opacity-100" : "opacity-0"
)}
/>
{column.columnLabel || column.columnName}
<span className="ml-2 text-[10px] text-gray-500">
({column.columnName})
</span>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex items-center gap-1">
<label className="flex cursor-pointer items-center gap-1 text-[10px] text-gray-600">
<input
type="checkbox"
checked={col.required ?? false}
disabled={isPK}
onChange={(e) => {
const newColumns = [...(config.leftPanel?.itemAddConfig?.addModalColumns || [])];
newColumns[index] = {
...newColumns[index],
required: e.target.checked,
};
updateLeftPanel({
itemAddConfig: {
...config.leftPanel?.itemAddConfig,
addModalColumns: newColumns,
parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "",
sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "",
}
});
}}
className="h-3 w-3"
/>
</label>
</div>
<Button
size="sm"
variant="ghost"
disabled={isPK}
onClick={() => {
const newColumns = (config.leftPanel?.itemAddConfig?.addModalColumns || []).filter(
(_, i) => i !== index
);
updateLeftPanel({
itemAddConfig: {
...config.leftPanel?.itemAddConfig,
addModalColumns: newColumns,
parentColumn: config.leftPanel?.itemAddConfig?.parentColumn || "",
sourceColumn: config.leftPanel?.itemAddConfig?.sourceColumn || "",
}
});
}}
className="h-7 w-7 p-0"
title={isPK ? "PK 컬럼은 삭제할 수 없습니다" : ""}
>
<X className="h-3 w-3" />
</Button>
</div>
);
})
)}
</div>
</div>
</div>
)}
{/* 좌측 패널 추가 모달 컬럼 설정 */}
{config.leftPanel?.showAdd && (
<div className="space-y-3 rounded-lg border border-purple-200 bg-purple-50 p-3">