From f2ebd6ae1be2d849a97beab384dce09316d882ad Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 6 Apr 2026 18:24:21 +0900 Subject: [PATCH] feat: Implement searchable category combobox in item info page - Added a new CategoryCombobox component for improved category selection. - Integrated the combobox into the item info form, replacing the previous select component. - Enhanced data handling to support multiple category selections with comma separation. - Updated selling and standard price fields to format numbers correctly. These changes aim to enhance user experience by providing a more intuitive and flexible category selection method. --- .../COMPANY_16/master-data/item-info/page.tsx | 83 +++++++++++++++---- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/frontend/app/(main)/COMPANY_16/master-data/item-info/page.tsx b/frontend/app/(main)/COMPANY_16/master-data/item-info/page.tsx index 954c54c1..5fa81eae 100644 --- a/frontend/app/(main)/COMPANY_16/master-data/item-info/page.tsx +++ b/frontend/app/(main)/COMPANY_16/master-data/item-info/page.tsx @@ -33,9 +33,11 @@ import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSea import { exportToExcel } from "@/lib/utils/excelExport"; import { Plus, Trash2, Save, Loader2, FileSpreadsheet, Download, - Pencil, Copy, Settings2, + Pencil, Copy, Settings2, Check, ChevronsUpDown, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -44,6 +46,43 @@ import { useTableSettings } from "@/hooks/useTableSettings"; import { TableSettingsModal } from "@/components/common/TableSettingsModal"; import { toast } from "sonner"; +// 검색 가능한 카테고리 콤보박스 +function CategoryCombobox({ options, value, onChange, placeholder }: { + options: { code: string; label: string }[]; + value: string; + onChange: (v: string) => void; + placeholder: string; +}) { + const [open, setOpen] = useState(false); + const selected = options.find((o) => o.code === value); + return ( + + + + + + + + + 검색 결과가 없어요 + + {options.map((opt) => ( + { onChange(opt.code); setOpen(false); }}> + + {opt.label} + + ))} + + + + + + ); +} + const TABLE_NAME = "item_info"; const GRID_COLUMNS = [ @@ -55,8 +94,8 @@ const GRID_COLUMNS = [ { key: "unit", label: "단위" }, { key: "material", label: "재질" }, { key: "status", label: "상태" }, - { key: "selling_price", label: "판매가격", align: "right" as const }, - { key: "standard_price", label: "기준단가", align: "right" as const }, + { key: "selling_price", label: "판매가격", align: "right" as const, formatNumber: true }, + { key: "standard_price", label: "기준단가", align: "right" as const, formatNumber: true }, { key: "weight", label: "중량", align: "right" as const }, { key: "inventory_unit", label: "재고단위" }, { key: "user_type01", label: "대분류" }, @@ -163,6 +202,14 @@ export default function ItemInfoPage() { const raw = res.data?.data?.data || res.data?.data?.rows || []; const resolve = (col: string, code: string) => { if (!code) return ""; + // 쉼표 구분 다중값 지원 + if (code.includes(",")) { + return code.split(",").map((c) => { + const trimmed = c.trim(); + if (!trimmed || trimmed === "s") return ""; + return categoryOptions[col]?.find((o) => o.code === trimmed)?.label || trimmed; + }).filter(Boolean).join(", "); + } return categoryOptions[col]?.find((o) => o.code === code)?.label || code; }; const data = raw.map((r: any) => { @@ -361,6 +408,7 @@ export default function ItemInfoPage() { key: col.key, label: col.label, align: col.align as "left" | "center" | "right" | undefined, + formatNumber: (col as any).formatNumber, }))} data={ts.groupData(items)} loading={loading} @@ -394,21 +442,12 @@ export default function ItemInfoPage() { {field.required && *} {field.type === "category" ? ( - + onChange={(v) => setFormData((prev) => ({ ...prev, [field.key]: v }))} + placeholder={`${field.label} 선택`} + /> ) : field.type === "textarea" ? (