토글 버튼 2개로 표시 형식 전환: - '창고 가로' (기본): 헤더=창고 7개, 행=품목 (가로로 김) - '품목 가로': 헤더=품목, 행=창고 7줄 (오른쪽으로 길게, 좌측 sticky) 각 모드에서 동일하게 셀당 '발주수량(현재고) / 여유분' 2행. 여유분 음수면 rose 강조.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { RefreshCcw, Warehouse, Download } from "lucide-react";
|
||||
import { RefreshCcw, Warehouse, Download, LayoutGrid, Columns3 } from "lucide-react";
|
||||
import { downloadXlsx } from "@/lib/xlsx-export";
|
||||
|
||||
interface Wh { OBJID: string; WH_CODE: string; WH_NAME: string }
|
||||
@@ -31,6 +31,8 @@ export default function WhStockStatusPage() {
|
||||
const [warehouses, setWarehouses] = useState<Wh[]>([]);
|
||||
const [items, setItems] = useState<ItemRow[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
// 보기 모드: by-wh = 헤더가 창고(가로로 김) / by-item = 헤더가 품목(오른쪽으로 길게)
|
||||
const [viewMode, setViewMode] = useState<"by-wh" | "by-item">("by-wh");
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -80,7 +82,18 @@ export default function WhStockStatusPage() {
|
||||
기간 내 발주(출고요청/완료/계산서/입금) 수량과 현재고를 창고별로 나란히. <b>발주수량</b> = 현재 보유 재고, <b>여유분</b> = 현재고 − 기간 내 발주된 출고 예정 수량 (해당 창고 기준).
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* 보기 모드 토글 */}
|
||||
<div className="inline-flex rounded border border-slate-300 overflow-hidden">
|
||||
<button onClick={() => setViewMode("by-wh")}
|
||||
className={`h-9 px-3 text-xs font-semibold inline-flex items-center gap-1 ${viewMode === "by-wh" ? "bg-emerald-700 text-white" : "bg-white text-slate-600 hover:bg-slate-50"}`}>
|
||||
<Columns3 size={14} /> 창고 가로
|
||||
</button>
|
||||
<button onClick={() => setViewMode("by-item")}
|
||||
className={`h-9 px-3 text-xs font-semibold inline-flex items-center gap-1 border-l border-slate-300 ${viewMode === "by-item" ? "bg-emerald-700 text-white" : "bg-white text-slate-600 hover:bg-slate-50"}`}>
|
||||
<LayoutGrid size={14} /> 품목 가로
|
||||
</button>
|
||||
</div>
|
||||
<input type="date" value={dateFrom} onChange={(e) => setRange([e.target.value, dateTo])}
|
||||
className="h-9 px-3 rounded border border-slate-300 text-sm" />
|
||||
<span className="text-slate-400">~</span>
|
||||
@@ -100,54 +113,108 @@ export default function WhStockStatusPage() {
|
||||
</div>
|
||||
|
||||
<div className="bg-white border border-slate-200 rounded-xl overflow-x-auto">
|
||||
<table className="w-full text-sm border-collapse">
|
||||
<thead className="bg-slate-50 text-slate-600 sticky top-0">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2 border-b border-slate-200 min-w-[160px]">품목</th>
|
||||
<th className="text-center px-3 py-2 border-b border-slate-200 min-w-[100px]">분류</th>
|
||||
{warehouses.map((w) => (
|
||||
<th key={w.WH_CODE} className="text-right px-3 py-2 border-b border-slate-200 min-w-[88px]">
|
||||
{w.WH_NAME}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="tabular-nums">
|
||||
{items.length === 0 ? (
|
||||
<tr><td colSpan={2 + warehouses.length} className="text-center py-12 text-slate-400">
|
||||
{loading ? "조회 중..." : "데이터가 없습니다."}
|
||||
</td></tr>
|
||||
) : items.flatMap((it) => [
|
||||
<tr key={`${it.ITEM_OBJID}-stock`} className="border-t border-slate-100 hover:bg-slate-50/60">
|
||||
<td className="px-3 py-2 align-top font-semibold" rowSpan={2}>
|
||||
{it.ITEM_NAME}
|
||||
<div className="text-[10px] text-slate-400 font-mono">{it.ITEM_CODE}</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-center text-[11px] text-slate-700 bg-slate-50/70">발주수량</td>
|
||||
{warehouses.map((w) => {
|
||||
const v = Number(it.STOCK[w.WH_CODE] ?? 0);
|
||||
return (
|
||||
<td key={w.WH_CODE} className={`px-3 py-1.5 text-right ${v === 0 ? "text-slate-300" : "text-slate-800"}`}>
|
||||
{v === 0 ? "-" : fmt(v)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>,
|
||||
<tr key={`${it.ITEM_OBJID}-avail`} className="border-b border-slate-100">
|
||||
<td className="px-3 py-1.5 text-center text-[11px] text-emerald-700 bg-emerald-50/40 font-semibold">여유분</td>
|
||||
{warehouses.map((w) => {
|
||||
const v = Number(it.AVAILABLE[w.WH_CODE] ?? 0);
|
||||
const negative = v < 0;
|
||||
return (
|
||||
<td key={w.WH_CODE} className={`px-3 py-1.5 text-right ${negative ? "text-rose-600 font-bold" : v === 0 ? "text-slate-300" : "text-emerald-700 font-semibold"}`}>
|
||||
{v === 0 ? "-" : fmt(v)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>,
|
||||
])}
|
||||
</tbody>
|
||||
</table>
|
||||
{viewMode === "by-wh" ? (
|
||||
/* 보기 1: 헤더=창고(가로), 행=품목 */
|
||||
<table className="w-full text-sm border-collapse">
|
||||
<thead className="bg-slate-50 text-slate-600 sticky top-0">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2 border-b border-slate-200 min-w-[160px]">품목</th>
|
||||
<th className="text-center px-3 py-2 border-b border-slate-200 min-w-[100px]">분류</th>
|
||||
{warehouses.map((w) => (
|
||||
<th key={w.WH_CODE} className="text-right px-3 py-2 border-b border-slate-200 min-w-[88px]">
|
||||
{w.WH_NAME}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="tabular-nums">
|
||||
{items.length === 0 ? (
|
||||
<tr><td colSpan={2 + warehouses.length} className="text-center py-12 text-slate-400">
|
||||
{loading ? "조회 중..." : "데이터가 없습니다."}
|
||||
</td></tr>
|
||||
) : items.flatMap((it) => [
|
||||
<tr key={`${it.ITEM_OBJID}-stock`} className="border-t border-slate-100 hover:bg-slate-50/60">
|
||||
<td className="px-3 py-2 align-top font-semibold" rowSpan={2}>
|
||||
{it.ITEM_NAME}
|
||||
<div className="text-[10px] text-slate-400 font-mono">{it.ITEM_CODE}</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-center text-[11px] text-slate-700 bg-slate-50/70">발주수량</td>
|
||||
{warehouses.map((w) => {
|
||||
const v = Number(it.STOCK[w.WH_CODE] ?? 0);
|
||||
return (
|
||||
<td key={w.WH_CODE} className={`px-3 py-1.5 text-right ${v === 0 ? "text-slate-300" : "text-slate-800"}`}>
|
||||
{v === 0 ? "-" : fmt(v)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>,
|
||||
<tr key={`${it.ITEM_OBJID}-avail`} className="border-b border-slate-100">
|
||||
<td className="px-3 py-1.5 text-center text-[11px] text-emerald-700 bg-emerald-50/40 font-semibold">여유분</td>
|
||||
{warehouses.map((w) => {
|
||||
const v = Number(it.AVAILABLE[w.WH_CODE] ?? 0);
|
||||
const negative = v < 0;
|
||||
return (
|
||||
<td key={w.WH_CODE} className={`px-3 py-1.5 text-right ${negative ? "text-rose-600 font-bold" : v === 0 ? "text-slate-300" : "text-emerald-700 font-semibold"}`}>
|
||||
{v === 0 ? "-" : fmt(v)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>,
|
||||
])}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
/* 보기 2: 헤더=품목(가로), 행=창고 7줄 — 전치 */
|
||||
<table className="text-sm border-collapse">
|
||||
<thead className="bg-slate-50 text-slate-600 sticky top-0">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2 border-b border-slate-200 sticky left-0 bg-slate-50 z-10 min-w-[120px]">창고</th>
|
||||
<th className="text-center px-3 py-2 border-b border-slate-200 sticky left-[120px] bg-slate-50 z-10 min-w-[90px]">분류</th>
|
||||
{items.map((it) => (
|
||||
<th key={it.ITEM_OBJID} className="text-right px-3 py-2 border-b border-slate-200 min-w-[100px] whitespace-nowrap">
|
||||
<div>{it.ITEM_NAME}</div>
|
||||
<div className="text-[10px] text-slate-400 font-mono font-normal">{it.ITEM_CODE}</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="tabular-nums">
|
||||
{items.length === 0 || warehouses.length === 0 ? (
|
||||
<tr><td colSpan={2 + items.length} className="text-center py-12 text-slate-400">
|
||||
{loading ? "조회 중..." : "데이터가 없습니다."}
|
||||
</td></tr>
|
||||
) : warehouses.flatMap((w) => [
|
||||
<tr key={`${w.WH_CODE}-stock`} className="border-t border-slate-100 hover:bg-slate-50/60">
|
||||
<td className="px-3 py-2 align-top font-semibold sticky left-0 bg-white" rowSpan={2}>
|
||||
{w.WH_NAME}
|
||||
<div className="text-[10px] text-slate-400 font-mono">{w.WH_CODE}</div>
|
||||
</td>
|
||||
<td className="px-3 py-1.5 text-center text-[11px] text-slate-700 bg-slate-50/70 sticky left-[120px]">발주수량</td>
|
||||
{items.map((it) => {
|
||||
const v = Number(it.STOCK[w.WH_CODE] ?? 0);
|
||||
return (
|
||||
<td key={it.ITEM_OBJID} className={`px-3 py-1.5 text-right ${v === 0 ? "text-slate-300" : "text-slate-800"}`}>
|
||||
{v === 0 ? "-" : fmt(v)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>,
|
||||
<tr key={`${w.WH_CODE}-avail`} className="border-b border-slate-100">
|
||||
<td className="px-3 py-1.5 text-center text-[11px] text-emerald-700 bg-emerald-50/40 font-semibold sticky left-[120px]">여유분</td>
|
||||
{items.map((it) => {
|
||||
const v = Number(it.AVAILABLE[w.WH_CODE] ?? 0);
|
||||
const negative = v < 0;
|
||||
return (
|
||||
<td key={it.ITEM_OBJID} className={`px-3 py-1.5 text-right ${negative ? "text-rose-600 font-bold" : v === 0 ? "text-slate-300" : "text-emerald-700 font-semibold"}`}>
|
||||
{v === 0 ? "-" : fmt(v)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>,
|
||||
])}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user