ERP-node/frontend/lib/registry/components/table-search-widget/TableSearchWidgetConfigPane...

345 lines
13 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState, useEffect } from "react";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Plus, X } from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
interface TableSearchWidgetConfigPanelProps {
component?: any; // 레거시 지원
config?: any; // 새 인터페이스
onUpdateProperty?: (property: string, value: any) => void; // 레거시 지원
onChange?: (newConfig: any) => void; // 새 인터페이스
tables?: any[]; // 화면의 테이블 정보
}
interface PresetFilter {
id: string;
columnName: string;
columnLabel: string;
filterType: "text" | "number" | "date" | "select";
width?: number;
2025-12-01 10:36:57 +09:00
multiSelect?: boolean; // 다중선택 여부 (select 타입에서만 사용)
}
export function TableSearchWidgetConfigPanel({
component,
config,
onUpdateProperty,
onChange,
tables = [],
}: TableSearchWidgetConfigPanelProps) {
// 레거시와 새 인터페이스 모두 지원
const currentConfig = config || component?.componentConfig || {};
const updateConfig = onChange || ((key: string, value: any) => {
if (onUpdateProperty) {
onUpdateProperty(`componentConfig.${key}`, value);
}
});
// 첫 번째 테이블의 컬럼 목록 가져오기
const availableColumns = tables.length > 0 && tables[0].columns ? tables[0].columns : [];
// inputType에서 filterType 추출 헬퍼 함수
const getFilterTypeFromInputType = (inputType: string): "text" | "number" | "date" | "select" => {
if (inputType.includes("number") || inputType.includes("decimal") || inputType.includes("integer")) {
return "number";
}
if (inputType.includes("date") || inputType.includes("time")) {
return "date";
}
if (inputType.includes("select") || inputType.includes("dropdown") || inputType.includes("code") || inputType.includes("category")) {
return "select";
}
return "text";
};
const [localAutoSelect, setLocalAutoSelect] = useState(
currentConfig.autoSelectFirstTable ?? true
);
const [localShowSelector, setLocalShowSelector] = useState(
currentConfig.showTableSelector ?? true
);
const [localFilterMode, setLocalFilterMode] = useState<"dynamic" | "preset">(
currentConfig.filterMode ?? "dynamic"
);
const [localPresetFilters, setLocalPresetFilters] = useState<PresetFilter[]>(
currentConfig.presetFilters ?? []
);
useEffect(() => {
setLocalAutoSelect(currentConfig.autoSelectFirstTable ?? true);
setLocalShowSelector(currentConfig.showTableSelector ?? true);
setLocalFilterMode(currentConfig.filterMode ?? "dynamic");
setLocalPresetFilters(currentConfig.presetFilters ?? []);
}, [currentConfig]);
// 설정 업데이트 헬퍼
const handleUpdate = (key: string, value: any) => {
if (onChange) {
// 새 인터페이스: 전체 config 업데이트
onChange({ ...currentConfig, [key]: value });
} else if (onUpdateProperty) {
// 레거시: 개별 속성 업데이트
onUpdateProperty(`componentConfig.${key}`, value);
}
};
// 필터 추가
const addFilter = () => {
const newFilter: PresetFilter = {
id: `filter_${Date.now()}`,
columnName: "",
columnLabel: "",
filterType: "text",
width: 200,
};
const updatedFilters = [...localPresetFilters, newFilter];
setLocalPresetFilters(updatedFilters);
handleUpdate("presetFilters", updatedFilters);
};
// 필터 삭제
const removeFilter = (id: string) => {
const updatedFilters = localPresetFilters.filter((f) => f.id !== id);
setLocalPresetFilters(updatedFilters);
handleUpdate("presetFilters", updatedFilters);
};
// 필터 업데이트
const updateFilter = (id: string, field: keyof PresetFilter, value: any) => {
const updatedFilters = localPresetFilters.map((f) =>
f.id === id ? { ...f, [field]: value } : f
);
setLocalPresetFilters(updatedFilters);
handleUpdate("presetFilters", updatedFilters);
};
return (
<div className="space-y-4">
<div className="space-y-2">
<h3 className="text-sm font-semibold"> </h3>
<p className="text-xs text-muted-foreground">
, , .
</p>
</div>
{/* 첫 번째 테이블 자동 선택 */}
<div className="flex items-center space-x-2">
<Checkbox
id="autoSelectFirstTable"
checked={localAutoSelect}
onCheckedChange={(checked) => {
setLocalAutoSelect(checked as boolean);
handleUpdate("autoSelectFirstTable", checked);
}}
/>
<Label htmlFor="autoSelectFirstTable" className="text-xs sm:text-sm cursor-pointer">
</Label>
</div>
{/* 테이블 선택 드롭다운 표시 */}
<div className="flex items-center space-x-2">
<Checkbox
id="showTableSelector"
checked={localShowSelector}
onCheckedChange={(checked) => {
setLocalShowSelector(checked as boolean);
handleUpdate("showTableSelector", checked);
}}
/>
<Label htmlFor="showTableSelector" className="text-xs sm:text-sm cursor-pointer">
( )
</Label>
</div>
{/* 필터 모드 선택 */}
<div className="space-y-2 border-t pt-4">
<Label className="text-xs sm:text-sm font-medium"> </Label>
<RadioGroup
value={localFilterMode}
onValueChange={(value: "dynamic" | "preset") => {
setLocalFilterMode(value);
handleUpdate("filterMode", value);
}}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="dynamic" id="mode-dynamic" />
<Label htmlFor="mode-dynamic" className="text-xs sm:text-sm cursor-pointer font-normal">
( )
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="preset" id="mode-preset" />
<Label htmlFor="mode-preset" className="text-xs sm:text-sm cursor-pointer font-normal">
( )
</Label>
</div>
</RadioGroup>
</div>
{/* 고정 모드일 때만 필터 설정 UI 표시 */}
{localFilterMode === "preset" && (
<div className="space-y-3 border-t pt-4">
<div className="flex items-center justify-between">
<Label className="text-xs sm:text-sm font-medium"> </Label>
<Button
variant="outline"
size="sm"
onClick={addFilter}
className="h-7 text-xs"
>
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
{localPresetFilters.length === 0 ? (
<div className="rounded-md bg-muted p-3 text-center text-xs text-muted-foreground">
. .
</div>
) : (
<div className="space-y-2">
{localPresetFilters.map((filter) => (
<div
key={filter.id}
className="rounded-md border bg-card p-3 space-y-2"
>
<div className="flex items-center justify-between">
<Label className="text-xs font-medium"> </Label>
<Button
variant="ghost"
size="sm"
onClick={() => removeFilter(filter.id)}
className="h-6 w-6 p-0"
>
<X className="h-3 w-3" />
</Button>
</div>
{/* 컬럼 선택 */}
<div>
<Label className="text-[10px] sm:text-xs mb-1"> </Label>
{availableColumns.length > 0 ? (
<Select
value={filter.columnName}
onValueChange={(value) => {
// 선택된 컬럼 정보 가져오기
const selectedColumn = availableColumns.find(
(col: any) => col.columnName === value
);
// 컬럼명과 라벨 동시 업데이트
const updatedFilters = localPresetFilters.map((f) =>
f.id === filter.id
? {
...f,
columnName: value,
columnLabel: selectedColumn?.columnLabel || value,
filterType: getFilterTypeFromInputType(selectedColumn?.inputType || "text"),
}
: f
);
setLocalPresetFilters(updatedFilters);
handleUpdate("presetFilters", updatedFilters);
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{availableColumns.map((col: any) => (
<SelectItem key={col.columnName} value={col.columnName}>
<div className="flex items-center gap-2">
<span className="font-medium">{col.columnLabel}</span>
<span className="text-muted-foreground text-[10px]">
({col.columnName})
</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
value={filter.columnName}
onChange={(e) => updateFilter(filter.id, "columnName", e.target.value)}
placeholder="예: customer_name"
className="h-7 text-xs"
/>
)}
{filter.columnLabel && (
<p className="text-muted-foreground mt-1 text-[10px]">
: {filter.columnLabel}
</p>
)}
</div>
{/* 필터 타입 */}
<div>
<Label className="text-[10px] sm:text-xs mb-1"> </Label>
<Select
value={filter.filterType}
onValueChange={(value: "text" | "number" | "date" | "select") =>
updateFilter(filter.id, "filterType", value)
}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="text"></SelectItem>
<SelectItem value="number"></SelectItem>
<SelectItem value="date"></SelectItem>
<SelectItem value="select"></SelectItem>
</SelectContent>
</Select>
</div>
{/* 너비 */}
<div>
<Label className="text-[10px] sm:text-xs mb-1"> (px)</Label>
<Input
type="number"
value={filter.width || 200}
onChange={(e) => updateFilter(filter.id, "width", parseInt(e.target.value))}
placeholder="200"
className="h-7 text-xs"
min={100}
max={500}
/>
</div>
</div>
))}
</div>
)}
</div>
)}
<div className="rounded-md bg-muted p-3 text-xs">
<p className="font-medium mb-1">:</p>
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
<li> , , </li>
<li> </li>
{localFilterMode === "dynamic" ? (
<li> </li>
) : (
<li> </li>
)}
</ul>
</div>
</div>
);
}