ERP-node/frontend/lib/registry/components/v2-split-panel-layout/config-panels/RightPanelConfigTab.tsx

802 lines
37 KiB
TypeScript

"use client";
import React from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Check, ChevronsUpDown, ChevronRight, Link2, Move, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { SplitPanelLayoutConfig } from "../types";
import { PanelInlineComponent } from "../types";
import { ColumnInfo, TableInfo } from "@/types/screen";
import { DataFilterConfigPanel } from "@/components/screen/config-panels/DataFilterConfigPanel";
import { DndContext, closestCenter, type DragEndEvent } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy, arrayMove } from "@dnd-kit/sortable";
import { SortableColumnRow, ScreenSelector, GroupByColumnsSelector } from "./SharedComponents";
export interface RightPanelConfigTabProps {
config: SplitPanelLayoutConfig;
onChange: (config: SplitPanelLayoutConfig) => void;
updateRightPanel: (updates: Partial<SplitPanelLayoutConfig["rightPanel"]>) => void;
relationshipType: "join" | "detail";
localTitles: { left: string; right: string };
setLocalTitles: (fn: (prev: { left: string; right: string }) => { left: string; right: string }) => void;
setIsUserEditing: (v: boolean) => void;
rightTableOpen: boolean;
setRightTableOpen: (open: boolean) => void;
availableRightTables: TableInfo[];
rightTableColumns: ColumnInfo[];
entityJoinColumns: Record<string, {
availableColumns: Array<{ tableName: string; columnName: string; columnLabel: string; dataType: string; joinAlias: string; suggestedLabel: string }>;
joinTables: Array<{ tableName: string; currentDisplayColumn: string; joinConfig?: any; availableColumns: Array<{ columnName: string; columnLabel: string; dataType: string; inputType?: string; description?: string }> }>;
}>;
menuObjid?: number;
renderAdditionalTabs?: () => React.ReactNode;
}
export const RightPanelConfigTab: React.FC<RightPanelConfigTabProps> = ({
config,
onChange,
updateRightPanel,
relationshipType,
localTitles,
setLocalTitles,
setIsUserEditing,
rightTableOpen,
setRightTableOpen,
availableRightTables,
rightTableColumns,
entityJoinColumns,
menuObjid,
renderAdditionalTabs,
}) => {
const dbNumericTypes = ["numeric", "decimal", "integer", "bigint", "double precision", "real", "smallint", "int4", "int8", "float4", "float8"];
const inputNumericTypes = ["number", "decimal", "currency", "integer"];
return (
<div className="space-y-4">
<div className="space-y-4 rounded-lg border border-border/50 bg-muted/40 p-4">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> ({relationshipType === "detail" ? "선택 시 표시" : "연관 목록"})</h3>
<div className="space-y-2">
<Label> </Label>
<Input
value={localTitles.right}
onChange={(e) => {
setIsUserEditing(true);
setLocalTitles((prev) => ({ ...prev, right: e.target.value }));
}}
onBlur={() => {
setIsUserEditing(false);
updateRightPanel({ title: localTitles.right });
}}
placeholder="우측 패널 제목"
/>
</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">
{availableRightTables.map((table) => {
const tableName = (table as any).tableName || (table as any).table_name;
return (
<CommandItem
key={tableName}
value={`${(table as any).displayName || ""} ${tableName}`}
onSelect={() => {
updateRightPanel({ tableName });
setRightTableOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.rightPanel?.tableName === tableName ? "opacity-100" : "opacity-0",
)}
/>
{(table as any).displayName || tableName}
{(table as any).displayName && <span className="ml-2 text-xs text-gray-500">({tableName})</span>}
</CommandItem>
);
})}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<Label> </Label>
<Select
value={config.rightPanel?.displayMode || "list"}
onValueChange={(value: "list" | "table" | "custom") => updateRightPanel({ displayMode: value })}
>
<SelectTrigger className="h-10 bg-white">
<SelectValue placeholder="표시 모드 선택">
{(config.rightPanel?.displayMode || "list") === "list" ? "목록 (LIST)" : (config.rightPanel?.displayMode || "list") === "table" ? "테이블 (TABLE)" : "커스텀 (CUSTOM)"}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="list">
<div className="flex flex-col py-1">
<span className="text-sm font-medium"> (LIST)</span>
<span className="text-xs text-gray-500"> ()</span>
</div>
</SelectItem>
<SelectItem value="table">
<div className="flex flex-col py-1">
<span className="text-sm font-medium"> (TABLE)</span>
<span className="text-xs text-gray-500"> </span>
</div>
</SelectItem>
<SelectItem value="custom">
<div className="flex flex-col py-1">
<span className="text-sm font-medium"> (CUSTOM)</span>
<span className="text-xs text-gray-500"> </span>
</div>
</SelectItem>
</SelectContent>
</Select>
{config.rightPanel?.displayMode === "custom" && (
<p className="text-xs text-amber-600">
.
</p>
)}
</div>
{config.rightPanel?.displayMode === "custom" && (
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
{!config.rightPanel?.components || config.rightPanel.components.length === 0 ? (
<div className="rounded-md border border-dashed bg-muted/30 p-4 text-center">
<Move className="mx-auto mb-2 h-6 w-6 text-muted-foreground" />
<p className="text-muted-foreground text-xs">
</p>
</div>
) : (
<div className="space-y-2">
{config.rightPanel.components.map((comp: PanelInlineComponent) => (
<div
key={comp.id}
className="flex items-center justify-between rounded-md border bg-background p-2"
>
<div className="flex-1">
<p className="text-xs font-medium">{comp.label || comp.componentType}</p>
<p className="text-muted-foreground text-[10px]">
{comp.componentType} | : ({comp.position?.x || 0}, {comp.position?.y || 0}) | : {comp.size?.width || 0}x{comp.size?.height || 0}
</p>
</div>
<Button
onClick={() => {
const updatedComponents = (config.rightPanel?.components || []).filter(
(c: PanelInlineComponent) => c.id !== comp.id
);
onChange({
...config,
rightPanel: {
...config.rightPanel,
components: updatedComponents,
},
});
}}
size="sm"
variant="ghost"
className="h-6 w-6 p-0 text-destructive hover:text-destructive"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
))}
</div>
)}
</div>
)}
{(config.rightPanel?.displayMode || "list") === "list" && (
<div className="space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
<Label className="text-sm font-semibold"> </Label>
<div className="space-y-2">
<Label className="text-xs"> </Label>
<Input
type="number"
min="1"
max="10"
value={config.rightPanel?.summaryColumnCount ?? 3}
onChange={(e) => {
const value = parseInt(e.target.value) || 3;
updateRightPanel({ summaryColumnCount: value });
}}
className="bg-white"
/>
<p className="text-xs text-gray-500"> (기본: 3개)</p>
</div>
<div className="flex items-center justify-between space-x-2">
<div className="flex-1">
<Label className="text-xs"> </Label>
<p className="text-xs text-gray-500"> </p>
</div>
<Checkbox
checked={config.rightPanel?.summaryShowLabel ?? true}
onCheckedChange={(checked) => {
updateRightPanel({ summaryShowLabel: checked as boolean });
}}
/>
</div>
</div>
)}
{config.rightPanel?.displayMode !== "custom" && (() => {
const selectedColumns = config.rightPanel?.columns || [];
const filteredTableColumns = rightTableColumns.filter((c) => !["company_code", "company_name"].includes(c.columnName));
const unselectedColumns = filteredTableColumns.filter((c) => !selectedColumns.some((sc) => sc.name === c.columnName));
const handleRightDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = selectedColumns.findIndex((c) => c.name === active.id);
const newIndex = selectedColumns.findIndex((c) => c.name === over.id);
if (oldIndex !== -1 && newIndex !== -1) {
updateRightPanel({ columns: arrayMove([...selectedColumns], oldIndex, newIndex) });
}
}
};
return (
<div className="space-y-2">
<Label className="text-xs font-medium"> ({selectedColumns.length} )</Label>
<div className="max-h-[400px] overflow-y-auto rounded-md border p-2">
{rightTableColumns.length === 0 ? (
<p className="text-muted-foreground py-2 text-center text-xs"> </p>
) : (
<>
{selectedColumns.length > 0 && (
<DndContext collisionDetection={closestCenter} onDragEnd={handleRightDragEnd}>
<SortableContext items={selectedColumns.map((c) => c.name)} strategy={verticalListSortingStrategy}>
<div className="space-y-1">
{selectedColumns.map((col, index) => {
const colInfo = rightTableColumns.find((c) => c.columnName === col.name);
const isNumeric = colInfo && (
dbNumericTypes.includes(colInfo.dataType?.toLowerCase() || "") ||
inputNumericTypes.includes(colInfo.input_type?.toLowerCase() || "") ||
inputNumericTypes.includes(colInfo.webType?.toLowerCase() || "")
);
return (
<SortableColumnRow
key={col.name}
id={col.name}
col={col}
index={index}
isNumeric={!!isNumeric}
isEntityJoin={!!(col as any).isEntityJoin}
onLabelChange={(value) => {
const newColumns = [...selectedColumns];
newColumns[index] = { ...newColumns[index], label: value };
updateRightPanel({ columns: newColumns });
}}
onWidthChange={(value) => {
const newColumns = [...selectedColumns];
newColumns[index] = { ...newColumns[index], width: value };
updateRightPanel({ columns: newColumns });
}}
onFormatChange={(checked) => {
const newColumns = [...selectedColumns];
newColumns[index] = { ...newColumns[index], format: { ...newColumns[index].format, type: "number", thousandSeparator: checked } };
updateRightPanel({ columns: newColumns });
}}
onRemove={() => updateRightPanel({ columns: selectedColumns.filter((_, i) => i !== index) })}
onShowInSummaryChange={(checked) => {
const newColumns = [...selectedColumns];
newColumns[index] = { ...newColumns[index], showInSummary: checked };
updateRightPanel({ columns: newColumns });
}}
onShowInDetailChange={(checked) => {
const newColumns = [...selectedColumns];
newColumns[index] = { ...newColumns[index], showInDetail: checked };
updateRightPanel({ columns: newColumns });
}}
/>
);
})}
</div>
</SortableContext>
</DndContext>
)}
{selectedColumns.length > 0 && unselectedColumns.length > 0 && (
<div className="border-border/60 my-2 flex items-center gap-2 border-t pt-2">
<span className="text-muted-foreground text-[10px]"> </span>
</div>
)}
<div className="space-y-0.5">
{unselectedColumns.map((column) => (
<div
key={column.columnName}
className="hover:bg-muted/50 flex cursor-pointer items-center gap-2 rounded px-2 py-1.5"
onClick={() => {
updateRightPanel({ columns: [...selectedColumns, { name: column.columnName, label: column.columnLabel || column.columnName, width: 100 }] });
}}
>
<Checkbox checked={false} className="pointer-events-none h-3.5 w-3.5 shrink-0" />
<span className="text-muted-foreground truncate text-xs">{column.columnLabel || column.columnName}</span>
</div>
))}
</div>
{(() => {
const rightTable = config.rightPanel?.tableName;
const joinData = rightTable ? entityJoinColumns[rightTable] : null;
if (!joinData || joinData.joinTables.length === 0) return null;
return joinData.joinTables.map((joinTable, tableIndex) => {
const joinColumnsToShow = joinTable.availableColumns.filter((column) => {
const matchingJoinColumn = joinData.availableColumns.find(
(jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName,
);
if (!matchingJoinColumn) return false;
return !selectedColumns.some((c) => c.name === matchingJoinColumn.joinAlias);
});
const addedCount = joinTable.availableColumns.length - joinColumnsToShow.length;
if (joinColumnsToShow.length === 0 && addedCount === 0) return null;
return (
<details key={`join-${tableIndex}`} className="group">
<summary className="border-border/60 my-2 flex cursor-pointer list-none items-center gap-2 border-t pt-2 select-none">
<ChevronRight className="h-3 w-3 shrink-0 text-blue-500 transition-transform group-open:rotate-90" />
<Link2 className="h-3 w-3 shrink-0 text-blue-500" />
<span className="text-[10px] font-medium text-blue-600">{joinTable.tableName}</span>
{addedCount > 0 && (
<span className="rounded-full bg-blue-100 px-1.5 text-[9px] font-medium text-blue-600">{addedCount} </span>
)}
<span className="text-[9px] text-gray-400">{joinColumnsToShow.length} </span>
</summary>
<div className="space-y-0.5 pt-1">
{joinColumnsToShow.map((column, colIndex) => {
const matchingJoinColumn = joinData.availableColumns.find(
(jc) => jc.tableName === joinTable.tableName && jc.columnName === column.columnName,
);
if (!matchingJoinColumn) return null;
return (
<div
key={colIndex}
className="flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 hover:bg-blue-50/60"
onClick={() => {
updateRightPanel({
columns: [...selectedColumns, {
name: matchingJoinColumn.joinAlias,
label: matchingJoinColumn.suggestedLabel || matchingJoinColumn.columnLabel,
width: 100,
isEntityJoin: true,
joinInfo: {
sourceTable: rightTable!,
sourceColumn: (joinTable as any).joinConfig?.sourceColumn || "",
referenceTable: matchingJoinColumn.tableName,
joinAlias: matchingJoinColumn.joinAlias,
},
}],
});
}}
>
<Checkbox checked={false} className="pointer-events-none h-3.5 w-3.5 shrink-0" />
<Link2 className="h-3 w-3 shrink-0 text-blue-500" />
<span className="truncate text-xs text-blue-700">{column.columnLabel || column.columnName}</span>
</div>
);
})}
{joinColumnsToShow.length === 0 && (
<p className="px-2 py-1 text-[10px] text-gray-400"> </p>
)}
</div>
</details>
);
});
})()}
</>
)}
</div>
</div>
);
})()}
</div>
<div className="space-y-4 rounded-lg border border-border/50 bg-muted/40 p-4">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-xs"> </p>
<DataFilterConfigPanel
tableName={config.rightPanel?.tableName}
columns={rightTableColumns.map(
(col) =>
({
columnName: col.columnName,
columnLabel: col.columnLabel || col.columnName,
dataType: col.dataType || "text",
input_type: (col as any).input_type,
}) as any,
)}
config={config.rightPanel?.dataFilter}
onConfigChange={(dataFilter) => updateRightPanel({ dataFilter })}
menuObjid={menuObjid}
/>
</div>
<div className="space-y-4 rounded-lg border border-border/50 bg-muted/40 p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-xs"> </p>
</div>
<Switch
checked={config.rightPanel?.deduplication?.enabled ?? false}
onCheckedChange={(checked) => {
if (checked) {
updateRightPanel({
deduplication: {
enabled: true,
groupByColumn: "",
keepStrategy: "latest",
sortColumn: "start_date",
},
});
} else {
updateRightPanel({ deduplication: undefined });
}
}}
/>
</div>
{config.rightPanel?.deduplication?.enabled && (
<div className="space-y-3 border-l-2 pl-4">
<div>
<Label className="text-xs"> </Label>
<Select
value={config.rightPanel?.deduplication?.groupByColumn || ""}
onValueChange={(value) =>
updateRightPanel({
deduplication: { ...config.rightPanel?.deduplication!, groupByColumn: value },
})
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="기준 컬럼 선택" />
</SelectTrigger>
<SelectContent>
{rightTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-muted-foreground mt-1 text-[10px]">
</p>
</div>
<div>
<Label className="text-xs"> </Label>
<Select
value={config.rightPanel?.deduplication?.keepStrategy || "latest"}
onValueChange={(value: any) =>
updateRightPanel({
deduplication: { ...config.rightPanel?.deduplication!, keepStrategy: value },
})
}
>
<SelectTrigger className="h-8 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>
{(config.rightPanel?.deduplication?.keepStrategy === "latest" ||
config.rightPanel?.deduplication?.keepStrategy === "earliest") && (
<div>
<Label className="text-xs"> </Label>
<Select
value={config.rightPanel?.deduplication?.sortColumn || ""}
onValueChange={(value) =>
updateRightPanel({
deduplication: { ...config.rightPanel?.deduplication!, sortColumn: value },
})
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{rightTableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div>
)}
</div>
{/* 수정 버튼 설정 */}
{config.rightPanel?.showEdit && (
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-4">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
<div className="space-y-3">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.rightPanel?.editButton?.mode || "auto"}
onValueChange={(value: "auto" | "modal") =>
updateRightPanel({
editButton: {
...config.rightPanel?.editButton,
enabled: config.rightPanel?.editButton?.enabled ?? true,
mode: value,
},
})
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto"> ()</SelectItem>
<SelectItem value="modal"> </SelectItem>
</SelectContent>
</Select>
</div>
{config.rightPanel?.editButton?.mode === "modal" && (
<GroupByColumnsSelector
tableName={config.rightPanel?.tableName}
selectedColumns={config.rightPanel?.editButton?.groupByColumns || []}
onChange={(columns) => {
updateRightPanel({
editButton: {
...config.rightPanel?.editButton!,
groupByColumns: columns,
enabled: config.rightPanel?.editButton?.enabled ?? true,
mode: config.rightPanel?.editButton?.mode || "auto",
},
});
}}
/>
)}
{config.rightPanel?.editButton?.mode === "modal" && (
<div className="space-y-1">
<Label className="text-xs"> </Label>
<ScreenSelector
value={config.rightPanel?.editButton?.modalScreenId}
onChange={(screenId) =>
updateRightPanel({
editButton: {
...config.rightPanel?.editButton!,
modalScreenId: screenId,
},
})
}
/>
</div>
)}
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={config.rightPanel?.editButton?.buttonLabel || ""}
onChange={(e) =>
updateRightPanel({
editButton: {
...config.rightPanel?.editButton!,
buttonLabel: e.target.value || undefined,
enabled: config.rightPanel?.editButton?.enabled ?? true,
mode: config.rightPanel?.editButton?.mode || "auto",
},
})
}
placeholder="수정"
className="h-8 text-xs"
/>
</div>
</div>
</div>
)}
{/* 추가 버튼 설정 */}
{config.rightPanel?.showAdd && (
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-4">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
<div className="space-y-3">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.rightPanel?.addButton?.mode || "auto"}
onValueChange={(value: "auto" | "modal") =>
updateRightPanel({
addButton: {
...config.rightPanel?.addButton,
mode: value,
enabled: true,
},
})
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto"> ( )</SelectItem>
<SelectItem value="modal"> </SelectItem>
</SelectContent>
</Select>
</div>
{config.rightPanel?.addButton?.mode === "modal" && (
<div className="space-y-1">
<Label className="text-xs"> </Label>
<ScreenSelector
value={config.rightPanel?.addButton?.modalScreenId}
onChange={(screenId) =>
updateRightPanel({
addButton: {
...config.rightPanel?.addButton!,
modalScreenId: screenId,
},
})
}
/>
</div>
)}
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={config.rightPanel?.addButton?.buttonLabel || "추가"}
onChange={(e) =>
updateRightPanel({
addButton: {
...config.rightPanel?.addButton!,
buttonLabel: e.target.value,
enabled: true,
mode: config.rightPanel?.addButton?.mode || "auto",
},
})
}
placeholder="추가"
className="h-8 text-xs"
/>
</div>
</div>
</div>
)}
{/* 삭제 버튼 설정 */}
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-4">
<div className="flex items-center justify-between">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
<Switch
checked={config.rightPanel?.deleteButton?.enabled ?? true}
onCheckedChange={(checked) => {
updateRightPanel({
deleteButton: {
enabled: checked,
buttonLabel: config.rightPanel?.deleteButton?.buttonLabel,
buttonVariant: config.rightPanel?.deleteButton?.buttonVariant,
},
});
}}
/>
</div>
{(config.rightPanel?.deleteButton?.enabled ?? true) && (
<div className="space-y-3">
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Input
value={config.rightPanel?.deleteButton?.buttonLabel || ""}
placeholder="삭제"
onChange={(e) => {
updateRightPanel({
deleteButton: {
...config.rightPanel?.deleteButton!,
buttonLabel: e.target.value || undefined,
enabled: config.rightPanel?.deleteButton?.enabled ?? true,
},
});
}}
className="h-8 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-xs"> </Label>
<Select
value={config.rightPanel?.deleteButton?.buttonVariant || "ghost"}
onValueChange={(value: "default" | "outline" | "ghost" | "destructive") => {
updateRightPanel({
deleteButton: {
...config.rightPanel?.deleteButton!,
buttonVariant: value,
enabled: config.rightPanel?.deleteButton?.enabled ?? true,
},
});
}}
>
<SelectTrigger className="h-8 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-xs"> </Label>
<Input
value={config.rightPanel?.deleteButton?.confirmMessage || ""}
placeholder="정말 삭제하시겠습니까?"
onChange={(e) => {
updateRightPanel({
deleteButton: {
...config.rightPanel?.deleteButton!,
confirmMessage: e.target.value || undefined,
enabled: config.rightPanel?.deleteButton?.enabled ?? true,
},
});
}}
className="h-8 text-xs"
/>
</div>
</div>
)}
</div>
{/* 추가 탭 */}
{renderAdditionalTabs && (
<div className="space-y-4 rounded-lg border border-border/50 bg-muted/40 p-4">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-xs">
</p>
{renderAdditionalTabs()}
</div>
)}
</div>
);
};