"use client"; import { useState, useEffect, useCallback, useRef } from "react"; import { Search, ChevronLeft, ChevronRight, ChevronUp, ChevronDown, X, CheckCircle2, Circle, ArrowUpDown, Boxes, SlidersHorizontal, Loader2, } from "lucide-react"; import type { WarehouseRow, QueryResult, FilterOptions } from "@/types/warehouse"; /* ── 品牌色:沿用原 Excel 的深蓝表头 + 淡蓝隔行 ── */ const NAVY = "#1F497D"; const NAVY_DARK = "#163758"; const FONT = '"PingFang SC","Microsoft YaHei","Noto Sans SC",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif'; /* ── 列定义(顺序与原 Excel 表头一致)── */ const COLUMNS = [ { key: "paichan", label: "排产号", mono: true, align: "left" }, { key: "zongpai", label: "总排号", mono: true, align: "left" }, { key: "workshop", label: "车间", align: "left" }, { key: "model", label: "产品型号", align: "left" }, { key: "range", label: "量程", align: "left" }, { key: "qty", label: "数量", num: true, align: "right" }, { key: "boxNo", label: "箱号", mono: true, align: "left" }, { key: "boxQty", label: "装箱数量", num: true, align: "right" }, { key: "workOrder", label: "工令号", mono: true, align: "left" }, { key: "shelf", label: "货架", mono: true, align: "left" }, { key: "handler", label: "经办人", align: "left" }, { key: "orderNo", label: "订单号", mono: true, align: "left" }, { key: "inbound", label: "入库时间", mono: true, align: "left" }, ]; /* ── Sub-components ── */ function SortIcon({ active, dir }: { active: boolean; dir: string }) { if (!active) return ; return dir === "asc" ? : ; } function StatusBadge({ status }: { status: "IN" | "WAIT" }) { if (status === "IN") { return ( 已入库 ); } return ( 待入库 ); } function Field({ label, children, className = "" }: { label: string; children: React.ReactNode; className?: string }) { return (
{children}
); } function SelectWrap({ value, onChange, children }: { value: string; onChange: (e: React.ChangeEvent) => void; children: React.ReactNode }) { return (
); } function Chip({ children, onRemove }: { children: React.ReactNode; onRemove: () => void }) { return ( {children} ); } /* ── Main Page Component ── */ export default function WarehouseDashboard() { const [search, setSearch] = useState(""); const [workshop, setWorkshop] = useState("ALL"); const [handler, setHandler] = useState("ALL"); const [status, setStatus] = useState<"ALL" | "IN" | "WAIT">("ALL"); const [dateFrom, setDateFrom] = useState(""); const [dateTo, setDateTo] = useState(""); const [sortKey, setSortKey] = useState(""); const [sortDir, setSortDir] = useState<"asc" | "desc">("asc"); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [data, setData] = useState(null); const [filterOptions, setFilterOptions] = useState(null); const searchTimeout = useRef | null>(null); /* Fetch filter options on mount */ useEffect(() => { (async () => { try { const res = await fetch("/api/warehouse/filters"); if (!res.ok) throw new Error("Failed to load filter options"); const json: FilterOptions = await res.json(); setFilterOptions(json); } catch { // Filter options are not critical — silently degrade } })(); }, []); /* Build fetch callback */ const fetchData = useCallback(async () => { setLoading(true); setError(null); const params = new URLSearchParams(); if (search) params.set("search", search); if (workshop !== "ALL") params.set("workshop", workshop); if (handler !== "ALL") params.set("handler", handler); if (status !== "ALL") params.set("status", status); if (dateFrom) params.set("dateFrom", dateFrom); if (dateTo) params.set("dateTo", dateTo); if (sortKey) { params.set("sortKey", sortKey); params.set("sortDir", sortDir); } params.set("page", String(page)); params.set("pageSize", String(pageSize)); try { const res = await fetch(`/api/warehouse?${params}`); if (!res.ok) throw new Error("Query failed"); const json: QueryResult = await res.json(); setData(json); } catch (e) { setError(String(e)); } finally { setLoading(false); } }, [search, workshop, handler, status, dateFrom, dateTo, sortKey, sortDir, page, pageSize]); /* Debounced effect: fetch data when any dependency changes */ useEffect(() => { if (searchTimeout.current) clearTimeout(searchTimeout.current); searchTimeout.current = setTimeout(() => { fetchData(); }, search ? 300 : 0); return () => { if (searchTimeout.current) clearTimeout(searchTimeout.current); }; }, [fetchData, search]); /* Derived values */ const total = data?.total ?? 0; const totalPages = data?.totalPages ?? 1; const safePage = Math.min(page, totalPages); const start = (safePage - 1) * pageSize; const rows: WarehouseRow[] = data?.rows ?? []; const filtersActive = search || workshop !== "ALL" || handler !== "ALL" || status !== "ALL" || dateFrom || dateTo; const toggleSort = (key: string) => { if (sortKey === key) setSortDir((d) => (d === "asc" ? "desc" : "asc")); else { setSortKey(key); setSortDir("asc"); } setPage(1); }; const resetFilters = () => { setSearch(""); setWorkshop("ALL"); setHandler("ALL"); setStatus("ALL"); setDateFrom(""); setDateTo(""); setSortKey(""); setSortDir("asc"); setPage(1); }; const STATUS_LABEL: Record = { IN: "已入库", WAIT: "待入库" }; const dateInputCls = "rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm text-slate-700 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-200"; return (
{/* ── 顶部应用栏 ── */}

成品仓库数据查询系统

Finished Goods Warehouse · 成品库存实时查询

实时数据 · CompanyDB · 192.168.110.114
{/* ── 查询条件(核心)── */}

查询条件

匹配 {total} 条结果
{/* 主搜索框 */}
{ setSearch(e.target.value); setPage(1); }} placeholder="输入 总排号 / 工令号 / 订单号 / 产品型号 / 箱号 / 货架 进行查询…" className="w-full rounded-xl border border-slate-200 bg-slate-50 py-3 pl-11 pr-10 text-base text-slate-800 placeholder:text-slate-400 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-300" /> {search && ( )}
{/* 筛选项 */}
{ setWorkshop(e.target.value); setPage(1); }}> {filterOptions?.workshops.map((w) => ( ))} { setHandler(e.target.value); setPage(1); }}> {filterOptions?.handlers.map((h) => ( ))} { setStatus(e.target.value as "ALL" | "IN" | "WAIT"); setPage(1); }}>
{ setDateFrom(e.target.value); setPage(1); }} className={dateInputCls} /> { setDateTo(e.target.value); setPage(1); }} className={dateInputCls} />
{/* 已选条件标签 */} {filtersActive && (
已选条件 {search && { setSearch(""); setPage(1); }}>关键词:{search}} {workshop !== "ALL" && ( { setWorkshop("ALL"); setPage(1); }}> 车间:{filterOptions?.workshops.find(w => w.code === workshop)?.name ?? workshop} )} {handler !== "ALL" && { setHandler("ALL"); setPage(1); }}>经办人:{handler}} {status !== "ALL" && { setStatus("ALL"); setPage(1); }}>状态:{STATUS_LABEL[status]}} {dateFrom && { setDateFrom(""); setPage(1); }}>入库起:{dateFrom}} {dateTo && { setDateTo(""); setPage(1); }}>入库止:{dateTo}}
)}
{/* ── 查询结果 ── */}

查询结果

数据来源:生产合同 + 装箱明细 + 库位 + 入库记录(LEFT JOIN)
{/* Error banner */} {error && (
查询出错:{error}
)} {/* Loading / Table area */}
{loading && (
加载中...
)}
{COLUMNS.map((col) => ( ))} {!loading && rows.length === 0 ? ( ) : ( rows.map((r, i) => ( {COLUMNS.map((col) => { const raw = r[col.key as keyof WarehouseRow]; const empty = raw === "" || raw == null; const align = col.align === "right" ? "text-right" : "text-left"; const font = col.mono ? "font-mono" : ""; const num = col.num ? "tabular-nums" : ""; const color = col.mono ? "text-slate-600" : "text-slate-700"; return ( ); })} )) )}
toggleSort(col.key)} className={`sticky top-0 z-10 cursor-pointer select-none whitespace-nowrap px-4 py-3 font-semibold text-white transition hover:brightness-110 ${col.align === "right" ? "text-right" : "text-left"}`} style={{ background: NAVY }} > {col.label} 状态
未找到匹配的记录
{empty ? : String(raw)}
{/* 分页 */}
显示第 {total === 0 ? 0 : start + 1}{Math.min(start + pageSize, total)} 条, 共 {total}
每页
{safePage} / {totalPages}
); }