- All ChangeInventoryPage sections (withdrawal/damage/waste/loss/transfer/ stock-count/project-transfer) become list-based like the withdrawal section: pick a warehouse, see its stock rows, material name is only an optional filter; manual material/brand/unit entry removed everywhere except PURCHASE. - RETURN redesigned: day-range presets (today/yesterday/7d/30d + Jalali custom range) with server-side filters, immutable-ledger returnable remainder per withdrawal (REF:[tx-id] note tag + FIFO attribution for legacy returns, round4 float-safe), destination warehouse selector. - PROJECT_TRANSFER: row-based list with one destination project per batch; sequential submits keep failed rows inline with errors and drop succeeded rows; history dashboard untouched. - PURCHASE: unit locked to catalog (search-only Combobox), wizard-style '+ register new material' option with yellow warning, optional Key-Value specs editor (replaces mandatory text chips), continuous-entry UX (warehouse/date kept, auto-focus, session log, duplicate warning). - Mobile-first: responsive table/cards core (StockTable), numeric keyboards (inputMode+pattern), sticky bottom submit bar with dynamic counter above MobileTabBar incl. safe-area inset, horizontally scrollable touch chips. - Backend fix (prereq of RETURN): GET /inventory/transactions date filters crashed with 500 under asyncpg (string vs timestamptz); parse to aware UTC datetimes (DB runs Etc/UTC). - docs/inventory-ledger-gaps.md: ledger gap report (server-side return cap, standalone TRANSFER_IN, transfer row fallback, audit coverage, legacy REF backfill, UTC day semantics, pagination gaps).
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
// ============================================================
|
|
// جدول «اقلام ثبتشده در این نشست» برای موجودی اولیه
|
|
// بازخورد دیداری برای جلوگیری از ثبت تکراری — فقط حافظهی همان صفحه.
|
|
// ============================================================
|
|
|
|
import { faNum } from "../../../lib/formatters";
|
|
import { specsLine } from "./StockTable";
|
|
|
|
export interface PurchaseSessionEntry {
|
|
material: string;
|
|
brand: string;
|
|
specs: Record<string, string>;
|
|
qty: number;
|
|
unitLabel: string;
|
|
time: string; // HH:MM آمادهی نمایش
|
|
whName: string;
|
|
}
|
|
|
|
interface PurchaseSessionLogProps {
|
|
entries: PurchaseSessionEntry[];
|
|
onClear: () => void;
|
|
}
|
|
|
|
export function PurchaseSessionLog({ entries, onClear }: PurchaseSessionLogProps) {
|
|
if (!entries.length) return null;
|
|
|
|
return (
|
|
<div className="rounded-xl border border-line bg-panel p-3 sm:p-4">
|
|
<div className="mb-2 flex items-center justify-between gap-2 border-b border-line pb-2">
|
|
<h3 className="text-[13px] font-bold">
|
|
🧾 اقلام ثبتشده در این نشست <span className="text-muted font-normal">({faNum(entries.length)} قلم)</span>
|
|
</h3>
|
|
<button
|
|
type="button"
|
|
onClick={onClear}
|
|
className="rounded-lg border border-line px-2.5 py-1.5 text-[11.5px] text-muted hover:bg-bg"
|
|
>
|
|
پاک کردن لیست
|
|
</button>
|
|
</div>
|
|
|
|
{/* دسکتاپ: جدول فشرده */}
|
|
<div className="hidden sm:block">
|
|
<div className="max-h-56 overflow-y-auto rounded-lg border border-line">
|
|
<table className="w-full text-[12px]">
|
|
<thead className="sticky top-0 bg-panel text-right text-muted shadow-2xs">
|
|
<tr>
|
|
<th className="py-2 px-3">متریال</th>
|
|
<th className="py-2 px-3">برند</th>
|
|
<th className="py-2 px-3">مشخصهها</th>
|
|
<th className="py-2 px-3">تعداد</th>
|
|
<th className="py-2 px-3">ساعت</th>
|
|
<th className="py-2 px-3">انبار</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-line/40">
|
|
{[...entries].reverse().map((e, i) => (
|
|
<tr key={i}>
|
|
<td className="py-1.5 px-3 font-semibold text-ink">{e.material}</td>
|
|
<td className="py-1.5 px-3 text-muted">{e.brand || "—"}</td>
|
|
<td className="py-1.5 px-3 text-muted">{specsLine(e.specs)}</td>
|
|
<td className="py-1.5 px-3 font-bold">{faNum(e.qty)} <span className="font-normal text-muted">{e.unitLabel}</span></td>
|
|
<td className="py-1.5 px-3 text-muted">{e.time}</td>
|
|
<td className="py-1.5 px-3 text-muted">{e.whName}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{/* موبایل: ردیفهای فشرده */}
|
|
<div className="sm:hidden max-h-64 space-y-1.5 overflow-y-auto">
|
|
{[...entries].reverse().map((e, i) => (
|
|
<div key={i} className="rounded-lg border border-line/70 bg-bg/40 px-3 py-2 text-[12px]">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<span className="min-w-0 truncate font-bold text-ink">{e.material}</span>
|
|
<span className="shrink-0 font-bold">{faNum(e.qty)} <span className="font-normal text-muted">{e.unitLabel}</span></span>
|
|
</div>
|
|
<div className="mt-0.5 text-[11px] text-muted">
|
|
{e.brand || "بدون برند"} · {specsLine(e.specs)} · {e.time} · {e.whName}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|