718 lines
26 KiB
TypeScript
718 lines
26 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "@/components/ui/accordion";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/components/ui/popover";
|
|
import {
|
|
Command,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
} from "@/components/ui/command";
|
|
import { cn } from "@/lib/utils";
|
|
import { tableTypeApi } from "@/lib/api/screen";
|
|
import { TableGroupedConfig, ColumnConfig, LinkedFilterConfig } from "./types";
|
|
import {
|
|
groupHeaderStyleOptions,
|
|
checkboxModeOptions,
|
|
sortDirectionOptions,
|
|
} from "./config";
|
|
import { Trash2, Plus } from "lucide-react";
|
|
|
|
interface TableGroupedConfigPanelProps {
|
|
config: TableGroupedConfig;
|
|
onConfigChange: (newConfig: TableGroupedConfig) => void;
|
|
}
|
|
|
|
/**
|
|
* v2-table-grouped 설정 패널
|
|
*/
|
|
// 테이블 정보 타입
|
|
interface TableInfo {
|
|
tableName: string;
|
|
displayName: string;
|
|
}
|
|
|
|
export function TableGroupedConfigPanel({
|
|
config,
|
|
onConfigChange,
|
|
}: TableGroupedConfigPanelProps) {
|
|
// 테이블 목록 (라벨명 포함)
|
|
const [tables, setTables] = useState<TableInfo[]>([]);
|
|
const [tableColumns, setTableColumns] = useState<ColumnConfig[]>([]);
|
|
const [loadingTables, setLoadingTables] = useState(false);
|
|
const [loadingColumns, setLoadingColumns] = useState(false);
|
|
const [tableSelectOpen, setTableSelectOpen] = useState(false);
|
|
|
|
// 테이블 목록 로드
|
|
useEffect(() => {
|
|
const loadTables = async () => {
|
|
setLoadingTables(true);
|
|
try {
|
|
const tableList = await tableTypeApi.getTables();
|
|
if (tableList && Array.isArray(tableList)) {
|
|
setTables(
|
|
tableList.map((t: any) => ({
|
|
tableName: t.tableName || t.table_name,
|
|
displayName: t.displayName || t.display_name || t.tableName || t.table_name,
|
|
}))
|
|
);
|
|
}
|
|
} catch (err) {
|
|
console.error("테이블 목록 로드 실패:", err);
|
|
} finally {
|
|
setLoadingTables(false);
|
|
}
|
|
};
|
|
loadTables();
|
|
}, []);
|
|
|
|
// 선택된 테이블의 컬럼 로드
|
|
useEffect(() => {
|
|
const tableName = config.useCustomTable
|
|
? config.customTableName
|
|
: config.selectedTable;
|
|
|
|
if (!tableName) {
|
|
setTableColumns([]);
|
|
return;
|
|
}
|
|
|
|
const loadColumns = async () => {
|
|
setLoadingColumns(true);
|
|
try {
|
|
const columns = await tableTypeApi.getColumns(tableName);
|
|
if (columns && Array.isArray(columns)) {
|
|
const cols: ColumnConfig[] = columns.map(
|
|
(col: any, idx: number) => ({
|
|
columnName: col.column_name || col.columnName,
|
|
displayName: col.display_name || col.displayName || col.column_name || col.columnName,
|
|
visible: true,
|
|
sortable: true,
|
|
searchable: false,
|
|
align: "left" as const,
|
|
order: idx,
|
|
})
|
|
);
|
|
setTableColumns(cols);
|
|
|
|
// 컬럼 설정이 없으면 자동 설정
|
|
if (!config.columns || config.columns.length === 0) {
|
|
onConfigChange({ ...config, columns: cols });
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("컬럼 로드 실패:", err);
|
|
} finally {
|
|
setLoadingColumns(false);
|
|
}
|
|
};
|
|
loadColumns();
|
|
}, [config.selectedTable, config.customTableName, config.useCustomTable]);
|
|
|
|
// 설정 업데이트 헬퍼
|
|
const updateConfig = (updates: Partial<TableGroupedConfig>) => {
|
|
onConfigChange({ ...config, ...updates });
|
|
};
|
|
|
|
// 그룹 설정 업데이트 헬퍼
|
|
const updateGroupConfig = (
|
|
updates: Partial<TableGroupedConfig["groupConfig"]>
|
|
) => {
|
|
onConfigChange({
|
|
...config,
|
|
groupConfig: { ...config.groupConfig, ...updates },
|
|
});
|
|
};
|
|
|
|
// 컬럼 가시성 토글
|
|
const toggleColumnVisibility = (columnName: string) => {
|
|
const updatedColumns = (config.columns || []).map((col) =>
|
|
col.columnName === columnName ? { ...col, visible: !col.visible } : col
|
|
);
|
|
updateConfig({ columns: updatedColumns });
|
|
};
|
|
|
|
// 합계 컬럼 토글
|
|
const toggleSumColumn = (columnName: string) => {
|
|
const currentSumCols = config.groupConfig?.summary?.sumColumns || [];
|
|
const newSumCols = currentSumCols.includes(columnName)
|
|
? currentSumCols.filter((c) => c !== columnName)
|
|
: [...currentSumCols, columnName];
|
|
|
|
updateGroupConfig({
|
|
summary: {
|
|
...config.groupConfig?.summary,
|
|
sumColumns: newSumCols,
|
|
},
|
|
});
|
|
};
|
|
|
|
// 연결 필터 추가
|
|
const addLinkedFilter = () => {
|
|
const newFilter: LinkedFilterConfig = {
|
|
sourceComponentId: "",
|
|
sourceField: "value",
|
|
targetColumn: "",
|
|
enabled: true,
|
|
};
|
|
updateConfig({
|
|
linkedFilters: [...(config.linkedFilters || []), newFilter],
|
|
});
|
|
};
|
|
|
|
// 연결 필터 제거
|
|
const removeLinkedFilter = (index: number) => {
|
|
const filters = [...(config.linkedFilters || [])];
|
|
filters.splice(index, 1);
|
|
updateConfig({ linkedFilters: filters });
|
|
};
|
|
|
|
// 연결 필터 업데이트
|
|
const updateLinkedFilter = (
|
|
index: number,
|
|
updates: Partial<LinkedFilterConfig>
|
|
) => {
|
|
const filters = [...(config.linkedFilters || [])];
|
|
filters[index] = { ...filters[index], ...updates };
|
|
updateConfig({ linkedFilters: filters });
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4 p-4">
|
|
<Accordion type="multiple" defaultValue={["table", "group", "display"]}>
|
|
{/* 테이블 설정 */}
|
|
<AccordionItem value="table">
|
|
<AccordionTrigger className="text-sm font-medium">
|
|
테이블 설정
|
|
</AccordionTrigger>
|
|
<AccordionContent className="space-y-3 pt-2">
|
|
{/* 커스텀 테이블 사용 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">커스텀 테이블 사용</Label>
|
|
<Switch
|
|
checked={config.useCustomTable}
|
|
onCheckedChange={(checked) =>
|
|
updateConfig({ useCustomTable: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* 테이블 선택 */}
|
|
{config.useCustomTable ? (
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">커스텀 테이블명</Label>
|
|
<Input
|
|
value={config.customTableName || ""}
|
|
onChange={(e) =>
|
|
updateConfig({ customTableName: e.target.value })
|
|
}
|
|
placeholder="테이블명 입력"
|
|
className="h-8 text-xs"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">테이블 선택</Label>
|
|
<Popover open={tableSelectOpen} onOpenChange={setTableSelectOpen}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
aria-expanded={tableSelectOpen}
|
|
className="h-8 w-full justify-between text-xs"
|
|
disabled={loadingTables}
|
|
>
|
|
{loadingTables ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-3 w-3 animate-spin" />
|
|
로딩 중...
|
|
</>
|
|
) : config.selectedTable ? (
|
|
<span className="truncate">
|
|
{tables.find((t) => t.tableName === config.selectedTable)
|
|
?.displayName || config.selectedTable}
|
|
<span className="ml-1 text-muted-foreground">
|
|
({config.selectedTable})
|
|
</span>
|
|
</span>
|
|
) : (
|
|
"테이블 검색..."
|
|
)}
|
|
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent
|
|
className="p-0"
|
|
style={{ width: "var(--radix-popover-trigger-width)" }}
|
|
align="start"
|
|
>
|
|
<Command
|
|
filter={(value, search) => {
|
|
// 테이블명 또는 라벨명에 검색어가 포함되면 1, 아니면 0
|
|
const lowerSearch = search.toLowerCase();
|
|
if (value.toLowerCase().includes(lowerSearch)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}}
|
|
>
|
|
<CommandInput
|
|
placeholder="테이블명 또는 라벨 검색..."
|
|
className="text-xs"
|
|
/>
|
|
<CommandList>
|
|
<CommandEmpty className="py-2 text-center text-xs">
|
|
테이블을 찾을 수 없습니다.
|
|
</CommandEmpty>
|
|
<CommandGroup>
|
|
{tables.map((table) => (
|
|
<CommandItem
|
|
key={table.tableName}
|
|
value={`${table.displayName} ${table.tableName}`}
|
|
onSelect={() => {
|
|
updateConfig({ selectedTable: table.tableName });
|
|
setTableSelectOpen(false);
|
|
}}
|
|
className="text-xs"
|
|
>
|
|
<Check
|
|
className={cn(
|
|
"mr-2 h-3 w-3",
|
|
config.selectedTable === table.tableName
|
|
? "opacity-100"
|
|
: "opacity-0"
|
|
)}
|
|
/>
|
|
<div className="flex flex-col">
|
|
<span>{table.displayName}</span>
|
|
<span className="text-[10px] text-muted-foreground">
|
|
{table.tableName}
|
|
</span>
|
|
</div>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
)}
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
|
|
{/* 그룹화 설정 */}
|
|
<AccordionItem value="group">
|
|
<AccordionTrigger className="text-sm font-medium">
|
|
그룹화 설정
|
|
</AccordionTrigger>
|
|
<AccordionContent className="space-y-3 pt-2">
|
|
{/* 그룹화 기준 컬럼 */}
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">그룹화 기준 컬럼 *</Label>
|
|
<Select
|
|
value={config.groupConfig?.groupByColumn || ""}
|
|
onValueChange={(value) =>
|
|
updateGroupConfig({ groupByColumn: value })
|
|
}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs">
|
|
<SelectValue placeholder="컬럼 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{tableColumns.map((col) => (
|
|
<SelectItem key={col.columnName} value={col.columnName}>
|
|
{col.displayName || col.columnName}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 그룹 라벨 형식 */}
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">그룹 라벨 형식</Label>
|
|
<Input
|
|
value={config.groupConfig?.groupLabelFormat || "{value}"}
|
|
onChange={(e) =>
|
|
updateGroupConfig({ groupLabelFormat: e.target.value })
|
|
}
|
|
placeholder="{value} ({컬럼명})"
|
|
className="h-8 text-xs"
|
|
/>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
{"{value}"} = 그룹값, {"{컬럼명}"} = 해당 컬럼 값
|
|
</p>
|
|
</div>
|
|
|
|
{/* 기본 펼침 상태 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">기본 펼침 상태</Label>
|
|
<Switch
|
|
checked={config.groupConfig?.defaultExpanded ?? true}
|
|
onCheckedChange={(checked) =>
|
|
updateGroupConfig({ defaultExpanded: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* 그룹 정렬 */}
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">그룹 정렬</Label>
|
|
<Select
|
|
value={config.groupConfig?.sortDirection || "asc"}
|
|
onValueChange={(value: "asc" | "desc") =>
|
|
updateGroupConfig({ sortDirection: value })
|
|
}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{sortDirectionOptions.map((opt) => (
|
|
<SelectItem key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 개수 표시 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">개수 표시</Label>
|
|
<Switch
|
|
checked={config.groupConfig?.summary?.showCount ?? true}
|
|
onCheckedChange={(checked) =>
|
|
updateGroupConfig({
|
|
summary: {
|
|
...config.groupConfig?.summary,
|
|
showCount: checked,
|
|
},
|
|
})
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* 합계 컬럼 */}
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">합계 표시 컬럼</Label>
|
|
<div className="max-h-32 space-y-1 overflow-y-auto rounded border p-2">
|
|
{tableColumns.map((col) => (
|
|
<div
|
|
key={col.columnName}
|
|
className="flex items-center gap-2 text-xs"
|
|
>
|
|
<Checkbox
|
|
id={`sum-${col.columnName}`}
|
|
checked={
|
|
config.groupConfig?.summary?.sumColumns?.includes(
|
|
col.columnName
|
|
) ?? false
|
|
}
|
|
onCheckedChange={() => toggleSumColumn(col.columnName)}
|
|
/>
|
|
<label
|
|
htmlFor={`sum-${col.columnName}`}
|
|
className="cursor-pointer"
|
|
>
|
|
{col.displayName || col.columnName}
|
|
</label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
|
|
{/* 표시 설정 */}
|
|
<AccordionItem value="display">
|
|
<AccordionTrigger className="text-sm font-medium">
|
|
표시 설정
|
|
</AccordionTrigger>
|
|
<AccordionContent className="space-y-3 pt-2">
|
|
{/* 체크박스 표시 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">체크박스 표시</Label>
|
|
<Switch
|
|
checked={config.showCheckbox}
|
|
onCheckedChange={(checked) =>
|
|
updateConfig({ showCheckbox: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* 체크박스 모드 */}
|
|
{config.showCheckbox && (
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">선택 모드</Label>
|
|
<Select
|
|
value={config.checkboxMode || "multi"}
|
|
onValueChange={(value: "single" | "multi") =>
|
|
updateConfig({ checkboxMode: value })
|
|
}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{checkboxModeOptions.map((opt) => (
|
|
<SelectItem key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
)}
|
|
|
|
{/* 그룹 헤더 스타일 */}
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">그룹 헤더 스타일</Label>
|
|
<Select
|
|
value={config.groupHeaderStyle || "default"}
|
|
onValueChange={(value: "default" | "compact" | "card") =>
|
|
updateConfig({ groupHeaderStyle: value })
|
|
}
|
|
>
|
|
<SelectTrigger className="h-8 text-xs">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{groupHeaderStyleOptions.map((opt) => (
|
|
<SelectItem key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 전체 펼치기/접기 버튼 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">펼치기/접기 버튼 표시</Label>
|
|
<Switch
|
|
checked={config.showExpandAllButton ?? true}
|
|
onCheckedChange={(checked) =>
|
|
updateConfig({ showExpandAllButton: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* 행 클릭 가능 */}
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">행 클릭 가능</Label>
|
|
<Switch
|
|
checked={config.rowClickable ?? true}
|
|
onCheckedChange={(checked) =>
|
|
updateConfig({ rowClickable: checked })
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* 최대 높이 */}
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">최대 높이 (px)</Label>
|
|
<Input
|
|
type="number"
|
|
value={config.maxHeight || 600}
|
|
onChange={(e) =>
|
|
updateConfig({ maxHeight: parseInt(e.target.value) || 600 })
|
|
}
|
|
className="h-8 text-xs"
|
|
/>
|
|
</div>
|
|
|
|
{/* 빈 데이터 메시지 */}
|
|
<div className="space-y-1">
|
|
<Label className="text-xs">빈 데이터 메시지</Label>
|
|
<Input
|
|
value={config.emptyMessage || ""}
|
|
onChange={(e) =>
|
|
updateConfig({ emptyMessage: e.target.value })
|
|
}
|
|
placeholder="데이터가 없습니다."
|
|
className="h-8 text-xs"
|
|
/>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
|
|
{/* 컬럼 설정 */}
|
|
<AccordionItem value="columns">
|
|
<AccordionTrigger className="text-sm font-medium">
|
|
컬럼 설정
|
|
</AccordionTrigger>
|
|
<AccordionContent className="space-y-2 pt-2">
|
|
<div className="max-h-48 space-y-1 overflow-y-auto rounded border p-2">
|
|
{(config.columns || tableColumns).map((col) => (
|
|
<div
|
|
key={col.columnName}
|
|
className="flex items-center gap-2 text-xs"
|
|
>
|
|
<Checkbox
|
|
id={`col-${col.columnName}`}
|
|
checked={col.visible !== false}
|
|
onCheckedChange={() =>
|
|
toggleColumnVisibility(col.columnName)
|
|
}
|
|
/>
|
|
<label
|
|
htmlFor={`col-${col.columnName}`}
|
|
className="cursor-pointer"
|
|
>
|
|
{col.displayName || col.columnName}
|
|
</label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
|
|
{/* 연동 설정 */}
|
|
<AccordionItem value="linked">
|
|
<AccordionTrigger className="text-sm font-medium">
|
|
연동 설정
|
|
</AccordionTrigger>
|
|
<AccordionContent className="space-y-3 pt-2">
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label className="text-xs">연결 필터</Label>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={addLinkedFilter}
|
|
className="h-6 text-xs"
|
|
>
|
|
<Plus className="mr-1 h-3 w-3" />
|
|
추가
|
|
</Button>
|
|
</div>
|
|
|
|
{(config.linkedFilters || []).length === 0 ? (
|
|
<p className="text-xs text-muted-foreground">
|
|
연결된 필터가 없습니다.
|
|
</p>
|
|
) : (
|
|
<div className="space-y-2">
|
|
{(config.linkedFilters || []).map((filter, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="space-y-2 rounded border p-2"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-medium">
|
|
필터 #{idx + 1}
|
|
</span>
|
|
<div className="flex items-center gap-2">
|
|
<Switch
|
|
checked={filter.enabled !== false}
|
|
onCheckedChange={(checked) =>
|
|
updateLinkedFilter(idx, { enabled: checked })
|
|
}
|
|
/>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => removeLinkedFilter(idx)}
|
|
className="h-6 w-6 p-0 text-destructive"
|
|
>
|
|
<Trash2 className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">소스 컴포넌트 ID</Label>
|
|
<Input
|
|
value={filter.sourceComponentId}
|
|
onChange={(e) =>
|
|
updateLinkedFilter(idx, {
|
|
sourceComponentId: e.target.value,
|
|
})
|
|
}
|
|
placeholder="예: search-filter-1"
|
|
className="h-7 text-xs"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">소스 필드</Label>
|
|
<Input
|
|
value={filter.sourceField || "value"}
|
|
onChange={(e) =>
|
|
updateLinkedFilter(idx, {
|
|
sourceField: e.target.value,
|
|
})
|
|
}
|
|
placeholder="value"
|
|
className="h-7 text-xs"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Label className="text-[10px]">대상 컬럼</Label>
|
|
<Select
|
|
value={filter.targetColumn}
|
|
onValueChange={(value) =>
|
|
updateLinkedFilter(idx, { targetColumn: value })
|
|
}
|
|
>
|
|
<SelectTrigger className="h-7 text-xs">
|
|
<SelectValue placeholder="컬럼 선택" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{tableColumns.map((col) => (
|
|
<SelectItem
|
|
key={col.columnName}
|
|
value={col.columnName}
|
|
>
|
|
{col.displayName || col.columnName}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<p className="text-[10px] text-muted-foreground">
|
|
다른 컴포넌트(검색필터 등)의 선택 값으로 이 테이블을 필터링합니다.
|
|
</p>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default TableGroupedConfigPanel;
|