"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, FieldGroup, DisplayItem, DisplayItemType, EmptyBehavior, DisplayFieldFormat } 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 [localFieldGroups, setLocalFieldGroups] = useState(config.fieldGroups || []); // πŸ†• ν•­λͺ© ν‘œμ‹œ μ„€μ • μƒνƒœ const [displayItems, setDisplayItems] = useState(config.displayItems || []); // πŸ†• 원본 ν…Œμ΄λΈ” 선택 μƒνƒœ 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 handleFieldGroupsChange = (groups: FieldGroup[]) => { setLocalFieldGroups(groups); handleChange("fieldGroups", groups); }; const addFieldGroup = () => { const newGroup: FieldGroup = { id: `group_${localFieldGroups.length + 1}`, title: `κ·Έλ£Ή ${localFieldGroups.length + 1}`, order: localFieldGroups.length, }; handleFieldGroupsChange([...localFieldGroups, newGroup]); }; const removeFieldGroup = (groupId: string) => { // κ·Έλ£Ή μ‚­μ œ μ‹œ ν•΄λ‹Ή 그룹에 μ†ν•œ ν•„λ“œλ“€μ˜ groupId도 제거 const updatedFields = localFields.map(field => field.groupId === groupId ? { ...field, groupId: undefined } : field ); setLocalFields(updatedFields); handleChange("additionalFields", updatedFields); handleFieldGroupsChange(localFieldGroups.filter(g => g.id !== groupId)); }; const updateFieldGroup = (groupId: string, updates: Partial) => { const newGroups = localFieldGroups.map(g => g.id === groupId ? { ...g, ...updates } : g ); handleFieldGroupsChange(newGroups); }; // ν‘œμ‹œ 컬럼 μΆ”κ°€ 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 handleDisplayItemsChange = (items: DisplayItem[]) => { setDisplayItems(items); handleChange("displayItems", items); }; const addDisplayItem = (type: DisplayItemType) => { const newItem: DisplayItem = { type, id: `display-${Date.now()}`, }; if (type === "field") { newItem.fieldName = localFields[0]?.name || ""; newItem.format = "text"; newItem.emptyBehavior = "default"; } else if (type === "icon") { newItem.icon = "Circle"; } else if (type === "text") { newItem.value = "ν…μŠ€νŠΈ"; } handleDisplayItemsChange([...displayItems, newItem]); }; const removeDisplayItem = (index: number) => { handleDisplayItemsChange(displayItems.filter((_, i) => i !== index)); }; const updateDisplayItem = (index: number, updates: Partial) => { const updated = [...displayItems]; updated[index] = { ...updated[index], ...updates }; handleDisplayItemsChange(updated); }; // πŸ†• μ„ νƒλœ 원본 ν…Œμ΄λΈ” ν‘œμ‹œλͺ… 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" />
{/* πŸ†• ν•„λ“œ κ·Έλ£Ή 선택 */} {localFieldGroups.length > 0 && (

같은 κ·Έλ£Ή IDλ₯Ό κ°€μ§„ ν•„λ“œλ“€μ€ 같은 μΉ΄λ“œμ— ν‘œμ‹œλ©λ‹ˆλ‹€

)}
updateField(index, { required: checked as boolean })} />
))}
{/* πŸ†• ν•„λ“œ κ·Έλ£Ή 관리 */}

μΆ”κ°€ μž…λ ₯ ν•„λ“œλ₯Ό μ—¬λŸ¬ μΉ΄λ“œλ‘œ λ‚˜λˆ μ„œ ν‘œμ‹œ (예: 거래처 정보, 단가 정보)

{localFieldGroups.map((group, index) => (
κ·Έλ£Ή {index + 1}
{/* κ·Έλ£Ή ID */}
updateFieldGroup(group.id, { id: e.target.value })} className="h-7 text-xs sm:h-8 sm:text-sm" placeholder="group_customer" />
{/* κ·Έλ£Ή 제λͺ© */}
updateFieldGroup(group.id, { title: e.target.value })} className="h-7 text-xs sm:h-8 sm:text-sm" placeholder="거래처 정보" />
{/* κ·Έλ£Ή μ„€λͺ… */}
updateFieldGroup(group.id, { description: e.target.value })} className="h-7 text-xs sm:h-8 sm:text-sm" placeholder="거래처 κ΄€λ ¨ 정보λ₯Ό μž…λ ₯ν•©λ‹ˆλ‹€" />
{/* ν‘œμ‹œ μˆœμ„œ */}
updateFieldGroup(group.id, { order: parseInt(e.target.value) || 0 })} className="h-7 text-xs sm:h-8 sm:text-sm" min="0" />
))} {localFieldGroups.length > 0 && (

πŸ’‘ μΆ”κ°€ μž…λ ₯ ν•„λ“œμ˜ "ν•„λ“œ κ·Έλ£Ή ID"에 μœ„μ—μ„œ μ •μ˜ν•œ κ·Έλ£Ή IDλ₯Ό μž…λ ₯ν•˜μ„Έμš”

)}
{/* μž…λ ₯ λͺ¨λ“œ μ„€μ • */}

{config.inputMode === "modal" ? "μΆ”κ°€ λ²„νŠΌ 클릭 μ‹œ μž…λ ₯μ°½ ν‘œμ‹œ, μ™„λ£Œ ν›„ μž‘μ€ μΉ΄λ“œλ‘œ ν‘œμ‹œ" : "λͺ¨λ“  ν•­λͺ©μ˜ μž…λ ₯창을 항상 ν‘œμ‹œ"}

{/* λ ˆμ΄μ•„μ›ƒ μ„€μ • */}

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

{/* μ˜΅μ…˜ */}
handleChange("showIndex", checked as boolean)} />
handleChange("allowRemove", checked as boolean)} />
handleChange("disabled", checked as boolean)} />
{/* πŸ†• ν•­λͺ© ν‘œμ‹œ μ„€μ • */}

각 μž…λ ₯ ν•­λͺ©μ΄ μΆ”κ°€λ˜λ©΄ μ–΄λ–»κ²Œ ν‘œμ‹œλ μ§€ μ„€μ •ν•©λ‹ˆλ‹€

{displayItems.length === 0 ? (
ν‘œμ‹œ ν•­λͺ©μ΄ μ—†μŠ΅λ‹ˆλ‹€. μœ„μ˜ λ²„νŠΌμœΌλ‘œ μΆ”κ°€ν•˜μ„Έμš”.
(λ―Έμ„€μ • μ‹œ λͺ¨λ“  ν•„λ“œλ₯Ό " / "둜 κ΅¬λΆ„ν•˜μ—¬ ν‘œμ‹œ)
) : (
{displayItems.map((item, index) => ( {/* 헀더 */}
{item.type === "icon" && "🎨 μ•„μ΄μ½˜"} {item.type === "field" && "πŸ“ ν•„λ“œ"} {item.type === "text" && "πŸ’¬ ν…μŠ€νŠΈ"} {item.type === "badge" && "🏷️ λ°°μ§€"}
{/* μ•„μ΄μ½˜ μ„€μ • */} {item.type === "icon" && (
updateDisplayItem(index, { icon: e.target.value })} placeholder="예: Building, User, Package" className="h-7 text-xs" />

lucide-react μ•„μ΄μ½˜ 이름을 μž…λ ₯ν•˜μ„Έμš”

)} {/* ν…μŠ€νŠΈ μ„€μ • */} {item.type === "text" && (
updateDisplayItem(index, { value: e.target.value })} placeholder="예: | , / , - " className="h-7 text-xs" />
)} {/* ν•„λ“œ μ„€μ • */} {item.type === "field" && (
{/* ν•„λ“œ 선택 */}
{/* 라벨 */}
updateDisplayItem(index, { label: e.target.value })} placeholder="예: 거래처:, 단가:" className="h-7 text-xs" />
{/* ν‘œμ‹œ ν˜•μ‹ */}
{/* 빈 κ°’ 처리 */}
{/* κΈ°λ³Έκ°’ (emptyBehaviorκ°€ "default"일 λ•Œλ§Œ) */} {item.emptyBehavior === "default" && (
updateDisplayItem(index, { defaultValue: e.target.value })} placeholder="예: λ―Έμž…λ ₯, 0, -" className="h-7 text-xs" />
)}
)} {/* μŠ€νƒ€μΌ μ„€μ • */}
{/* 색상 */}
updateDisplayItem(index, { color: e.target.value })} className="h-7 w-full" />
updateDisplayItem(index, { backgroundColor: e.target.value })} className="h-7 w-full" />
))}
)}
{/* μ‚¬μš© μ˜ˆμ‹œ */}

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

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