"use client"; import React, { useEffect, useState } from "react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { StatusCountConfig, StatusCountItem, STATUS_COLOR_MAP } from "./types"; import { tableTypeApi } from "@/lib/api/screen"; import { entityJoinApi, EntityJoinConfig } from "@/lib/api/entityJoin"; import { Plus, Trash2, Loader2, Check, ChevronsUpDown } from "lucide-react"; import { cn } from "@/lib/utils"; export interface StatusCountConfigPanelProps { config: StatusCountConfig; onChange: (config: Partial) => void; } const COLOR_OPTIONS = Object.keys(STATUS_COLOR_MAP); interface SearchableComboboxProps { value: string; onSelect: (value: string) => void; items: Array<{ value: string; label: string; sublabel?: string }>; placeholder: string; searchPlaceholder: string; emptyText: string; disabled?: boolean; loading?: boolean; } const SearchableCombobox: React.FC = ({ value, onSelect, items, placeholder, searchPlaceholder, emptyText, disabled, loading, }) => { const [open, setOpen] = useState(false); if (loading) { return (
로딩중...
); } const selectedItem = items.find((item) => item.value === value); return ( {emptyText} {items.map((item) => ( { onSelect(item.value === value ? "" : item.value); setOpen(false); }} className="text-xs" >
{item.label} {item.sublabel && ( {item.sublabel} )}
))}
); }; export const StatusCountConfigPanel: React.FC = ({ config, onChange, }) => { const items = config.items || []; const [tables, setTables] = useState>([]); const [columns, setColumns] = useState>([]); const [entityJoins, setEntityJoins] = useState([]); const [loadingTables, setLoadingTables] = useState(false); const [loadingColumns, setLoadingColumns] = useState(false); const [loadingJoins, setLoadingJoins] = useState(false); useEffect(() => { const loadTables = async () => { setLoadingTables(true); try { const result = await tableTypeApi.getTables(); setTables( (result || []).map((t: any) => ({ tableName: t.tableName || t.table_name, displayName: t.displayName || t.tableName || t.table_name, })) ); } catch (err) { console.error("테이블 목록 로드 실패:", err); } finally { setLoadingTables(false); } }; loadTables(); }, []); useEffect(() => { if (!config.tableName) { setColumns([]); setEntityJoins([]); return; } const loadColumns = async () => { setLoadingColumns(true); try { const result = await tableTypeApi.getColumns(config.tableName); setColumns( (result || []).map((c: any) => ({ columnName: c.columnName || c.column_name, columnLabel: c.columnLabel || c.column_label || c.displayName || c.columnName || c.column_name, })) ); } catch (err) { console.error("컬럼 목록 로드 실패:", err); } finally { setLoadingColumns(false); } }; const loadEntityJoins = async () => { setLoadingJoins(true); try { const result = await entityJoinApi.getEntityJoinConfigs(config.tableName); setEntityJoins(result?.joinConfigs || []); } catch (err) { console.error("엔티티 조인 설정 로드 실패:", err); setEntityJoins([]); } finally { setLoadingJoins(false); } }; loadColumns(); loadEntityJoins(); }, [config.tableName]); const handleChange = (key: keyof StatusCountConfig, value: any) => { onChange({ [key]: value }); }; const handleItemChange = (index: number, key: keyof StatusCountItem, value: string) => { const newItems = [...items]; newItems[index] = { ...newItems[index], [key]: value }; handleChange("items", newItems); }; const addItem = () => { handleChange("items", [ ...items, { value: "", label: "새 상태", color: "gray" }, ]); }; const removeItem = (index: number) => { handleChange( "items", items.filter((_: StatusCountItem, i: number) => i !== index) ); }; // 상태 컬럼의 카테고리 값 로드 const [statusCategoryValues, setStatusCategoryValues] = useState>([]); const [loadingCategoryValues, setLoadingCategoryValues] = useState(false); useEffect(() => { if (!config.tableName || !config.statusColumn) { setStatusCategoryValues([]); return; } const loadCategoryValues = async () => { setLoadingCategoryValues(true); try { const { apiClient } = await import("@/lib/api/client"); const response = await apiClient.get( `/table-categories/${config.tableName}/${config.statusColumn}/values` ); if (response.data?.success && response.data?.data) { const flatValues: Array<{ value: string; label: string }> = []; const flatten = (items: any[]) => { for (const item of items) { flatValues.push({ value: item.valueCode || item.value_code, label: item.valueLabel || item.value_label, }); if (item.children?.length > 0) flatten(item.children); } }; flatten(response.data.data); setStatusCategoryValues(flatValues); } } catch { setStatusCategoryValues([]); } finally { setLoadingCategoryValues(false); } }; loadCategoryValues(); }, [config.tableName, config.statusColumn]); const tableComboItems = tables.map((t) => ({ value: t.tableName, label: t.displayName, sublabel: t.displayName !== t.tableName ? t.tableName : undefined, })); const columnComboItems = columns.map((c) => ({ value: c.columnName, label: c.columnLabel, sublabel: c.columnLabel !== c.columnName ? c.columnName : undefined, })); const relationComboItems = entityJoins.map((ej) => { const refTableLabel = tables.find((t) => t.tableName === ej.referenceTable)?.displayName || ej.referenceTable; return { value: `${ej.sourceColumn}::${ej.referenceTable}.${ej.referenceColumn}`, label: `${ej.sourceColumn} -> ${refTableLabel}`, sublabel: `${ej.referenceTable}.${ej.referenceColumn}`, }; }); const currentRelationValue = config.relationColumn && config.parentColumn ? relationComboItems.find((item) => { const [srcCol] = item.value.split("::"); return srcCol === config.relationColumn; })?.value || "" : ""; return (
상태별 카운트 설정
handleChange("title", e.target.value)} placeholder="일련번호 현황" className="h-8 text-xs" />
{ onChange({ tableName: v, statusColumn: "", relationColumn: "", parentColumn: "" }); }} items={tableComboItems} placeholder="테이블 선택" searchPlaceholder="테이블명 또는 라벨 검색..." emptyText="테이블을 찾을 수 없습니다" loading={loadingTables} />
handleChange("statusColumn", v)} items={columnComboItems} placeholder={config.tableName ? "상태 컬럼 선택" : "테이블을 먼저 선택"} searchPlaceholder="컬럼명 또는 라벨 검색..." emptyText="컬럼을 찾을 수 없습니다" disabled={!config.tableName} loading={loadingColumns} />
{loadingJoins ? (
로딩중...
) : entityJoins.length > 0 ? ( { if (!v) { onChange({ relationColumn: "", parentColumn: "" }); return; } const [sourceCol, refPart] = v.split("::"); const [refTable, refCol] = refPart.split("."); onChange({ relationColumn: sourceCol, parentColumn: refCol }); }} items={relationComboItems} placeholder="엔티티 관계 선택" searchPlaceholder="관계 검색..." emptyText="엔티티 관계가 없습니다" disabled={!config.tableName} /> ) : (

{config.tableName ? "설정된 엔티티 관계가 없습니다" : "테이블을 먼저 선택하세요"}

)} {config.relationColumn && config.parentColumn && (

자식 FK: {config.relationColumn} {" -> "} 부모 매칭: {config.parentColumn}

)}
{loadingCategoryValues && (
카테고리 값 로딩...
)} {items.map((item: StatusCountItem, i: number) => (
{statusCategoryValues.length > 0 ? ( ) : ( handleItemChange(i, "value", e.target.value)} placeholder="상태값 (예: IN_USE)" className="h-7 text-xs" /> )}
handleItemChange(i, "label", e.target.value)} placeholder="표시 라벨" className="h-7 text-xs" />
))} {!loadingCategoryValues && statusCategoryValues.length === 0 && config.tableName && config.statusColumn && (

카테고리 값이 없습니다. 옵션설정 > 카테고리설정에서 값을 추가하거나 직접 입력하세요.

)}
); };