feat(frontend): fulfillment split, API-driven topics, hub-and-spoke nav, stock-count modal, FOUND ceiling, semantic colors

- bug1: quote header fulfillment counter («رسید: X از Y قلم» amber / «تکمیل
  شد» green / «در راه»); expanded line items visually split into the
  received part (رسید شده ✓ + date + target warehouse from the receipt
  allocations) and the in-transit remainder — quantities always sum to the
  original line (invariant D holds, display-only split)
- bug2: 8-topic grids (suppliers/wizard/units) now come from
  /material-categories via the store (new lib/topics.ts; fixed the mismatched
  «ابزار و تجهیزات ساخت» label); suppliers topic->category resolution no
  longer falls back to an arbitrary category; duplicate month/status lists
  reuse the shared dictionaries; TopicGrid gained a loading EmptyState
- bug3: hub-and-spoke — the tab strip renders only on the warehouse
  dashboard; sub-views hide it and navigate back via «بازگشت به داشبورد»;
  «افزایش موجود» became the «تغییر موجودی» entry button
- bug4: stock count goes through a confirmation modal (current vs counted
  vs variance with کسری/مازاد badges) before posting STOCK_COUNT; numbers
  parsed defensively
- bug5: found qty is capped client-side (red input + inline warning, submit
  disabled) and backend returns 422 when exceeding the missing balance;
  fully-recovered losses drop off the pending list
- bug6+7: quantities render as absolute values with semantic color coding
  (emerald inflow / rose outflow / blue neutral) across dashboard timeline,
  daily control and the change-inventory form
This commit is contained in:
ali_mohaghegh
2026-09-02 18:15:32 +03:30
parent ca84d8664e
commit ac76af2ffe
27 changed files with 500 additions and 217 deletions
+2 -34
View File
@@ -1,37 +1,8 @@
// ============================================================
// ثابت‌های سراسری
// ثابت‌های سراسری — برچسب‌های UI ثابت (وضعیت‌ها و ...)
// تاپیک‌ها/دسته‌ها اینجا نیستند — از API می‌آیند (lib/topics.ts)
// ============================================================
import { normalize } from "./normalize";
export interface Topic {
key: string;
name: string;
icon: string;
}
// ۸ تاپیک ثابت — HVAC و لوله‌کشی ادغام شدند؛ ابزار و تجهیزات ساخت اضافه شد
export const TOPICS: Topic[] = [
{ key: "hvac-plumb", name: "تاسیسات مکانیکی و آبی", icon: "/icons/topic-1.png" },
{ key: "electric", name: "تجهیزات و لوازم برقی", icon: "/icons/topic-2.png" },
{ key: "door-win", name: "درب، پنجره و شیشه", icon: "/icons/topic-3.png" },
{ key: "metal", name: "فلزات و مقاطع فولادی", icon: "/icons/topic-4.png" },
{ key: "build-mat", name: "مصالح ساختمانی", icon: "/icons/topic-6.png" },
{ key: "chem", name: "مواد شیمیایی ساختمانی", icon: "/icons/topic-7.png" },
{ key: "finish", name: "نازک‌کاری و دکوراسیون داخلی", icon: "/icons/topic-8.png" },
{ key: "tools", name: "ابزار و تجهیزات ساخت", icon: "/icons/topic-9.png" },
];
function topicEq(a: string, b: string): boolean {
return normalize(a) === normalize(b);
}
// یافتن تاپیک بر اساس نام (نرمال‌شده)
export function topicByName(name: string | null | undefined): Topic | null {
if (!name) return null;
return TOPICS.find((t) => topicEq(t.name, name)) || null;
}
export const STATUS_LABEL: Record<string, string> = {
offered: "پیشنهاد شده",
accepted: "پذیرفته شده",
@@ -47,6 +18,3 @@ export function statusOptionsFor(current: string): string[] {
const idx = STATUS_ORDER.indexOf(current);
return idx < 0 ? STATUS_ORDER : STATUS_ORDER.slice(0, idx + 1);
}
// واحدهای شناخته‌شده از بک‌اند خوانده می‌شوند — این لیست حذف شد
// (واحدها فقط در دیتابیس تعریف می‌شوند و واحد جدید هنگام تایپ ساخته می‌شود)