docs: V2 컴포넌트 목록 및 v2-table-grouped 개발 상태 업데이트
- V2 컴포넌트 목록에 `v2-table-grouped`를 추가하여 총 18개로 업데이트하였습니다. - `v2-table-grouped`의 구현 완료 상태를 체크리스트에 반영하였으며, 관련 문서화 작업도 완료하였습니다. - 생산계획관리 화면의 신규 컴포넌트 개발 상태를 업데이트하여 `v2-table-grouped`의 완료를 명시하였습니다.
This commit is contained in:
@@ -0,0 +1,717 @@
|
||||
"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;
|
||||
Reference in New Issue
Block a user