ERP-node/frontend/components/v2/config-panels/V2SelectConfigPanel.tsx

369 lines
14 KiB
TypeScript

"use client";
/**
* V2Select 설정 패널
* 통합 선택 컴포넌트의 세부 설정을 관리합니다.
*/
import React, { useState, useEffect, useCallback } from "react";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { Plus, Trash2, Loader2 } from "lucide-react";
import { apiClient } from "@/lib/api/client";
interface ColumnOption {
columnName: string;
columnLabel: string;
}
interface V2SelectConfigPanelProps {
config: Record<string, any>;
onChange: (config: Record<string, any>) => void;
/** 컬럼의 inputType (entity 타입인 경우에만 엔티티 소스 표시) */
inputType?: string;
}
export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config, onChange, inputType }) => {
// 엔티티 타입인지 확인
const isEntityType = inputType === "entity";
// 엔티티 테이블의 컬럼 목록
const [entityColumns, setEntityColumns] = useState<ColumnOption[]>([]);
const [loadingColumns, setLoadingColumns] = useState(false);
// 설정 업데이트 핸들러
const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value });
};
// 엔티티 테이블 변경 시 컬럼 목록 조회
const loadEntityColumns = useCallback(async (tableName: string) => {
if (!tableName) {
setEntityColumns([]);
return;
}
setLoadingColumns(true);
try {
const response = await apiClient.get(`/table-management/tables/${tableName}/columns?size=500`);
const data = response.data.data || response.data;
const columns = data.columns || data || [];
const columnOptions: ColumnOption[] = columns.map((col: any) => {
const name = col.columnName || col.column_name || col.name;
// displayName 우선 사용
const label = col.displayName || col.display_name || col.columnLabel || col.column_label || name;
return {
columnName: name,
columnLabel: label,
};
});
setEntityColumns(columnOptions);
} catch (error) {
console.error("컬럼 목록 조회 실패:", error);
setEntityColumns([]);
} finally {
setLoadingColumns(false);
}
}, []);
// 엔티티 테이블이 변경되면 컬럼 목록 로드
useEffect(() => {
if (config.source === "entity" && config.entityTable) {
loadEntityColumns(config.entityTable);
}
}, [config.source, config.entityTable, loadEntityColumns]);
// 정적 옵션 관리
const options = config.options || [];
const addOption = () => {
const newOptions = [...options, { value: "", label: "" }];
updateConfig("options", newOptions);
};
const updateOptionValue = (index: number, value: string) => {
const newOptions = [...options];
newOptions[index] = { ...newOptions[index], value, label: value };
updateConfig("options", newOptions);
};
const removeOption = (index: number) => {
const newOptions = options.filter((_: any, i: number) => i !== index);
updateConfig("options", newOptions);
};
return (
<div className="space-y-4">
{/* 선택 모드 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Select value={config.mode || "dropdown"} onValueChange={(value) => updateConfig("mode", value)}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="모드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="dropdown"></SelectItem>
<SelectItem value="combobox"> ()</SelectItem>
<SelectItem value="radio"> </SelectItem>
<SelectItem value="check"></SelectItem>
<SelectItem value="tag"> </SelectItem>
<SelectItem value="tagbox"> (+)</SelectItem>
<SelectItem value="toggle"> </SelectItem>
<SelectItem value="swap"> </SelectItem>
</SelectContent>
</Select>
</div>
<Separator />
{/* 데이터 소스 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Select value={config.source || "static"} onValueChange={(value) => updateConfig("source", value)}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="소스 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="static"> </SelectItem>
<SelectItem value="code"> </SelectItem>
{/* 엔티티 타입일 때만 엔티티 옵션 표시 */}
{isEntityType && <SelectItem value="entity"></SelectItem>}
</SelectContent>
</Select>
</div>
{/* 정적 옵션 관리 */}
{(config.source || "static") === "static" && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium"> </Label>
<Button type="button" variant="ghost" size="sm" onClick={addOption} className="h-6 px-2 text-xs">
<Plus className="mr-1 h-3 w-3" />
</Button>
</div>
<div className="max-h-40 space-y-1.5 overflow-y-auto">
{options.map((option: any, index: number) => (
<div key={index} className="flex items-center gap-1.5">
<Input
value={option.value || ""}
onChange={(e) => updateOptionValue(index, e.target.value)}
placeholder={`옵션 ${index + 1}`}
className="h-7 flex-1 text-xs"
/>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeOption(index)}
className="text-destructive h-7 w-7 shrink-0 p-0"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
))}
{options.length === 0 && (
<p className="text-muted-foreground py-2 text-center text-xs"> </p>
)}
</div>
{/* 기본값 설정 */}
{options.length > 0 && (
<div className="mt-3 border-t pt-2">
<Label className="text-xs font-medium"></Label>
<Select
value={config.defaultValue || "_none_"}
onValueChange={(value) => updateConfig("defaultValue", value === "_none_" ? "" : value)}
>
<SelectTrigger className="mt-1 h-8 text-xs">
<SelectValue placeholder="기본값 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="_none_"> </SelectItem>
{options.map((option: any, index: number) => (
<SelectItem key={`default-${index}`} value={option.value || `_idx_${index}`}>
{option.label || option.value || `옵션 ${index + 1}`}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-muted-foreground mt-1 text-[10px]"> </p>
</div>
)}
</div>
)}
{/* 공통 코드 설정 - 테이블 타입 관리에서 설정되므로 정보만 표시 */}
{config.source === "code" && (
<div className="space-y-1">
<Label className="text-xs font-medium"> </Label>
{config.codeGroup ? (
<p className="text-foreground text-sm font-medium">{config.codeGroup}</p>
) : (
<p className="text-xs text-amber-600"> </p>
)}
</div>
)}
{/* 엔티티(참조 테이블) 설정 */}
{config.source === "entity" && (
<div className="space-y-3">
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Input
value={config.entityTable || ""}
readOnly
disabled
placeholder="테이블 타입 관리에서 설정"
className="bg-muted h-8 text-xs"
/>
<p className="text-muted-foreground text-[10px]">
( )
</p>
</div>
{/* 컬럼 로딩 중 표시 */}
{loadingColumns && (
<div className="text-muted-foreground flex items-center gap-2 text-xs">
<Loader2 className="h-3 w-3 animate-spin" />
...
</div>
)}
{/* 컬럼 선택 - 테이블이 설정되어 있고 컬럼 목록이 있는 경우 Select로 표시 */}
<div className="grid grid-cols-2 gap-2">
<div className="space-y-2">
<Label className="text-xs font-medium"> ()</Label>
{entityColumns.length > 0 ? (
<Select
value={config.entityValueColumn || ""}
onValueChange={(value) => updateConfig("entityValueColumn", value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{entityColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
value={config.entityValueColumn || ""}
onChange={(e) => updateConfig("entityValueColumn", e.target.value)}
placeholder="id"
className="h-8 text-xs"
/>
)}
<p className="text-muted-foreground text-[10px]"> </p>
</div>
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
{entityColumns.length > 0 ? (
<Select
value={config.entityLabelColumn || ""}
onValueChange={(value) => updateConfig("entityLabelColumn", value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{entityColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
value={config.entityLabelColumn || ""}
onChange={(e) => updateConfig("entityLabelColumn", e.target.value)}
placeholder="name"
className="h-8 text-xs"
/>
)}
<p className="text-muted-foreground text-[10px]"> </p>
</div>
</div>
{/* 컬럼이 없는 경우 안내 */}
{config.entityTable && !loadingColumns && entityColumns.length === 0 && (
<p className="text-[10px] text-amber-600">
. .
</p>
)}
</div>
)}
<Separator />
{/* 추가 옵션 */}
<div className="space-y-3">
<Label className="text-xs font-medium"> </Label>
<div className="flex items-center space-x-2">
<Checkbox
id="multiple"
checked={config.multiple || false}
onCheckedChange={(checked) => updateConfig("multiple", checked)}
/>
<label htmlFor="multiple" className="text-xs">
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="searchable"
checked={config.searchable || false}
onCheckedChange={(checked) => updateConfig("searchable", checked)}
/>
<label htmlFor="searchable" className="text-xs">
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="allowClear"
checked={config.allowClear !== false}
onCheckedChange={(checked) => updateConfig("allowClear", checked)}
/>
<label htmlFor="allowClear" className="text-xs">
</label>
</div>
</div>
{/* 다중 선택 시 최대 개수 */}
{config.multiple && (
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Input
type="number"
value={config.maxSelect ?? ""}
onChange={(e) => updateConfig("maxSelect", e.target.value ? Number(e.target.value) : undefined)}
placeholder="제한 없음"
min="1"
className="h-8 text-xs"
/>
</div>
)}
</div>
);
};
V2SelectConfigPanel.displayName = "V2SelectConfigPanel";
export default V2SelectConfigPanel;