"use client"; import React, { useState, useCallback, useEffect, useMemo, useRef } from "react"; import { useRouter } from "next/navigation"; import { apiClient } from "@/lib/api/client"; import { SupplierModal, type Supplier, matchChosung } from "./SupplierModal"; import { NumberPadModal, type PackageEntry } from "./NumberPadModal"; import { BarcodeScanModal } from "../common/BarcodeScanModal"; import type { CartItemWithId } from "../common/useCartSync"; /* ------------------------------------------------------------------ */ /* Types */ /* ------------------------------------------------------------------ */ interface PurchaseOrder { id: string; purchase_no: string; order_date: string; supplier_code: string; supplier_name: string; item_code: string; item_name: string; spec: string; material: string; order_qty: number; received_qty: number; remain_qty: number; unit_price: number; status: string; due_date: string; source_table: string; /** Inspection type: "self" = self inspection required, "request" = inspection request optional, null = none */ inspection_type: "self" | "request" | null; /** Item image URL from item_info.image (may be null) */ image: string | null; } /* ------------------------------------------------------------------ */ /* Dummy data (fallback) */ /* ------------------------------------------------------------------ */ const DUMMY_ORDERS: PurchaseOrder[] = [ { id: "dm1", purchase_no: "PO-2026-0401-001", order_date: "2026-03-28", supplier_code: "C001", supplier_name: "(주)한국철강", item_code: "STS304-2T", item_name: "STS304 2T 판재", spec: "2T x 1219 x 2438", material: "STS304", order_qty: 500, received_qty: 0, remain_qty: 500, unit_price: 12000, status: "발주확정", due_date: "2026-04-05", source_table: "purchase_detail", inspection_type: "self", image: null, }, { id: "dm2", purchase_no: "PO-2026-0401-001", order_date: "2026-03-28", supplier_code: "C001", supplier_name: "(주)한국철강", item_code: "STS316-3T", item_name: "STS316 3T 판재", spec: "3T x 1219 x 2438", material: "STS316", order_qty: 200, received_qty: 50, remain_qty: 150, unit_price: 18000, status: "부분입고", due_date: "2026-04-05", source_table: "purchase_detail", inspection_type: "request", image: null, }, { id: "dm3", purchase_no: "PO-2026-0330-005", order_date: "2026-03-25", supplier_code: "C001", supplier_name: "(주)한국철강", item_code: "AL-A100", item_name: "알루미늄 프로파일 A100", spec: "A100 x 6000L", material: "AL6063", order_qty: 300, received_qty: 100, remain_qty: 200, unit_price: 8500, status: "부분입고", due_date: "2026-04-02", source_table: "purchase_detail", inspection_type: null, image: null, }, { id: "dm4", purchase_no: "PO-2026-0329-003", order_date: "2026-03-22", supplier_code: "C002", supplier_name: "(주)대한알루미늄", item_code: "BOLT-M10", item_name: "볼트 M10x30", spec: "M10x30 SUS", material: "SUS304", order_qty: 1000, received_qty: 0, remain_qty: 1000, unit_price: 150, status: "발주확정", due_date: "2026-04-01", source_table: "purchase_detail", inspection_type: "self", image: null, }, { id: "dm5", purchase_no: "PO-2026-0329-003", order_date: "2026-03-22", supplier_code: "C002", supplier_name: "(주)대한알루미늄", item_code: "NUT-M10", item_name: "너트 M10 SUS", spec: "M10", material: "SUS304", order_qty: 1000, received_qty: 0, remain_qty: 1000, unit_price: 80, status: "발주확정", due_date: "2026-04-01", source_table: "purchase_detail", inspection_type: null, image: null, }, ]; /* ------------------------------------------------------------------ */ /* Component */ /* ------------------------------------------------------------------ */ interface PurchaseInboundProps { /** useCartSync 훅 인스턴스 (page.tsx에서 생성하여 전달) */ cart: import("../common/useCartSync").UseCartSyncReturn; } export function PurchaseInbound({ cart }: PurchaseInboundProps) { const router = useRouter(); /* State */ const [selectedSupplier, setSelectedSupplier] = useState(null); const [supplierModalOpen, setSupplierModalOpen] = useState(false); const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(false); const [keyword, setKeyword] = useState(""); /* NumberPad state */ const [numpadOpen, setNumpadOpen] = useState(false); const [numpadTarget, setNumpadTarget] = useState(null); /* Barcode scan modal state */ const [supplierScanOpen, setSupplierScanOpen] = useState(false); const [itemScanOpen, setItemScanOpen] = useState(false); /* Inline supplier search state */ const [supplierSearchText, setSupplierSearchText] = useState(""); const [supplierDropdownOpen, setSupplierDropdownOpen] = useState(false); const [allSuppliers, setAllSuppliers] = useState([]); const supplierInputRef = useRef(null); const supplierDropdownRef = useRef(null); /* Fetch all suppliers for inline search (supplier_mng = 공급사) */ const fetchAllSuppliers = useCallback(async () => { try { const res = await apiClient.get("/data/supplier_mng", { params: { pageSize: 500 } }); const data = res.data?.data ?? res.data?.rows ?? []; const list: Supplier[] = (Array.isArray(data) ? data : []).map((r: Record) => ({ id: String(r.id ?? ""), customer_name: String(r.supplier_name ?? r.customer_name ?? r.name ?? ""), customer_code: String(r.supplier_code ?? r.customer_code ?? r.code ?? ""), business_number: String(r.business_number ?? ""), phone: String(r.contact_phone ?? r.phone ?? ""), address: String(r.address ?? ""), })); setAllSuppliers(list); } catch { setAllSuppliers([]); } }, []); useEffect(() => { fetchAllSuppliers(); }, [fetchAllSuppliers]); /* Filtered suppliers for inline dropdown */ const filteredSuppliers = useMemo(() => { if (!supplierSearchText.trim()) return []; return allSuppliers.filter((s) => matchChosung(s.customer_name, supplierSearchText.trim())); }, [allSuppliers, supplierSearchText]); /* Close dropdown on outside click */ useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( supplierDropdownRef.current && !supplierDropdownRef.current.contains(e.target as Node) && supplierInputRef.current && !supplierInputRef.current.contains(e.target as Node) ) { setSupplierDropdownOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); /* Fetch purchase orders */ const fetchOrders = useCallback(async (searchKeyword?: string) => { setLoading(true); try { const params: Record = { pageSize: "50" }; if (searchKeyword) params.keyword = searchKeyword; const res = await apiClient.get("/receiving/source/purchase-orders", { params }); const data = res.data?.data; if (Array.isArray(data) && data.length > 0) { setOrders(data.map((r: Record) => ({ id: String(r.id ?? ""), purchase_no: String(r.purchase_no ?? ""), order_date: String(r.order_date ?? "").slice(0, 10), supplier_code: String(r.supplier_code ?? ""), supplier_name: String(r.supplier_name ?? ""), item_code: String(r.item_code ?? ""), item_name: String(r.item_name ?? ""), spec: String(r.spec ?? ""), material: String(r.material ?? ""), order_qty: Number(r.order_qty ?? 0), received_qty: Number(r.received_qty ?? 0), remain_qty: Number(r.remain_qty ?? 0), unit_price: Number(r.unit_price ?? 0), status: String(r.status ?? ""), due_date: String(r.due_date ?? "").slice(0, 10), source_table: String(r.source_table ?? "purchase_detail"), inspection_type: r.inspection_type === "self" ? "self" : r.inspection_type === "request" ? "request" : null, image: r.image ? String(r.image) : null, }))); } else { setOrders(DUMMY_ORDERS); } } catch { setOrders(DUMMY_ORDERS); } finally { setLoading(false); } }, []); /* Initial load */ useEffect(() => { fetchOrders(); }, [fetchOrders]); /* Filter orders by selected supplier */ const filteredOrders = selectedSupplier ? orders.filter((o) => o.supplier_code === selectedSupplier.customer_code || o.supplier_name === selectedSupplier.customer_name ) : orders; /* Filter by keyword */ const displayOrders = keyword ? filteredOrders.filter((o) => o.item_name.toLowerCase().includes(keyword.toLowerCase()) || o.item_code.toLowerCase().includes(keyword.toLowerCase()) || o.purchase_no.toLowerCase().includes(keyword.toLowerCase()) ) : filteredOrders; /* Open numpad for an order */ const openNumpad = (order: PurchaseOrder) => { setNumpadTarget(order); setNumpadOpen(true); }; /* Add to cart with numpad result */ const handleNumpadConfirm = (qty: number, packages: PackageEntry[]) => { if (!numpadTarget) return; const order = numpadTarget; if (cart.isItemInCart(order.id)) return; const finalQty = Math.min(qty, order.remain_qty); cart.addItem( { row: { id: order.id, item_code: order.item_code, item_name: order.item_name, supplier_code: order.supplier_code, supplier_name: order.supplier_name, purchase_no: order.purchase_no, unit_price: order.unit_price || 0, spec: order.spec || "", material: order.material || "", order_qty: order.order_qty, remain_qty: order.remain_qty, order_date: order.order_date || "", inspection_type: order.inspection_type, source_table: order.source_table, image: order.image || null, }, quantity: finalQty, // PackageEntry 타입이 registry vs NumberPadModal에서 다르므로 any 캐스팅 // eslint-disable-next-line @typescript-eslint/no-explicit-any packageEntries: packages.length > 0 ? packages as any : undefined, }, order.id, ); setNumpadTarget(null); }; /* Remove from cart (cancel) */ const handleRemoveFromCart = (id: string) => { cart.removeItem(id); }; /* Search */ const handleSearch = () => { fetchOrders(keyword || undefined); }; const isInCart = (id: string) => cart.isItemInCart(id); const getCartItem = (id: string): CartItemWithId | undefined => cart.getCartItem(id); return (
{/* ===== Header ===== */}

구매입고

발주 품목을 선택하여 입고하세요

{/* Cart button moved to header via headerRight prop */}
{/* ===== Search area (2 columns on tablet+) ===== */}
{/* Supplier search card */}
거래처 {selectedSupplier && ( {selectedSupplier.customer_name} )}
{/* QR/Barcode scan button - glossy v3 */} {selectedSupplier && ( )} {/* Supplier dropdown removed — use modal instead */}
{/* Item search card */}
발주 품목 {selectedSupplier ? displayOrders.length : 0}
setKeyword(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") handleSearch(); }} placeholder="품목명, 품목코드, 발주번호 검색..." disabled={!selectedSupplier} className={`flex-1 px-3 py-2.5 border border-gray-200 rounded-lg text-sm outline-none transition-all ${ selectedSupplier ? "focus:border-blue-400 focus:ring-2 focus:ring-blue-100" : "bg-gray-50 text-gray-400 cursor-not-allowed" }`} /> {/* QR/Barcode scan button - glossy v3 */}
{/* ===== Order items ===== */}
발주 품목 목록 {selectedSupplier ? `${displayOrders.length}건` : "-"}
{!selectedSupplier ? (

거래처를 먼저 선택하세요

거래처를 선택하면 해당 거래처의 발주 품목이 표시됩니다

) : loading ? (
불러오는 중...
) : displayOrders.length === 0 ? (

{selectedSupplier ? "해당 거래처의 미입고 발주가 없습니다" : "거래처를 선택하거나 품목을 검색하세요"}

) : (
{displayOrders.map((order) => { const inCart = isInCart(order.id); const cartItem = getCartItem(order.id); return (
{/* Green left bar for in-cart items */} {inCart && (
)} {/* === Header row: item code + item name + inspection badge === */}
{order.item_code} {order.item_name} {order.inspection_type === "self" && ( 검사 필수 )} {order.inspection_type === "request" && ( 검사의뢰 선택 )}
{/* === Body row: image + info + action === */}
{/* Product image */}
{order.image ? ( {order.item_name} ) : ( {"\uD83D\uDCE6"} )}
{/* Info columns */}
발주일 {order.order_date}
발주번호 {order.purchase_no}
발주수량 {order.order_qty.toLocaleString()}
미입고 {inCart ? (order.remain_qty - (cartItem?.quantity ?? 0)).toLocaleString() : order.remain_qty.toLocaleString() }
{/* Action column: qty display + add/cancel button */}
{/* Qty display - clickable to open numpad */} {/* Add / Cancel button */} {inCart ? ( ) : ( )}
{/* === Package info (shown when in cart with packages) === */} {(() => { // packageEntries의 실제 런타임 타입은 NumberPadModal의 PackageEntry[] const pkgs = (inCart && cartItem?.packageEntries) ? cartItem.packageEntries as unknown as PackageEntry[] : []; return pkgs.length > 0 && (
포장완료 {pkgs.reduce((s, p) => s + p.count * p.qtyPerUnit, 0).toLocaleString()} EA
{pkgs.map((pkg, idx) => (
{pkg.unit.icon} {pkg.count}{pkg.unit.label} x {pkg.qtyPerUnit.toLocaleString()}EA = {(pkg.count * pkg.qtyPerUnit).toLocaleString()}EA
))}
); })()}
); })}
)}
{/* ===== Modals ===== */} setSupplierModalOpen(false)} onSelect={(s) => setSelectedSupplier(s)} /> { setNumpadOpen(false); setNumpadTarget(null); }} onConfirm={handleNumpadConfirm} maxQty={numpadTarget?.remain_qty ?? 0} itemName={numpadTarget?.item_name ?? ""} /> {/* Barcode scan modal for supplier */} { setSupplierScanOpen(false); // 스캔 결과로 거래처 검색 (거래처명 또는 코드 매칭) const match = allSuppliers.find( (s) => s.customer_code === barcode || s.customer_name.includes(barcode) ); if (match) { setSelectedSupplier(match); setSupplierSearchText(""); } else { // 매칭 안 되면 검색 텍스트에 넣어서 드롭다운 표시 setSupplierSearchText(barcode); setSupplierDropdownOpen(true); } }} /> {/* Barcode scan modal for item */} { setItemScanOpen(false); // 스캔 결과로 품목 필터 setKeyword(barcode); fetchOrders(barcode); }} />
); }