분할패널 테이블 리스트 구현

This commit is contained in:
kjs
2025-11-11 11:37:26 +09:00
parent 5a5f86092f
commit 532c80a86b
4 changed files with 1662 additions and 102 deletions

View File

@@ -48,6 +48,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const [isLoadingRight, setIsLoadingRight] = useState(false);
const [rightTableColumns, setRightTableColumns] = useState<any[]>([]); // 우측 테이블 컬럼 정보
const [expandedItems, setExpandedItems] = useState<Set<any>>(new Set()); // 펼쳐진 항목들
const [leftColumnLabels, setLeftColumnLabels] = useState<Record<string, string>>({}); // 좌측 컬럼 라벨
const [rightColumnLabels, setRightColumnLabels] = useState<Record<string, string>>({}); // 우측 컬럼 라벨
const { toast } = useToast();
// 추가 모달 상태
@@ -270,6 +272,32 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
[rightTableColumns],
);
// 좌측 테이블 컬럼 라벨 로드
useEffect(() => {
const loadLeftColumnLabels = async () => {
const leftTableName = componentConfig.leftPanel?.tableName;
if (!leftTableName || isDesignMode) return;
try {
const columnsResponse = await tableTypeApi.getColumns(leftTableName);
const labels: Record<string, string> = {};
columnsResponse.forEach((col: any) => {
const columnName = col.columnName || col.column_name;
const label = col.columnLabel || col.column_label || col.displayName || columnName;
if (columnName) {
labels[columnName] = label;
}
});
setLeftColumnLabels(labels);
console.log("✅ 좌측 컬럼 라벨 로드:", labels);
} catch (error) {
console.error("좌측 테이블 컬럼 라벨 로드 실패:", error);
}
};
loadLeftColumnLabels();
}, [componentConfig.leftPanel?.tableName, isDesignMode]);
// 우측 테이블 컬럼 정보 로드
useEffect(() => {
const loadRightTableColumns = async () => {
@@ -279,6 +307,18 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
try {
const columnsResponse = await tableTypeApi.getColumns(rightTableName);
setRightTableColumns(columnsResponse || []);
// 우측 컬럼 라벨도 함께 로드
const labels: Record<string, string> = {};
columnsResponse.forEach((col: any) => {
const columnName = col.columnName || col.column_name;
const label = col.columnLabel || col.column_label || col.displayName || columnName;
if (columnName) {
labels[columnName] = label;
}
});
setRightColumnLabels(labels);
console.log("✅ 우측 컬럼 라벨 로드:", labels);
} catch (error) {
console.error("우측 테이블 컬럼 정보 로드 실패:", error);
}
@@ -784,46 +824,157 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
)}
</CardHeader>
<CardContent className="flex-1 overflow-auto p-4">
{/* 좌측 데이터 목록 */}
<div className="space-y-1">
{isDesignMode ? (
// 디자인 모드: 샘플 데이터
<>
<div
onClick={() => handleLeftItemSelect({ id: 1, name: "항목 1" })}
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
selectedLeftItem?.id === 1 ? "bg-primary/10 text-primary" : ""
}`}
>
<div className="font-medium"> 1</div>
<div className="text-muted-foreground text-xs"> </div>
{/* 좌측 데이터 목록/테이블 */}
{componentConfig.leftPanel?.displayMode === "table" ? (
// 테이블 모드
<div className="w-full">
{isDesignMode ? (
// 디자인 모드: 샘플 테이블
<div className="overflow-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase"> 1</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase"> 2</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase"> 3</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
<tr className="hover:bg-gray-50 cursor-pointer">
<td className="whitespace-nowrap px-3 py-2 text-sm"> 1-1</td>
<td className="whitespace-nowrap px-3 py-2 text-sm"> 1-2</td>
<td className="whitespace-nowrap px-3 py-2 text-sm"> 1-3</td>
</tr>
<tr className="hover:bg-gray-50 cursor-pointer">
<td className="whitespace-nowrap px-3 py-2 text-sm"> 2-1</td>
<td className="whitespace-nowrap px-3 py-2 text-sm"> 2-2</td>
<td className="whitespace-nowrap px-3 py-2 text-sm"> 2-3</td>
</tr>
</tbody>
</table>
</div>
<div
onClick={() => handleLeftItemSelect({ id: 2, name: "항목 2" })}
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
selectedLeftItem?.id === 2 ? "bg-primary/10 text-primary" : ""
}`}
>
<div className="font-medium"> 2</div>
<div className="text-muted-foreground text-xs"> </div>
) : isLoadingLeft ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="text-primary h-6 w-6 animate-spin" />
<span className="text-muted-foreground ml-2 text-sm"> ...</span>
</div>
<div
onClick={() => handleLeftItemSelect({ id: 3, name: "항목 3" })}
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
selectedLeftItem?.id === 3 ? "bg-primary/10 text-primary" : ""
}`}
>
<div className="font-medium"> 3</div>
<div className="text-muted-foreground text-xs"> </div>
) : (
(() => {
const filteredData = leftSearchQuery
? leftData.filter((item) => {
const searchLower = leftSearchQuery.toLowerCase();
return Object.entries(item).some(([key, value]) => {
if (value === null || value === undefined) return false;
return String(value).toLowerCase().includes(searchLower);
});
})
: leftData;
const displayColumns = componentConfig.leftPanel?.columns || [];
const columnsToShow = displayColumns.length > 0
? displayColumns.map(col => ({
...col,
label: leftColumnLabels[col.name] || col.label || col.name
}))
: Object.keys(filteredData[0] || {}).filter(key => key !== 'children' && key !== 'level').slice(0, 5).map(key => ({
name: key,
label: leftColumnLabels[key] || key,
width: 150,
align: "left" as const
}));
return (
<div className="overflow-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="sticky top-0 bg-gray-50 z-10">
<tr>
{columnsToShow.map((col, idx) => (
<th
key={idx}
className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
style={{ width: col.width ? `${col.width}px` : 'auto', textAlign: col.align || "left" }}
>
{col.label}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{filteredData.map((item, idx) => {
const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || 'id';
const itemId = item[sourceColumn] || item.id || item.ID || idx;
const isSelected = selectedLeftItem && (selectedLeftItem[sourceColumn] === itemId || selectedLeftItem === item);
return (
<tr
key={itemId}
onClick={() => handleLeftItemSelect(item)}
className={`hover:bg-accent cursor-pointer transition-colors ${
isSelected ? "bg-primary/10" : ""
}`}
>
{columnsToShow.map((col, colIdx) => (
<td
key={colIdx}
className="whitespace-nowrap px-3 py-2 text-sm text-gray-900"
style={{ textAlign: col.align || "left" }}
>
{item[col.name] !== null && item[col.name] !== undefined
? String(item[col.name])
: "-"}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
);
})()
)}
</div>
) : (
// 목록 모드 (기존)
<div className="space-y-1">
{isDesignMode ? (
// 디자인 모드: 샘플 데이터
<>
<div
onClick={() => handleLeftItemSelect({ id: 1, name: "항목 1" })}
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
selectedLeftItem?.id === 1 ? "bg-primary/10 text-primary" : ""
}`}
>
<div className="font-medium"> 1</div>
<div className="text-muted-foreground text-xs"> </div>
</div>
<div
onClick={() => handleLeftItemSelect({ id: 2, name: "항목 2" })}
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
selectedLeftItem?.id === 2 ? "bg-primary/10 text-primary" : ""
}`}
>
<div className="font-medium"> 2</div>
<div className="text-muted-foreground text-xs"> </div>
</div>
<div
onClick={() => handleLeftItemSelect({ id: 3, name: "항목 3" })}
className={`hover:bg-accent cursor-pointer rounded-md p-3 transition-colors ${
selectedLeftItem?.id === 3 ? "bg-primary/10 text-primary" : ""
}`}
>
<div className="font-medium"> 3</div>
<div className="text-muted-foreground text-xs"> </div>
</div>
</>
) : isLoadingLeft ? (
// 로딩 중
<div className="flex items-center justify-center py-8">
<Loader2 className="text-primary h-6 w-6 animate-spin" />
<span className="text-muted-foreground ml-2 text-sm"> ...</span>
</div>
</>
) : isLoadingLeft ? (
// 로딩 중
<div className="flex items-center justify-center py-8">
<Loader2 className="text-primary h-6 w-6 animate-spin" />
<span className="text-muted-foreground ml-2 text-sm"> ...</span>
</div>
) : (
) : (
(() => {
// 검색 필터링 (클라이언트 사이드)
const filteredLeftData = leftSearchQuery
@@ -1001,7 +1152,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
);
})()
)}
</div>
</div>
)}
</CardContent>
</Card>
</div>
@@ -1081,6 +1233,107 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
})
: rightData;
// 테이블 모드 체크
const isTableMode = componentConfig.rightPanel?.displayMode === "table";
if (isTableMode) {
// 테이블 모드 렌더링
const displayColumns = componentConfig.rightPanel?.columns || [];
const columnsToShow = displayColumns.length > 0
? displayColumns.map(col => ({
...col,
label: rightColumnLabels[col.name] || col.label || col.name
}))
: Object.keys(filteredData[0] || {}).filter(key => !key.toLowerCase().includes("password")).slice(0, 5).map(key => ({
name: key,
label: rightColumnLabels[key] || key,
width: 150,
align: "left" as const
}));
return (
<div className="w-full">
<div className="mb-2 text-xs text-muted-foreground">
{filteredData.length}
{rightSearchQuery && filteredData.length !== rightData.length && (
<span className="ml-1 text-primary">( {rightData.length} )</span>
)}
</div>
<div className="overflow-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="sticky top-0 bg-gray-50 z-10">
<tr>
{columnsToShow.map((col, idx) => (
<th
key={idx}
className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
style={{ width: col.width ? `${col.width}px` : 'auto', textAlign: col.align || "left" }}
>
{col.label}
</th>
))}
{!isDesignMode && (
<th className="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase"></th>
)}
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
{filteredData.map((item, idx) => {
const itemId = item.id || item.ID || idx;
return (
<tr
key={itemId}
className="hover:bg-accent transition-colors"
>
{columnsToShow.map((col, colIdx) => (
<td
key={colIdx}
className="whitespace-nowrap px-3 py-2 text-sm text-gray-900"
style={{ textAlign: col.align || "left" }}
>
{item[col.name] !== null && item[col.name] !== undefined
? String(item[col.name])
: "-"}
</td>
))}
{!isDesignMode && (
<td className="whitespace-nowrap px-3 py-2 text-right text-sm">
<div className="flex justify-end gap-1">
<button
onClick={(e) => {
e.stopPropagation();
handleEditClick("right", item);
}}
className="rounded p-1 hover:bg-gray-200 transition-colors"
title="수정"
>
<Pencil className="h-4 w-4 text-gray-600" />
</button>
<button
onClick={(e) => {
e.stopPropagation();
handleDeleteClick("right", item);
}}
className="rounded p-1 hover:bg-red-100 transition-colors"
title="삭제"
>
<Trash2 className="h-4 w-4 text-red-600" />
</button>
</div>
</td>
)}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
// 목록 모드 (기존)
return filteredData.length > 0 ? (
<div className="space-y-2">
<div className="mb-2 text-xs text-muted-foreground">

View File

@@ -353,6 +353,32 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
</div>
</div>
<div className="space-y-2">
<Label> </Label>
<Select
value={config.leftPanel?.displayMode || "list"}
onValueChange={(value: "list" | "table") => updateLeftPanel({ displayMode: value })}
>
<SelectTrigger className="bg-white">
<SelectValue placeholder="표시 모드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="list">
<div className="flex flex-col">
<span className="font-medium"> (LIST)</span>
<span className="text-xs text-gray-500"> ()</span>
</div>
</SelectItem>
<SelectItem value="table">
<div className="flex flex-col">
<span className="font-medium"> (TABLE)</span>
<span className="text-xs text-gray-500"> </span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center justify-between">
<Label> </Label>
<Switch
@@ -670,6 +696,185 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
</div>
)}
{/* 좌측 패널 표시 컬럼 설정 */}
<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-sm font-semibold"> </Label>
<Button
size="sm"
variant="outline"
onClick={() => {
const currentColumns = config.leftPanel?.columns || [];
const newColumns = [
...currentColumns,
{ name: "", label: "", width: 100 },
];
updateLeftPanel({ columns: newColumns });
}}
className="h-7 text-xs"
disabled={!config.leftPanel?.tableName && !screenTableName}
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
<p className="text-xs text-gray-600">
. .
</p>
{/* 선택된 컬럼 목록 */}
<div className="space-y-2">
{(config.leftPanel?.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>
) : (
(config.leftPanel?.columns || []).map((col, index) => {
const isTableMode = config.leftPanel?.displayMode === "table";
return (
<div
key={index}
className="space-y-2 rounded-md border bg-white p-2"
>
<div className="flex items-center gap-2">
<div className="flex-1">
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="h-8 w-full justify-between text-xs"
>
{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.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
const newColumns = [...(config.leftPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
name: value,
label: column.columnLabel || value,
};
updateLeftPanel({ columns: newColumns });
}}
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>
<Button
size="sm"
variant="ghost"
onClick={() => {
const newColumns = (config.leftPanel?.columns || []).filter(
(_, i) => i !== index
);
updateLeftPanel({ columns: newColumns });
}}
className="h-8 w-8 p-0"
>
<X className="h-3 w-3" />
</Button>
</div>
{/* 테이블 모드 전용 옵션 */}
{isTableMode && (
<div className="grid grid-cols-3 gap-2 pt-1">
<div className="space-y-1">
<Label className="text-[10px] text-gray-600"> (px)</Label>
<Input
type="number"
min="50"
value={col.width || 100}
onChange={(e) => {
const newColumns = [...(config.leftPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
width: parseInt(e.target.value) || 100,
};
updateLeftPanel({ columns: newColumns });
}}
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-[10px] text-gray-600"></Label>
<Select
value={col.align || "left"}
onValueChange={(value: "left" | "center" | "right") => {
const newColumns = [...(config.leftPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
align: value,
};
updateLeftPanel({ columns: newColumns });
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="left"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="right"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-end">
<label className="flex h-7 items-center gap-1 text-[10px] cursor-pointer">
<input
type="checkbox"
checked={col.sortable ?? false}
onChange={(e) => {
const newColumns = [...(config.leftPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
sortable: e.target.checked,
};
updateLeftPanel({ columns: newColumns });
}}
className="h-3 w-3"
/>
</label>
</div>
</div>
)}
</div>
);
})
)}
</div>
</div>
{/* 좌측 패널 추가 모달 컬럼 설정 */}
{config.leftPanel?.showAdd && (
<div className="space-y-3 rounded-lg border border-purple-200 bg-purple-50 p-3">
@@ -895,6 +1100,32 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
</div>
)}
<div className="space-y-2">
<Label> </Label>
<Select
value={config.rightPanel?.displayMode || "list"}
onValueChange={(value: "list" | "table") => updateRightPanel({ displayMode: value })}
>
<SelectTrigger className="bg-white">
<SelectValue placeholder="표시 모드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="list">
<div className="flex flex-col">
<span className="font-medium"> (LIST)</span>
<span className="text-xs text-gray-500"> ()</span>
</div>
</SelectItem>
<SelectItem value="table">
<div className="flex flex-col">
<span className="font-medium"> (TABLE)</span>
<span className="text-xs text-gray-500"> </span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
{/* 컬럼 매핑 - 조인 모드에서만 표시 */}
{relationshipType !== "detail" && (
<div className="space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
@@ -1057,75 +1288,145 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
</p>
</div>
) : (
(config.rightPanel?.columns || []).map((col, index) => (
(config.rightPanel?.columns || []).map((col, index) => {
const isTableMode = config.rightPanel?.displayMode === "table";
return (
<div
key={index}
className="flex items-center gap-2 rounded-md border bg-white p-2"
className="space-y-2 rounded-md border bg-white p-2"
>
<div className="flex-1">
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="h-8 w-full justify-between text-xs"
>
{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">
{rightTableColumns.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
const newColumns = [...(config.rightPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
name: value,
label: column.columnLabel || value,
};
updateRightPanel({ columns: newColumns });
}}
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 className="flex items-center gap-2">
<div className="flex-1">
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="h-8 w-full justify-between text-xs"
>
{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">
{rightTableColumns.map((column) => (
<CommandItem
key={column.columnName}
value={column.columnName}
onSelect={(value) => {
const newColumns = [...(config.rightPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
name: value,
label: column.columnLabel || value,
};
updateRightPanel({ columns: newColumns });
}}
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>
<Button
size="sm"
variant="ghost"
onClick={() => {
const newColumns = (config.rightPanel?.columns || []).filter(
(_, i) => i !== index
);
updateRightPanel({ columns: newColumns });
}}
className="h-8 w-8 p-0"
>
<X className="h-3 w-3" />
</Button>
</div>
<Button
size="sm"
variant="ghost"
onClick={() => {
const newColumns = (config.rightPanel?.columns || []).filter(
(_, i) => i !== index
);
updateRightPanel({ columns: newColumns });
}}
className="h-8 w-8 p-0"
>
<X className="h-3 w-3" />
</Button>
{/* 테이블 모드 전용 옵션 */}
{isTableMode && (
<div className="grid grid-cols-3 gap-2 pt-1">
<div className="space-y-1">
<Label className="text-[10px] text-gray-600"> (px)</Label>
<Input
type="number"
min="50"
value={col.width || 100}
onChange={(e) => {
const newColumns = [...(config.rightPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
width: parseInt(e.target.value) || 100,
};
updateRightPanel({ columns: newColumns });
}}
className="h-7 text-xs"
/>
</div>
<div className="space-y-1">
<Label className="text-[10px] text-gray-600"></Label>
<Select
value={col.align || "left"}
onValueChange={(value: "left" | "center" | "right") => {
const newColumns = [...(config.rightPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
align: value,
};
updateRightPanel({ columns: newColumns });
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="left"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="right"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-end">
<label className="flex h-7 items-center gap-1 text-[10px] cursor-pointer">
<input
type="checkbox"
checked={col.sortable ?? false}
onChange={(e) => {
const newColumns = [...(config.rightPanel?.columns || [])];
newColumns[index] = {
...newColumns[index],
sortable: e.target.checked,
};
updateRightPanel({ columns: newColumns });
}}
className="h-3 w-3"
/>
</label>
</div>
</div>
)}
</div>
))
);
})
)}
</div>
</div>

View File

@@ -8,6 +8,7 @@ export interface SplitPanelLayoutConfig {
title: string;
tableName?: string; // 데이터베이스 테이블명
dataSource?: string; // API 엔드포인트
displayMode?: "list" | "table"; // 표시 모드: 목록 또는 테이블
showSearch?: boolean;
showAdd?: boolean;
showEdit?: boolean; // 수정 버튼
@@ -16,6 +17,8 @@ export interface SplitPanelLayoutConfig {
name: string;
label: string;
width?: number;
sortable?: boolean; // 정렬 가능 여부 (테이블 모드)
align?: "left" | "center" | "right"; // 정렬 (테이블 모드)
}>;
// 추가 모달에서 입력받을 컬럼 설정
addModalColumns?: Array<{
@@ -38,6 +41,17 @@ export interface SplitPanelLayoutConfig {
// 현재 항목의 어떤 컬럼 값을 parentColumn에 넣을지 (예: dept_code)
sourceColumn: string;
};
// 테이블 모드 설정
tableConfig?: {
showCheckbox?: boolean; // 체크박스 표시 여부
showRowNumber?: boolean; // 행 번호 표시 여부
rowHeight?: number; // 행 높이
headerHeight?: number; // 헤더 높이
striped?: boolean; // 줄무늬 배경
bordered?: boolean; // 테두리 표시
hoverable?: boolean; // 호버 효과
stickyHeader?: boolean; // 헤더 고정
};
};
// 우측 패널 설정
@@ -45,6 +59,7 @@ export interface SplitPanelLayoutConfig {
title: string;
tableName?: string;
dataSource?: string;
displayMode?: "list" | "table"; // 표시 모드: 목록 또는 테이블
showSearch?: boolean;
showAdd?: boolean;
showEdit?: boolean; // 수정 버튼
@@ -53,6 +68,8 @@ export interface SplitPanelLayoutConfig {
name: string;
label: string;
width?: number;
sortable?: boolean; // 정렬 가능 여부 (테이블 모드)
align?: "left" | "center" | "right"; // 정렬 (테이블 모드)
}>;
// 추가 모달에서 입력받을 컬럼 설정
addModalColumns?: Array<{
@@ -76,6 +93,18 @@ export interface SplitPanelLayoutConfig {
leftPanelColumn?: string; // 좌측 패널의 어떤 컬럼값을 가져올지
targetColumn?: string; // targetTable의 어떤 컬럼에 넣을지
};
// 테이블 모드 설정
tableConfig?: {
showCheckbox?: boolean; // 체크박스 표시 여부
showRowNumber?: boolean; // 행 번호 표시 여부
rowHeight?: number; // 행 높이
headerHeight?: number; // 헤더 높이
striped?: boolean; // 줄무늬 배경
bordered?: boolean; // 테두리 표시
hoverable?: boolean; // 호버 효과
stickyHeader?: boolean; // 헤더 고정
};
};
// 레이아웃 설정