"use client"; import React, { useState, useMemo } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Card, CardContent } from "@/components/ui/card"; import { Plus, X } from "lucide-react"; import { SelectedItemsDetailInputConfig, AdditionalFieldDefinition } from "./types"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Check, ChevronsUpDown } from "lucide-react"; import { cn } from "@/lib/utils"; export interface SelectedItemsDetailInputConfigPanelProps { config: SelectedItemsDetailInputConfig; onChange: (config: Partial) => void; sourceTableColumns?: Array<{ columnName: string; columnLabel?: string; dataType?: string }>; // πŸ†• 원본 ν…Œμ΄λΈ” 컬럼 targetTableColumns?: Array<{ columnName: string; columnLabel?: string; dataType?: string }>; // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” 컬럼 allTables?: Array<{ tableName: string; displayName?: string }>; screenTableName?: string; // πŸ†• ν˜„μž¬ ν™”λ©΄μ˜ ν…Œμ΄λΈ”λͺ… (μžλ™ μ„€μ •μš©) onSourceTableChange?: (tableName: string) => void; // πŸ†• 원본 ν…Œμ΄λΈ” λ³€κ²½ 콜백 onTargetTableChange?: (tableName: string) => void; // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” λ³€κ²½ 콜백 (κΈ°μ‘΄ onTableChange λŒ€μ²΄) } /** * SelectedItemsDetailInput μ„€μ • νŒ¨λ„ * μ»΄ν¬λ„ŒνŠΈμ˜ 섀정값듀을 νŽΈμ§‘ν•  수 μžˆλŠ” UI 제곡 */ export const SelectedItemsDetailInputConfigPanel: React.FC = ({ config, onChange, sourceTableColumns = [], // πŸ†• 원본 ν…Œμ΄λΈ” 컬럼 targetTableColumns = [], // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” 컬럼 allTables = [], screenTableName, // πŸ†• ν˜„μž¬ ν™”λ©΄μ˜ ν…Œμ΄λΈ”λͺ… onSourceTableChange, // πŸ†• 원본 ν…Œμ΄λΈ” λ³€κ²½ 콜백 onTargetTableChange, // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” λ³€κ²½ 콜백 }) => { const [localFields, setLocalFields] = useState(config.additionalFields || []); const [displayColumns, setDisplayColumns] = useState>(config.displayColumns || []); const [fieldPopoverOpen, setFieldPopoverOpen] = useState>({}); // πŸ†• 원본 ν…Œμ΄λΈ” 선택 μƒνƒœ const [sourceTableSelectOpen, setSourceTableSelectOpen] = useState(false); const [sourceTableSearchValue, setSourceTableSearchValue] = useState(""); // πŸ†• λŒ€μƒ ν…Œμ΄λΈ” 선택 μƒνƒœ (κΈ°μ‘΄ tableSelectOpen) const [tableSelectOpen, setTableSelectOpen] = useState(false); const [tableSearchValue, setTableSearchValue] = useState(""); // πŸ†• 초기 λ‘œλ“œ μ‹œ screenTableName을 targetTable둜 μžλ™ μ„€μ • React.useEffect(() => { if (screenTableName && !config.targetTable) { console.log("✨ ν˜„μž¬ ν™”λ©΄ ν…Œμ΄λΈ”μ„ μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ”λ‘œ μžλ™ μ„€μ •:", screenTableName); handleChange("targetTable", screenTableName); // μ»¬λŸΌλ„ μžλ™ λ‘œλ“œ if (onTargetTableChange) { onTargetTableChange(screenTableName); } } }, [screenTableName]); // config.targetTable은 μ˜μ‘΄μ„±μ—μ„œ μ œμ™Έ (ν•œ 번만 μ‹€ν–‰) const handleChange = (key: keyof SelectedItemsDetailInputConfig, value: any) => { onChange({ [key]: value }); }; const handleFieldsChange = (fields: AdditionalFieldDefinition[]) => { setLocalFields(fields); handleChange("additionalFields", fields); }; const handleDisplayColumnsChange = (columns: Array<{ name: string; label: string; width?: string }>) => { setDisplayColumns(columns); handleChange("displayColumns", columns); }; // ν•„λ“œ μΆ”κ°€ const addField = () => { const newField: AdditionalFieldDefinition = { name: `field_${localFields.length + 1}`, label: `ν•„λ“œ ${localFields.length + 1}`, type: "text", }; handleFieldsChange([...localFields, newField]); }; // ν•„λ“œ 제거 const removeField = (index: number) => { handleFieldsChange(localFields.filter((_, i) => i !== index)); }; // ν•„λ“œ μˆ˜μ • const updateField = (index: number, updates: Partial) => { const newFields = [...localFields]; newFields[index] = { ...newFields[index], ...updates }; handleFieldsChange(newFields); }; // ν‘œμ‹œ 컬럼 μΆ”κ°€ const addDisplayColumn = (columnName: string, columnLabel: string) => { if (!displayColumns.some(col => col.name === columnName)) { handleDisplayColumnsChange([...displayColumns, { name: columnName, label: columnLabel }]); } }; // ν‘œμ‹œ 컬럼 제거 const removeDisplayColumn = (columnName: string) => { handleDisplayColumnsChange(displayColumns.filter((col) => col.name !== columnName)); }; // πŸ†• ν‘œμ‹œ 컬럼용: 원본 ν…Œμ΄λΈ”μ—μ„œ μ‚¬μš©λ˜μ§€ μ•Šμ€ 컬럼 λͺ©λ‘ const availableColumns = useMemo(() => { const usedColumns = new Set([...displayColumns.map(c => c.name), ...localFields.map((f) => f.name)]); return sourceTableColumns.filter((col) => !usedColumns.has(col.columnName)); }, [sourceTableColumns, displayColumns, localFields]); // πŸ†• μΆ”κ°€ μž…λ ₯ ν•„λ“œμš©: λŒ€μƒ ν…Œμ΄λΈ”μ—μ„œ μ‚¬μš©λ˜μ§€ μ•Šμ€ 컬럼 λͺ©λ‘ const availableTargetColumns = useMemo(() => { const usedColumns = new Set([...displayColumns.map(c => c.name), ...localFields.map((f) => f.name)]); return targetTableColumns.filter((col) => !usedColumns.has(col.columnName)); }, [targetTableColumns, displayColumns, localFields]); // πŸ†• 원본 ν…Œμ΄λΈ” 필터링 const filteredSourceTables = useMemo(() => { if (!sourceTableSearchValue) return allTables; const searchLower = sourceTableSearchValue.toLowerCase(); return allTables.filter( (table) => table.tableName.toLowerCase().includes(searchLower) || table.displayName?.toLowerCase().includes(searchLower), ); }, [allTables, sourceTableSearchValue]); // πŸ†• μ„ νƒλœ 원본 ν…Œμ΄λΈ” ν‘œμ‹œλͺ… const selectedSourceTableLabel = useMemo(() => { if (!config.sourceTable) return "원본 ν…Œμ΄λΈ”μ„ μ„ νƒν•˜μ„Έμš”"; const table = allTables.find((t) => t.tableName === config.sourceTable); return table ? table.displayName || table.tableName : config.sourceTable; }, [config.sourceTable, allTables]); // λŒ€μƒ ν…Œμ΄λΈ” 필터링 const filteredTables = useMemo(() => { if (!tableSearchValue) return allTables; const searchLower = tableSearchValue.toLowerCase(); return allTables.filter( (table) => table.tableName.toLowerCase().includes(searchLower) || table.displayName?.toLowerCase().includes(searchLower), ); }, [allTables, tableSearchValue]); // μ„ νƒλœ λŒ€μƒ ν…Œμ΄λΈ” ν‘œμ‹œλͺ… const selectedTableLabel = useMemo(() => { if (!config.targetTable) return "μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ”μ„ μ„ νƒν•˜μ„Έμš”"; const table = allTables.find((t) => t.tableName === config.targetTable); return table ? table.displayName || table.tableName : config.targetTable; }, [config.targetTable, allTables]); return (
{/* 데이터 μ†ŒμŠ€ ID */}
handleChange("dataSourceId", e.target.value)} placeholder="λΉ„μ›Œλ‘λ©΄ URL νŒŒλΌλ―Έν„°μ—μ„œ μžλ™ μ„€μ •" className="h-7 text-xs sm:h-8 sm:text-sm" />

✨ URL νŒŒλΌλ―Έν„°μ—μ„œ μžλ™μœΌλ‘œ κ°€μ Έμ˜΅λ‹ˆλ‹€ (Button이 전달)

ν…ŒμŠ€νŠΈμš©μœΌλ‘œ 직접 μž…λ ₯ν•˜λ €λ©΄ ν…Œμ΄λΈ”λͺ…을 μž…λ ₯ν•˜μ„Έμš”

{/* πŸ†• 원본 데이터 ν…Œμ΄λΈ” */}
ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. {filteredSourceTables.map((table) => ( { handleChange("sourceTable", currentValue); setSourceTableSelectOpen(false); setSourceTableSearchValue(""); if (onSourceTableChange) { onSourceTableChange(currentValue); } }} className="text-xs sm:text-sm" > {table.displayName || table.tableName} ))}

이전 ν™”λ©΄μ—μ„œ 전달받은 λ°μ΄ν„°μ˜ 원본 ν…Œμ΄λΈ” (예: item_info)

{/* μ €μž₯ λŒ€μƒ ν…Œμ΄λΈ” */}
ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. {filteredTables.map((table) => ( { handleChange("targetTable", currentValue); setTableSelectOpen(false); setTableSearchValue(""); if (onTargetTableChange) { onTargetTableChange(currentValue); } }} className="text-xs sm:text-sm" > {table.displayName || table.tableName} ))}

μ΅œμ’… 데이터λ₯Ό μ €μž₯ν•  ν…Œμ΄λΈ”

{/* ν‘œμ‹œν•  원본 데이터 컬럼 */}
{displayColumns.map((col, index) => (
))} μ‚¬μš© κ°€λŠ₯ν•œ 컬럼이 μ—†μŠ΅λ‹ˆλ‹€. {availableColumns.map((column) => ( addDisplayColumn(column.columnName, column.columnLabel || column.columnName)} className="text-xs sm:text-sm" >
{column.columnLabel || column.columnName}
{column.dataType &&
{column.dataType}
}
))}

전달받은 원본 데이터 쀑 화면에 ν‘œμ‹œν•  컬럼 (예: ν’ˆλͺ©μ½”λ“œ, ν’ˆλͺ©λͺ…)

{/* μΆ”κ°€ μž…λ ₯ ν•„λ“œ μ •μ˜ */}
{localFields.map((field, index) => (
ν•„λ“œ {index + 1}
setFieldPopoverOpen({ ...fieldPopoverOpen, [index]: open })} > μ‚¬μš© κ°€λŠ₯ν•œ 컬럼이 μ—†μŠ΅λ‹ˆλ‹€. {availableTargetColumns.map((column) => ( { updateField(index, { name: column.columnName, label: column.columnLabel || column.columnName, inputType: column.inputType || "text", // πŸ†• inputType 포함 codeCategory: column.codeCategory, // πŸ†• codeCategory 포함 }); setFieldPopoverOpen({ ...fieldPopoverOpen, [index]: false }); }} className="text-[10px] sm:text-xs" >
{column.columnLabel}
{column.columnName}
))}
updateField(index, { label: e.target.value })} placeholder="ν•„λ“œ 라벨" className="h-6 w-full text-[10px] sm:h-7 sm:text-xs" />

ν…Œμ΄λΈ” νƒ€μž…κ΄€λ¦¬μ—μ„œ μžλ™ 섀정됨

updateField(index, { placeholder: e.target.value })} placeholder="μž…λ ₯ μ•ˆλ‚΄" className="h-6 w-full text-[10px] sm:h-7 sm:text-xs" />
updateField(index, { required: checked as boolean })} />
))}
{/* λ ˆμ΄μ•„μ›ƒ μ„€μ • */}

{config.layout === "grid" ? "ν–‰ λ‹¨μœ„λ‘œ 데이터λ₯Ό ν‘œμ‹œν•©λ‹ˆλ‹€" : "각 ν•­λͺ©μ„ μΉ΄λ“œλ‘œ ν‘œμ‹œν•©λ‹ˆλ‹€"}

{/* μ˜΅μ…˜ */}
handleChange("showIndex", checked as boolean)} />
handleChange("allowRemove", checked as boolean)} />
handleChange("disabled", checked as boolean)} />
{/* μ‚¬μš© μ˜ˆμ‹œ */}

πŸ’‘ μ‚¬μš© μ˜ˆμ‹œ

  • β€’ ν’ˆλͺ© 선택 λͺ¨λ‹¬ β†’ λ‹€μŒ λ²„νŠΌ β†’ κ±°λž˜μ²˜λ³„ 가격 μž…λ ₯
  • β€’ μ‚¬μš©μž 선택 λͺ¨λ‹¬ β†’ λ‹€μŒ λ²„νŠΌ β†’ κΆŒν•œ 및 λΆ€μ„œ ν• λ‹Ή
  • β€’ μ œν’ˆ 선택 λͺ¨λ‹¬ β†’ λ‹€μŒ λ²„νŠΌ β†’ μˆ˜λŸ‰ 및 납기일 μž…λ ₯
); }; SelectedItemsDetailInputConfigPanel.displayName = "SelectedItemsDetailInputConfigPanel";