- Enhanced the `ScreenManagementService` to include updates for V2 layouts in the `screen_layouts_v2` table. - Implemented logic to remap `screenId`, `targetScreenId`, `modalScreenId`, and other related IDs in layout data. - Added logging for the number of layouts updated in both V1 and V2, improving traceability of the update process. - This update ensures that screen references are correctly maintained across different layout versions, enhancing the overall functionality of the screen management system.
607 lines
30 KiB
TypeScript
607 lines
30 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 { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
import { Check, ChevronsUpDown, ChevronRight, Database, Link2, Move, Trash2 } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { SplitPanelLayoutConfig } from "../types";
|
|
import { PanelInlineComponent } from "../types";
|
|
import { ColumnInfo } 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 } from "./SharedComponents";
|
|
|
|
export interface LeftPanelConfigTabProps {
|
|
config: SplitPanelLayoutConfig;
|
|
onChange: (config: SplitPanelLayoutConfig) => void;
|
|
updateLeftPanel: (updates: Partial<SplitPanelLayoutConfig["leftPanel"]>) => void;
|
|
screenTableName?: string;
|
|
allTables: any[];
|
|
leftTableOpen: boolean;
|
|
setLeftTableOpen: (open: boolean) => void;
|
|
localTitles: { left: string; right: string };
|
|
setLocalTitles: (fn: (prev: { left: string; right: string }) => { left: string; right: string }) => void;
|
|
isUserEditing: boolean;
|
|
setIsUserEditing: (v: boolean) => void;
|
|
leftTableColumns: 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;
|
|
}
|
|
|
|
export const LeftPanelConfigTab: React.FC<LeftPanelConfigTabProps> = ({
|
|
config,
|
|
onChange,
|
|
updateLeftPanel,
|
|
screenTableName,
|
|
allTables,
|
|
leftTableOpen,
|
|
setLeftTableOpen,
|
|
localTitles,
|
|
setLocalTitles,
|
|
setIsUserEditing,
|
|
leftTableColumns,
|
|
entityJoinColumns,
|
|
menuObjid,
|
|
}) => {
|
|
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">좌측 패널 설정 (마스터)</h3>
|
|
|
|
<div className="space-y-2">
|
|
<Label className="text-xs font-medium">표시 테이블</Label>
|
|
<Popover open={leftTableOpen} onOpenChange={setLeftTableOpen}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={leftTableOpen}
|
|
className="h-9 w-full justify-between text-xs"
|
|
>
|
|
<div className="flex items-center gap-2 truncate">
|
|
<Database className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
<span className="truncate">
|
|
{config.leftPanel?.tableName
|
|
? allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.tableLabel ||
|
|
allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.displayName ||
|
|
config.leftPanel?.tableName
|
|
: "테이블을 선택하세요"}
|
|
</span>
|
|
</div>
|
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-[300px] p-0" align="start">
|
|
<Command>
|
|
<CommandInput placeholder="테이블 검색..." className="text-xs" />
|
|
<CommandList>
|
|
<CommandEmpty className="py-3 text-center text-xs text-muted-foreground">
|
|
테이블을 찾을 수 없습니다.
|
|
</CommandEmpty>
|
|
{screenTableName && (
|
|
<CommandGroup heading="기본 (화면 테이블)">
|
|
<CommandItem
|
|
value={screenTableName}
|
|
onSelect={() => {
|
|
updateLeftPanel({ tableName: screenTableName, columns: [] });
|
|
setLeftTableOpen(false);
|
|
}}
|
|
className="text-xs"
|
|
>
|
|
<Check
|
|
className={cn(
|
|
"mr-2 h-4 w-4",
|
|
config.leftPanel?.tableName === screenTableName ? "opacity-100" : "opacity-0"
|
|
)}
|
|
/>
|
|
<Database className="mr-2 h-3.5 w-3.5 text-blue-500" />
|
|
{allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.tableLabel ||
|
|
allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.displayName ||
|
|
screenTableName}
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
)}
|
|
<CommandGroup heading="전체 테이블">
|
|
{allTables
|
|
.filter((t) => (t.tableName || t.table_name) !== screenTableName)
|
|
.map((table) => {
|
|
const tableName = table.tableName || table.table_name;
|
|
const displayName = table.tableLabel || table.displayName || tableName;
|
|
return (
|
|
<CommandItem
|
|
key={tableName}
|
|
value={tableName}
|
|
onSelect={() => {
|
|
updateLeftPanel({ tableName, columns: [] });
|
|
setLeftTableOpen(false);
|
|
}}
|
|
className="text-xs"
|
|
>
|
|
<Check
|
|
className={cn(
|
|
"mr-2 h-4 w-4",
|
|
config.leftPanel?.tableName === tableName ? "opacity-100" : "opacity-0"
|
|
)}
|
|
/>
|
|
<Database className="mr-2 h-3.5 w-3.5 text-muted-foreground" />
|
|
<span className="truncate">{displayName}</span>
|
|
</CommandItem>
|
|
);
|
|
})}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
{config.leftPanel?.tableName && config.leftPanel?.tableName !== screenTableName && (
|
|
<p className="text-[10px] text-muted-foreground">
|
|
화면 기본 테이블이 아닌 다른 테이블의 데이터를 표시합니다.
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>패널 제목</Label>
|
|
<Input
|
|
value={localTitles.left}
|
|
onChange={(e) => {
|
|
setIsUserEditing(true);
|
|
setLocalTitles((prev) => ({ ...prev, left: e.target.value }));
|
|
}}
|
|
onBlur={() => {
|
|
setIsUserEditing(false);
|
|
updateLeftPanel({ title: localTitles.left });
|
|
}}
|
|
placeholder="좌측 패널 제목"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>표시 모드</Label>
|
|
<Select
|
|
value={config.leftPanel?.displayMode || "list"}
|
|
onValueChange={(value: "list" | "table" | "custom") => updateLeftPanel({ displayMode: value })}
|
|
>
|
|
<SelectTrigger className="h-10 bg-white">
|
|
<SelectValue placeholder="표시 모드 선택">
|
|
{(config.leftPanel?.displayMode || "list") === "list" ? "목록 (LIST)" : (config.leftPanel?.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.leftPanel?.displayMode === "custom" && (
|
|
<p className="text-xs text-amber-600">
|
|
화면 디자이너에서 좌측 패널에 컴포넌트를 드래그하여 배치하세요.
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{config.leftPanel?.displayMode === "custom" && (
|
|
<div className="space-y-2">
|
|
<Label className="text-xs font-medium">배치된 컴포넌트</Label>
|
|
{!config.leftPanel?.components || config.leftPanel.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.leftPanel.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.leftPanel?.components || []).filter(
|
|
(c: PanelInlineComponent) => c.id !== comp.id
|
|
);
|
|
onChange({
|
|
...config,
|
|
leftPanel: {
|
|
...config.leftPanel,
|
|
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.leftPanel?.displayMode !== "custom" && (() => {
|
|
const selectedColumns = config.leftPanel?.columns || [];
|
|
const filteredTableColumns = leftTableColumns.filter((c) => !["company_code", "company_name"].includes(c.columnName));
|
|
const unselectedColumns = filteredTableColumns.filter((c) => !selectedColumns.some((sc) => sc.name === c.columnName));
|
|
|
|
const handleLeftDragEnd = (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) {
|
|
updateLeftPanel({ 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">
|
|
{leftTableColumns.length === 0 ? (
|
|
<p className="text-muted-foreground py-2 text-center text-xs">컬럼 로딩 중...</p>
|
|
) : (
|
|
<>
|
|
{selectedColumns.length > 0 && (
|
|
<DndContext collisionDetection={closestCenter} onDragEnd={handleLeftDragEnd}>
|
|
<SortableContext items={selectedColumns.map((c) => c.name)} strategy={verticalListSortingStrategy}>
|
|
<div className="space-y-1">
|
|
{selectedColumns.map((col, index) => {
|
|
const colInfo = leftTableColumns.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 };
|
|
updateLeftPanel({ columns: newColumns });
|
|
}}
|
|
onWidthChange={(value) => {
|
|
const newColumns = [...selectedColumns];
|
|
newColumns[index] = { ...newColumns[index], width: value };
|
|
updateLeftPanel({ columns: newColumns });
|
|
}}
|
|
onFormatChange={(checked) => {
|
|
const newColumns = [...selectedColumns];
|
|
newColumns[index] = { ...newColumns[index], format: { ...newColumns[index].format, type: "number", thousandSeparator: checked } };
|
|
updateLeftPanel({ columns: newColumns });
|
|
}}
|
|
onRemove={() => updateLeftPanel({ columns: selectedColumns.filter((_, i) => i !== index) })}
|
|
/>
|
|
);
|
|
})}
|
|
</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={() => {
|
|
updateLeftPanel({ 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 leftTable = config.leftPanel?.tableName || screenTableName;
|
|
const joinData = leftTable ? entityJoinColumns[leftTable] : 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={() => {
|
|
updateLeftPanel({
|
|
columns: [...selectedColumns, {
|
|
name: matchingJoinColumn.joinAlias,
|
|
label: matchingJoinColumn.suggestedLabel || matchingJoinColumn.columnLabel,
|
|
width: 100,
|
|
isEntityJoin: true,
|
|
joinInfo: {
|
|
sourceTable: leftTable!,
|
|
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.leftPanel?.tableName || screenTableName}
|
|
columns={leftTableColumns.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.leftPanel?.dataFilter}
|
|
onConfigChange={(dataFilter) => updateLeftPanel({ dataFilter })}
|
|
menuObjid={menuObjid}
|
|
/>
|
|
</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>
|
|
|
|
<div className="grid grid-cols-4 gap-2">
|
|
<div className="flex items-center gap-1">
|
|
<Checkbox
|
|
id="left-showSearch"
|
|
checked={config.leftPanel?.showSearch ?? false}
|
|
onCheckedChange={(checked) => updateLeftPanel({ showSearch: !!checked })}
|
|
/>
|
|
<label htmlFor="left-showSearch" className="text-xs">검색</label>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Checkbox
|
|
id="left-showAdd"
|
|
checked={config.leftPanel?.showAdd ?? false}
|
|
onCheckedChange={(checked) => updateLeftPanel({ showAdd: !!checked })}
|
|
/>
|
|
<label htmlFor="left-showAdd" className="text-xs">추가</label>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Checkbox
|
|
id="left-showEdit"
|
|
checked={config.leftPanel?.showEdit ?? true}
|
|
onCheckedChange={(checked) => updateLeftPanel({ showEdit: !!checked })}
|
|
/>
|
|
<label htmlFor="left-showEdit" className="text-xs">수정</label>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<Checkbox
|
|
id="left-showDelete"
|
|
checked={config.leftPanel?.showDelete ?? true}
|
|
onCheckedChange={(checked) => updateLeftPanel({ showDelete: !!checked })}
|
|
/>
|
|
<label htmlFor="left-showDelete" className="text-xs">삭제</label>
|
|
</div>
|
|
</div>
|
|
|
|
{config.leftPanel?.showAdd && (
|
|
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-3">
|
|
<h3 className="border-l-2 border-l-primary/40 pl-2 text-xs font-semibold">추가 버튼 설정</h3>
|
|
<div className="space-y-2">
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">추가 모드</Label>
|
|
<Select
|
|
value={config.leftPanel?.addButton?.mode || "auto"}
|
|
onValueChange={(value: "auto" | "modal") =>
|
|
updateLeftPanel({
|
|
addButton: { ...config.leftPanel?.addButton, enabled: true, mode: value },
|
|
})
|
|
}
|
|
>
|
|
<SelectTrigger className="h-7 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="auto">자동 (내장 폼)</SelectItem>
|
|
<SelectItem value="modal">모달 화면</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{config.leftPanel?.addButton?.mode === "modal" && (
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">추가 모달 화면</Label>
|
|
<ScreenSelector
|
|
value={config.leftPanel?.addButton?.modalScreenId}
|
|
onChange={(screenId) =>
|
|
updateLeftPanel({
|
|
addButton: { ...config.leftPanel?.addButton, enabled: true, mode: "modal", modalScreenId: screenId },
|
|
})
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">버튼 라벨</Label>
|
|
<Input
|
|
value={config.leftPanel?.addButton?.buttonLabel || ""}
|
|
onChange={(e) =>
|
|
updateLeftPanel({
|
|
addButton: {
|
|
...config.leftPanel?.addButton,
|
|
enabled: true,
|
|
mode: config.leftPanel?.addButton?.mode || "auto",
|
|
buttonLabel: e.target.value || undefined,
|
|
},
|
|
})
|
|
}
|
|
placeholder="추가"
|
|
className="h-7 text-xs"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{(config.leftPanel?.showEdit ?? true) && (
|
|
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-3">
|
|
<h3 className="border-l-2 border-l-primary/40 pl-2 text-xs font-semibold">수정 버튼 설정</h3>
|
|
<div className="space-y-2">
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">수정 모드</Label>
|
|
<Select
|
|
value={config.leftPanel?.editButton?.mode || "auto"}
|
|
onValueChange={(value: "auto" | "modal") =>
|
|
updateLeftPanel({
|
|
editButton: { ...config.leftPanel?.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>
|
|
|
|
{config.leftPanel?.editButton?.mode === "modal" && (
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">수정 모달 화면</Label>
|
|
<ScreenSelector
|
|
value={config.leftPanel?.editButton?.modalScreenId}
|
|
onChange={(screenId) =>
|
|
updateLeftPanel({
|
|
editButton: { ...config.leftPanel?.editButton, enabled: true, mode: "modal", modalScreenId: screenId },
|
|
})
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">버튼 라벨</Label>
|
|
<Input
|
|
value={config.leftPanel?.editButton?.buttonLabel || ""}
|
|
onChange={(e) =>
|
|
updateLeftPanel({
|
|
editButton: {
|
|
...config.leftPanel?.editButton,
|
|
enabled: true,
|
|
mode: config.leftPanel?.editButton?.mode || "auto",
|
|
buttonLabel: e.target.value || undefined,
|
|
},
|
|
})
|
|
}
|
|
placeholder="수정"
|
|
className="h-7 text-xs"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|