From 0775a4560602c7427d75e632c01552931ad4ce9f Mon Sep 17 00:00:00 2001 From: kjs Date: Wed, 25 Mar 2026 11:03:11 +0900 Subject: [PATCH] Enhance packaging page with popover and command components - Integrated Popover and Command components for improved item selection in the packaging page. - Replaced the previous search input with a more dynamic and user-friendly popover interface for selecting packaging items. - Updated state management to handle popover visibility and item selection more effectively. - Enhanced user experience by providing real-time search capabilities within the popover for packaging items. --- .../app/(main)/logistics/packaging/page.tsx | 202 ++++++++---------- 1 file changed, 95 insertions(+), 107 deletions(-) diff --git a/frontend/app/(main)/logistics/packaging/page.tsx b/frontend/app/(main)/logistics/packaging/page.tsx index 30e79672..0b91e1ae 100644 --- a/frontend/app/(main)/logistics/packaging/page.tsx +++ b/frontend/app/(main)/logistics/packaging/page.tsx @@ -15,10 +15,12 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Command, CommandInput, CommandList, CommandEmpty, CommandItem } from "@/components/ui/command"; import { FullscreenDialog } from "@/components/common/FullscreenDialog"; import { useConfirmDialog } from "@/components/common/ConfirmDialog"; import { - Search, Plus, Trash2, RotateCcw, Loader2, Package, Box, X, Save, Edit2, Download, + Search, Plus, Trash2, RotateCcw, Loader2, Package, Box, X, Save, Edit2, Download, ChevronsUpDown, Check, } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; @@ -85,13 +87,13 @@ export default function PackagingPage() { const [pkgModalMode, setPkgModalMode] = useState<"create" | "edit">("create"); const [pkgForm, setPkgForm] = useState>({}); const [pkgItemOptions, setPkgItemOptions] = useState([]); - const [pkgItemSearchKw, setPkgItemSearchKw] = useState(""); + const [pkgItemPopoverOpen, setPkgItemPopoverOpen] = useState(false); const [loadModalOpen, setLoadModalOpen] = useState(false); const [loadModalMode, setLoadModalMode] = useState<"create" | "edit">("create"); const [loadForm, setLoadForm] = useState>({}); const [loadItemOptions, setLoadItemOptions] = useState([]); - const [loadItemSearchKw, setLoadItemSearchKw] = useState(""); + const [loadItemPopoverOpen, setLoadItemPopoverOpen] = useState(false); const [itemMatchModalOpen, setItemMatchModalOpen] = useState(false); const [itemMatchKeyword, setItemMatchKeyword] = useState(""); @@ -166,19 +168,16 @@ export default function PackagingPage() { } else { setPkgForm({ pkg_code: "", pkg_name: "", pkg_type: "", status: "ACTIVE", width_mm: "", length_mm: "", height_mm: "", self_weight_kg: "", max_load_kg: "", volume_l: "", remarks: "" }); } - setPkgItemSearchKw(""); - setPkgItemOptions([]); + setPkgItemPopoverOpen(false); + try { + const res = await getItemsByDivision("포장재"); + if (res.success) setPkgItemOptions(res.data); + } catch { setPkgItemOptions([]); } setPkgModalOpen(true); }; - const searchPkgItems = async (kw?: string) => { - try { - const res = await getItemsByDivision("포장재", kw || undefined); - if (res.success) setPkgItemOptions(res.data); - } catch { setPkgItemOptions([]); } - }; - const onPkgItemSelect = (item: ItemInfoForPkg) => { + setPkgItemPopoverOpen(false); const dims = parseSpecDimensions(item.size); setPkgForm((prev) => ({ ...prev, @@ -224,19 +223,16 @@ export default function PackagingPage() { } else { setLoadForm({ loading_code: "", loading_name: "", loading_type: "", status: "ACTIVE", width_mm: "", length_mm: "", height_mm: "", self_weight_kg: "", max_load_kg: "", max_stack: "", remarks: "" }); } - setLoadItemSearchKw(""); - setLoadItemOptions([]); + setLoadItemPopoverOpen(false); + try { + const res = await getItemsByDivision("적재함"); + if (res.success) setLoadItemOptions(res.data); + } catch { setLoadItemOptions([]); } setLoadModalOpen(true); }; - const searchLoadItems = async (kw?: string) => { - try { - const res = await getItemsByDivision("적재함", kw || undefined); - if (res.success) setLoadItemOptions(res.data); - } catch { setLoadItemOptions([]); } - }; - const onLoadItemSelect = (item: ItemInfoForPkg) => { + setLoadItemPopoverOpen(false); const dims = parseSpecDimensions(item.size); setLoadForm((prev) => ({ ...prev, @@ -275,9 +271,13 @@ export default function PackagingPage() { }; // --- 품목 추가 모달 (포장재 매칭) --- - const openItemMatchModal = () => { - setItemMatchKeyword(""); setItemMatchResults([]); setItemMatchSelected(null); setItemMatchQty(1); + const openItemMatchModal = async () => { + setItemMatchKeyword(""); setItemMatchSelected(null); setItemMatchQty(1); setItemMatchModalOpen(true); + try { + const res = await getGeneralItems(); + if (res.success) setItemMatchResults(res.data); + } catch { setItemMatchResults([]); } }; const searchItemsForMatch = async () => { @@ -635,44 +635,37 @@ export default function PackagingPage() { {pkgModalMode === "create" && (
-
- setPkgItemSearchKw(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && searchPkgItems(pkgItemSearchKw)} - className="h-9 text-xs flex-1" - /> - -
- {pkgItemOptions.length > 0 && ( -
- - - - - 품목코드 - 품목명 - 규격 - - - + + + + + + { + const item = pkgItemOptions.find((i) => i.id === value); + if (!item) return 0; + const target = `${item.item_number} ${item.item_name} ${item.size || ""}`.toLowerCase(); + return target.includes(search.toLowerCase()) ? 1 : 0; + }}> + + + 검색 결과가 없습니다 {pkgItemOptions.map((item) => ( - onPkgItemSelect(item)}> - {pkgForm.pkg_code === item.item_number ? "✓" : ""} - {item.item_number} - {item.item_name} - {item.size || "-"} - + onPkgItemSelect(item)} className="text-xs"> + + {item.item_name} + {item.item_number} + {item.size && {item.size}} + ))} - -
-
- )} - {pkgItemOptions.length === 0 &&

검색어를 입력하고 검색 버튼을 눌러주세요

} + + + +
)}
@@ -723,44 +716,37 @@ export default function PackagingPage() { {loadModalMode === "create" && (
-
- setLoadItemSearchKw(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && searchLoadItems(loadItemSearchKw)} - className="h-9 text-xs flex-1" - /> - -
- {loadItemOptions.length > 0 && ( -
- - - - - 품목코드 - 품목명 - 규격 - - - + + + + + + { + const item = loadItemOptions.find((i) => i.id === value); + if (!item) return 0; + const target = `${item.item_number} ${item.item_name} ${item.size || ""}`.toLowerCase(); + return target.includes(search.toLowerCase()) ? 1 : 0; + }}> + + + 검색 결과가 없습니다 {loadItemOptions.map((item) => ( - onLoadItemSelect(item)}> - {loadForm.loading_code === item.item_number ? "✓" : ""} - {item.item_number} - {item.item_name} - {item.size || "-"} - + onLoadItemSelect(item)} className="text-xs"> + + {item.item_name} + {item.item_number} + {item.size && {item.size}} + ))} - -
-
- )} - {loadItemOptions.length === 0 &&

검색어를 입력하고 검색 버튼을 눌러주세요

} + + + +
)}
@@ -798,7 +784,7 @@ export default function PackagingPage() { {/* 품목 추가 모달 (포장재 매칭) */} - + 품목 추가 — {selectedPkg?.pkg_name} 포장재에 매칭할 품목을 검색하여 추가합니다. @@ -809,27 +795,29 @@ export default function PackagingPage() { onKeyDown={(e) => e.key === "Enter" && searchItemsForMatch()} className="h-9 text-xs" />
-
+
- 품목코드 + 품목코드 품목명 - 규격 + 규격 + 재질 단위 {itemMatchResults.length === 0 ? ( - 검색 결과가 없습니다 + 검색 결과가 없습니다 ) : itemMatchResults.map((item) => ( setItemMatchSelected(item)}> {itemMatchSelected?.id === item.id ? "✓" : ""} - {item.item_number} - {item.item_name} - {item.spec || "-"} + {item.item_number} + {item.item_name} + {item.spec || "-"} + {item.material || "-"} {item.unit || "EA"} ))} @@ -848,8 +836,8 @@ export default function PackagingPage() { - - + + @@ -899,8 +887,8 @@ export default function PackagingPage() { - - + +