Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management

This commit is contained in:
kjs
2026-01-09 15:46:24 +09:00
8 changed files with 1774 additions and 43 deletions

View File

@@ -11,10 +11,11 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Button } from "@/components/ui/button";
// Accordion 제거 - 단순 섹션으로 변경
import { Check, ChevronsUpDown, ArrowRight, Plus, X, ArrowUp, ArrowDown } from "lucide-react";
import { Check, ChevronsUpDown, ArrowRight, Plus, X, ArrowUp, ArrowDown, Trash2, GripVertical } from "lucide-react";
import { cn } from "@/lib/utils";
import { SplitPanelLayoutConfig } from "./types";
import { SplitPanelLayoutConfig, AdditionalTabConfig } from "./types";
import { TableInfo, ColumnInfo } from "@/types/screen";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { tableTypeApi } from "@/lib/api/screen";
import { DataFilterConfigPanel } from "@/components/screen/config-panels/DataFilterConfigPanel";
@@ -189,6 +190,848 @@ const ScreenSelector: React.FC<{
);
};
/**
* 추가 탭 설정 패널 (우측 패널과 동일한 구조)
*/
interface AdditionalTabConfigPanelProps {
tab: AdditionalTabConfig;
tabIndex: number;
config: SplitPanelLayoutConfig;
updateRightPanel: (updates: Partial<SplitPanelLayoutConfig["rightPanel"]>) => void;
availableRightTables: TableInfo[];
leftTableColumns: ColumnInfo[];
menuObjid?: number;
// 공유 컬럼 로드 상태
loadedTableColumns: Record<string, ColumnInfo[]>;
loadTableColumns: (tableName: string) => Promise<void>;
loadingColumns: Record<string, boolean>;
}
const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
tab,
tabIndex,
config,
updateRightPanel,
availableRightTables,
leftTableColumns,
menuObjid,
loadedTableColumns,
loadTableColumns,
loadingColumns,
}) => {
// 탭 테이블 변경 시 컬럼 로드
useEffect(() => {
if (tab.tableName && !loadedTableColumns[tab.tableName] && !loadingColumns[tab.tableName]) {
loadTableColumns(tab.tableName);
}
}, [tab.tableName, loadedTableColumns, loadingColumns, loadTableColumns]);
// 현재 탭의 컬럼 목록
const tabColumns = useMemo(() => {
return tab.tableName ? loadedTableColumns[tab.tableName] || [] : [];
}, [tab.tableName, loadedTableColumns]);
// 로딩 상태
const loadingTabColumns = tab.tableName ? loadingColumns[tab.tableName] || false : false;
// 탭 업데이트 헬퍼
const updateTab = (updates: Partial<AdditionalTabConfig>) => {
const newTabs = [...(config.rightPanel?.additionalTabs || [])];
newTabs[tabIndex] = { ...tab, ...updates };
updateRightPanel({ additionalTabs: newTabs });
};
return (
<AccordionItem
key={tab.tabId}
value={tab.tabId}
className="rounded-lg border bg-gray-50"
>
<AccordionTrigger className="px-3 py-2 hover:no-underline">
<div className="flex flex-1 items-center gap-2">
<GripVertical className="h-4 w-4 text-gray-400" />
<span className="text-sm font-medium">
{tab.label || `${tabIndex + 1}`}
</span>
{tab.tableName && (
<span className="text-xs text-gray-500">({tab.tableName})</span>
)}
</div>
</AccordionTrigger>
<AccordionContent className="px-3 pb-3">
<div className="space-y-4">
{/* ===== 1. 기본 정보 ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<Label className="text-xs font-semibold text-blue-600"> </Label>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={tab.label}
onChange={(e) => updateTab({ label: e.target.value })}
placeholder="탭 이름"
className="h-8 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={tab.title}
onChange={(e) => updateTab({ title: e.target.value })}
placeholder="패널 제목"
className="h-8 text-xs"
/>
</div>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
type="number"
value={tab.panelHeaderHeight ?? 48}
onChange={(e) => updateTab({ panelHeaderHeight: parseInt(e.target.value) || 48 })}
placeholder="48"
className="h-8 w-24 text-xs"
/>
</div>
</div>
{/* ===== 2. 테이블 선택 ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<Label className="text-xs font-semibold text-blue-600"> </Label>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="h-8 w-full justify-between text-xs"
>
{tab.tableName || "테이블을 선택하세요"}
<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> .</CommandEmpty>
<CommandGroup className="max-h-[200px] overflow-auto">
{availableRightTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.displayName || ""} ${table.tableName}`}
onSelect={() => updateTab({ tableName: table.tableName, columns: [] })}
>
<Check
className={cn(
"mr-2 h-4 w-4",
tab.tableName === table.tableName ? "opacity-100" : "opacity-0"
)}
/>
{table.displayName || table.tableName}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
</div>
{/* ===== 3. 표시 모드 ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<Label className="text-xs font-semibold text-blue-600"> </Label>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={tab.displayMode || "list"}
onValueChange={(value: "list" | "table") => updateTab({ displayMode: value })}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="list"> ()</SelectItem>
<SelectItem value="table"></SelectItem>
</SelectContent>
</Select>
</div>
{/* 요약 설정 (목록 모드) */}
{tab.displayMode === "list" && (
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
type="number"
value={tab.summaryColumnCount ?? 3}
onChange={(e) => updateTab({ summaryColumnCount: parseInt(e.target.value) || 3 })}
min={1}
max={10}
className="h-8 text-xs"
/>
</div>
<div className="flex items-center gap-2 pt-5">
<Checkbox
id={`tab-${tabIndex}-summary-label`}
checked={tab.summaryShowLabel ?? true}
onCheckedChange={(checked) => updateTab({ summaryShowLabel: !!checked })}
/>
<label htmlFor={`tab-${tabIndex}-summary-label`} className="text-xs"> </label>
</div>
</div>
)}
</div>
{/* ===== 4. 컬럼 매핑 (조인 키) ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<Label className="text-xs font-semibold text-blue-600"> ( )</Label>
<p className="text-[10px] text-gray-500">
</p>
<div className="mt-2 grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={tab.relation?.keys?.[0]?.leftColumn || tab.relation?.leftColumn || ""}
onValueChange={(value) => {
updateTab({
relation: {
...tab.relation,
type: "join",
keys: [{ leftColumn: value, rightColumn: tab.relation?.keys?.[0]?.rightColumn || "" }],
},
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{leftTableColumns.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-[10px]"> </Label>
<Select
value={tab.relation?.keys?.[0]?.rightColumn || tab.relation?.foreignKey || ""}
onValueChange={(value) => {
updateTab({
relation: {
...tab.relation,
type: "join",
keys: [{ leftColumn: tab.relation?.keys?.[0]?.leftColumn || "", rightColumn: value }],
},
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{tabColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
{/* ===== 5. 기능 버튼 ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<Label className="text-xs font-semibold text-blue-600"> </Label>
<div className="grid grid-cols-4 gap-2">
<div className="flex items-center gap-1">
<Checkbox
id={`tab-${tabIndex}-search`}
checked={tab.showSearch}
onCheckedChange={(checked) => updateTab({ showSearch: !!checked })}
/>
<label htmlFor={`tab-${tabIndex}-search`} className="text-xs"></label>
</div>
<div className="flex items-center gap-1">
<Checkbox
id={`tab-${tabIndex}-add`}
checked={tab.showAdd}
onCheckedChange={(checked) => updateTab({ showAdd: !!checked })}
/>
<label htmlFor={`tab-${tabIndex}-add`} className="text-xs"></label>
</div>
<div className="flex items-center gap-1">
<Checkbox
id={`tab-${tabIndex}-edit`}
checked={tab.showEdit}
onCheckedChange={(checked) => updateTab({ showEdit: !!checked })}
/>
<label htmlFor={`tab-${tabIndex}-edit`} className="text-xs"></label>
</div>
<div className="flex items-center gap-1">
<Checkbox
id={`tab-${tabIndex}-delete`}
checked={tab.showDelete}
onCheckedChange={(checked) => updateTab({ showDelete: !!checked })}
/>
<label htmlFor={`tab-${tabIndex}-delete`} className="text-xs"></label>
</div>
</div>
</div>
{/* ===== 6. 표시 컬럼 설정 ===== */}
<div className="space-y-3 rounded-lg border border-green-200 bg-green-50 p-3">
<div className="flex items-center justify-between">
<Label className="text-xs font-semibold text-green-700"> </Label>
<Button
size="sm"
variant="outline"
onClick={() => {
const currentColumns = tab.columns || [];
const newColumns = [...currentColumns, { name: "", label: "", width: 100 }];
updateTab({ columns: newColumns });
}}
className="h-7 text-xs"
disabled={!tab.tableName || loadingTabColumns}
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
<p className="text-[10px] text-gray-600">
. .
</p>
{/* 테이블 미선택 상태 */}
{!tab.tableName && (
<div className="rounded-md border border-dashed border-gray-300 bg-white p-3 text-center">
<p className="text-xs text-gray-500"> </p>
</div>
)}
{/* 테이블 선택됨 - 컬럼 목록 */}
{tab.tableName && (
<div className="space-y-2">
{/* 로딩 상태 */}
{loadingTabColumns && (
<div className="rounded-md border border-dashed border-gray-300 bg-white p-3 text-center">
<p className="text-xs text-gray-500"> ...</p>
</div>
)}
{/* 설정된 컬럼이 없을 때 */}
{!loadingTabColumns && (tab.columns || []).length === 0 && (
<div className="rounded-md border border-dashed border-gray-300 bg-white p-3 text-center">
<p className="text-xs text-gray-500"> </p>
<p className="mt-1 text-[10px] text-gray-400"> </p>
</div>
)}
{/* 설정된 컬럼 목록 */}
{!loadingTabColumns && (tab.columns || []).length > 0 && (
(tab.columns || []).map((col, colIndex) => (
<div key={colIndex} className="space-y-2 rounded-md border bg-white p-3">
{/* 상단: 순서 변경 + 삭제 버튼 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-1">
<Button
size="sm"
variant="ghost"
onClick={() => {
if (colIndex === 0) return;
const newColumns = [...(tab.columns || [])];
[newColumns[colIndex - 1], newColumns[colIndex]] = [newColumns[colIndex], newColumns[colIndex - 1]];
updateTab({ columns: newColumns });
}}
disabled={colIndex === 0}
className="h-6 w-6 p-0"
>
<ArrowUp className="h-3 w-3" />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => {
const columns = tab.columns || [];
if (colIndex === columns.length - 1) return;
const newColumns = [...columns];
[newColumns[colIndex], newColumns[colIndex + 1]] = [newColumns[colIndex + 1], newColumns[colIndex]];
updateTab({ columns: newColumns });
}}
disabled={colIndex === (tab.columns || []).length - 1}
className="h-6 w-6 p-0"
>
<ArrowDown className="h-3 w-3" />
</Button>
<span className="text-[10px] text-gray-400">#{colIndex + 1}</span>
</div>
<Button
size="sm"
variant="ghost"
onClick={() => {
const newColumns = (tab.columns || []).filter((_, i) => i !== colIndex);
updateTab({ columns: newColumns });
}}
className="h-6 w-6 p-0 text-red-500 hover:bg-red-50 hover:text-red-600"
>
<X className="h-3 w-3" />
</Button>
</div>
{/* 컬럼 선택 */}
<div className="space-y-1">
<Label className="text-[10px] text-gray-500"></Label>
<Select
value={col.name}
onValueChange={(value) => {
const selectedCol = tabColumns.find((c) => c.columnName === value);
const newColumns = [...(tab.columns || [])];
newColumns[colIndex] = {
...col,
name: value,
label: selectedCol?.columnLabel || value,
};
updateTab({ columns: newColumns });
}}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="컬럼을 선택하세요" />
</SelectTrigger>
<SelectContent>
{tabColumns.map((column) => (
<SelectItem key={column.columnName} value={column.columnName}>
{column.columnLabel || column.columnName}
<span className="ml-1 text-[10px] text-gray-400">({column.columnName})</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 라벨 + 너비 */}
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px] text-gray-500"></Label>
<Input
value={col.label}
onChange={(e) => {
const newColumns = [...(tab.columns || [])];
newColumns[colIndex] = { ...col, label: e.target.value };
updateTab({ columns: newColumns });
}}
placeholder="표시 라벨"
className="h-8 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-[10px] text-gray-500"> (px)</Label>
<Input
type="number"
value={col.width || 100}
onChange={(e) => {
const newColumns = [...(tab.columns || [])];
newColumns[colIndex] = { ...col, width: parseInt(e.target.value) || 100 };
updateTab({ columns: newColumns });
}}
placeholder="100"
className="h-8 text-xs"
/>
</div>
</div>
</div>
))
)}
</div>
)}
</div>
{/* ===== 7. 추가 모달 컬럼 설정 (showAdd일 때) ===== */}
{tab.showAdd && (
<div className="space-y-3 rounded-lg border border-purple-200 bg-purple-50 p-3">
<div className="flex items-center justify-between">
<Label className="text-xs font-semibold text-purple-700"> </Label>
<Button
size="sm"
variant="outline"
onClick={() => {
const currentColumns = tab.addModalColumns || [];
const newColumns = [...currentColumns, { name: "", label: "", required: false }];
updateTab({ addModalColumns: newColumns });
}}
className="h-7 text-xs"
disabled={!tab.tableName}
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
<div className="space-y-2">
{(tab.addModalColumns || []).length === 0 ? (
<div className="rounded-md border border-dashed border-gray-300 bg-white p-3 text-center">
<p className="text-xs text-gray-500"> </p>
</div>
) : (
(tab.addModalColumns || []).map((col, colIndex) => (
<div key={colIndex} className="flex items-center gap-2 rounded-md border bg-white p-2">
<Select
value={col.name}
onValueChange={(value) => {
const selectedCol = tabColumns.find((c) => c.columnName === value);
const newColumns = [...(tab.addModalColumns || [])];
newColumns[colIndex] = {
...col,
name: value,
label: selectedCol?.columnLabel || value,
};
updateTab({ addModalColumns: newColumns });
}}
>
<SelectTrigger className="h-8 flex-1 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{tabColumns.map((column) => (
<SelectItem key={column.columnName} value={column.columnName}>
{column.columnLabel || column.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
<Input
value={col.label}
onChange={(e) => {
const newColumns = [...(tab.addModalColumns || [])];
newColumns[colIndex] = { ...col, label: e.target.value };
updateTab({ addModalColumns: newColumns });
}}
placeholder="라벨"
className="h-8 w-24 text-xs"
/>
<div className="flex items-center gap-1">
<Checkbox
checked={col.required}
onCheckedChange={(checked) => {
const newColumns = [...(tab.addModalColumns || [])];
newColumns[colIndex] = { ...col, required: !!checked };
updateTab({ addModalColumns: newColumns });
}}
/>
<span className="text-[10px]"></span>
</div>
<Button
size="sm"
variant="ghost"
onClick={() => {
const newColumns = (tab.addModalColumns || []).filter((_, i) => i !== colIndex);
updateTab({ addModalColumns: newColumns });
}}
className="h-8 w-8 p-0 text-red-500"
>
<X className="h-3 w-3" />
</Button>
</div>
))
)}
</div>
</div>
)}
{/* ===== 8. 데이터 필터링 ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<Label className="text-xs font-semibold text-blue-600"> </Label>
<DataFilterConfigPanel
tableName={tab.tableName}
columns={tabColumns}
config={tab.dataFilter}
onConfigChange={(dataFilter) => updateTab({ dataFilter })}
menuObjid={menuObjid}
/>
</div>
{/* ===== 9. 중복 데이터 제거 ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<div className="flex items-center justify-between">
<Label className="text-xs font-semibold text-blue-600"> </Label>
<Switch
checked={tab.deduplication?.enabled ?? false}
onCheckedChange={(checked) => {
if (checked) {
updateTab({
deduplication: {
enabled: true,
groupByColumn: "",
keepStrategy: "latest",
sortColumn: "start_date",
},
});
} else {
updateTab({ deduplication: undefined });
}
}}
/>
</div>
{tab.deduplication?.enabled && (
<div className="mt-2 space-y-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={tab.deduplication?.groupByColumn || ""}
onValueChange={(value) => {
updateTab({
deduplication: { ...tab.deduplication!, groupByColumn: value },
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{tabColumns.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-[10px]"> </Label>
<Select
value={tab.deduplication?.sortColumn || ""}
onValueChange={(value) => {
updateTab({
deduplication: { ...tab.deduplication!, sortColumn: value },
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{tabColumns.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-[10px]"> </Label>
<Select
value={tab.deduplication?.keepStrategy || "latest"}
onValueChange={(value: "latest" | "earliest" | "base_price" | "current_date") => {
updateTab({
deduplication: { ...tab.deduplication!, keepStrategy: value },
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="latest"></SelectItem>
<SelectItem value="earliest"> </SelectItem>
<SelectItem value="current_date"> </SelectItem>
<SelectItem value="base_price"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
</div>
{/* ===== 10. 수정 버튼 설정 ===== */}
{tab.showEdit && (
<div className="space-y-3 rounded-lg border border-blue-200 bg-blue-50 p-3">
<Label className="text-xs font-semibold text-blue-700"> </Label>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={tab.editButton?.mode || "auto"}
onValueChange={(value: "auto" | "modal") => {
updateTab({
editButton: { ...tab.editButton, enabled: true, mode: value },
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto"> ()</SelectItem>
<SelectItem value="modal"> </SelectItem>
</SelectContent>
</Select>
</div>
{tab.editButton?.mode === "modal" && (
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<ScreenSelector
value={tab.editButton?.modalScreenId}
onChange={(screenId) => {
updateTab({
editButton: { ...tab.editButton, enabled: true, mode: "modal", modalScreenId: screenId },
});
}}
/>
</div>
)}
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Input
value={tab.editButton?.buttonLabel || ""}
onChange={(e) => {
updateTab({
editButton: { ...tab.editButton, enabled: true, buttonLabel: e.target.value || undefined },
});
}}
placeholder="수정"
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={tab.editButton?.buttonVariant || "ghost"}
onValueChange={(value: "default" | "outline" | "ghost") => {
updateTab({
editButton: { ...tab.editButton, enabled: true, buttonVariant: value },
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ghost">Ghost ()</SelectItem>
<SelectItem value="outline">Outline</SelectItem>
<SelectItem value="default">Default</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* 그룹핑 기준 컬럼 */}
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<p className="text-[9px] text-gray-500"> </p>
<div className="max-h-[120px] space-y-1 overflow-y-auto rounded-md border bg-white p-2">
{tabColumns.map((col) => (
<div key={col.columnName} className="flex items-center gap-2">
<Checkbox
id={`tab-${tabIndex}-groupby-${col.columnName}`}
checked={(tab.editButton?.groupByColumns || []).includes(col.columnName)}
onCheckedChange={(checked) => {
const current = tab.editButton?.groupByColumns || [];
const newColumns = checked
? [...current, col.columnName]
: current.filter((c) => c !== col.columnName);
updateTab({
editButton: { ...tab.editButton, enabled: true, groupByColumns: newColumns },
});
}}
/>
<label htmlFor={`tab-${tabIndex}-groupby-${col.columnName}`} className="text-[10px]">
{col.columnLabel || col.columnName}
</label>
</div>
))}
</div>
</div>
</div>
</div>
)}
{/* ===== 11. 삭제 버튼 설정 ===== */}
{tab.showDelete && (
<div className="space-y-3 rounded-lg border border-red-200 bg-red-50 p-3">
<Label className="text-xs font-semibold text-red-700"> </Label>
<div className="space-y-2">
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Input
value={tab.deleteButton?.buttonLabel || ""}
onChange={(e) => {
updateTab({
deleteButton: { ...tab.deleteButton, enabled: true, buttonLabel: e.target.value || undefined },
});
}}
placeholder="삭제"
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={tab.deleteButton?.buttonVariant || "ghost"}
onValueChange={(value: "default" | "outline" | "ghost" | "destructive") => {
updateTab({
deleteButton: { ...tab.deleteButton, enabled: true, buttonVariant: value },
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="ghost">Ghost ()</SelectItem>
<SelectItem value="outline">Outline</SelectItem>
<SelectItem value="default">Default</SelectItem>
<SelectItem value="destructive">Destructive ()</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Input
value={tab.deleteButton?.confirmMessage || ""}
onChange={(e) => {
updateTab({
deleteButton: { ...tab.deleteButton, enabled: true, confirmMessage: e.target.value || undefined },
});
}}
placeholder="정말 삭제하시겠습니까?"
className="h-7 text-xs"
/>
</div>
</div>
</div>
)}
{/* ===== 탭 삭제 버튼 ===== */}
<div className="flex justify-end border-t pt-2">
<Button
variant="ghost"
size="sm"
className="text-red-500 hover:bg-red-50 hover:text-red-600"
onClick={() => {
const newTabs = config.rightPanel?.additionalTabs?.filter((_, i) => i !== tabIndex) || [];
updateRightPanel({ additionalTabs: newTabs });
}}
>
<Trash2 className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
</AccordionContent>
</AccordionItem>
);
};
/**
* SplitPanelLayout 설정 패널
*/
@@ -2854,6 +3697,72 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
)}
</div>
{/* ======================================== */}
{/* 추가 탭 설정 (우측 패널과 동일한 구조) */}
{/* ======================================== */}
<div className="mt-4 space-y-4 border-t pt-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-xs">
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => {
const newTab: AdditionalTabConfig = {
tabId: `tab_${Date.now()}`,
label: `${(config.rightPanel?.additionalTabs?.length || 0) + 1}`,
title: "",
tableName: "",
displayMode: "list",
showSearch: false,
showAdd: false,
showEdit: true,
showDelete: true,
summaryColumnCount: 3,
summaryShowLabel: true,
};
updateRightPanel({
additionalTabs: [...(config.rightPanel?.additionalTabs || []), newTab],
});
}}
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
{/* 추가된 탭 목록 */}
{(config.rightPanel?.additionalTabs?.length || 0) > 0 ? (
<Accordion type="multiple" className="space-y-2">
{config.rightPanel?.additionalTabs?.map((tab, tabIndex) => (
<AdditionalTabConfigPanel
key={tab.tabId}
tab={tab}
tabIndex={tabIndex}
config={config}
updateRightPanel={updateRightPanel}
availableRightTables={availableRightTables}
leftTableColumns={leftTableColumns}
menuObjid={menuObjid}
loadedTableColumns={loadedTableColumns}
loadTableColumns={loadTableColumns}
loadingColumns={loadingColumns}
/>
))}
</Accordion>
) : (
<div className="rounded-lg border border-dashed p-4 text-center">
<p className="text-xs text-gray-500">
. [ ] .
</p>
</div>
)}
</div>
{/* 레이아웃 설정 */}
<div className="mt-4 space-y-4 border-t pt-4">
<div className="space-y-2">