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:
@@ -1,8 +1,8 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import type { Material, Supplier } from "../../lib/types";
|
||||
import { TOPICS } from "../../lib/constants";
|
||||
import { normalize } from "../../lib/normalize";
|
||||
import { useSuppliers } from "../../hooks";
|
||||
import { useAppStore } from "../../store";
|
||||
import { TopicGrid } from "../../components/TopicGrid";
|
||||
import { Combobox } from "../../components/Combobox";
|
||||
import { cn } from "../../lib/utils";
|
||||
@@ -32,6 +32,7 @@ export function Step1TopicSupplier({
|
||||
invalid,
|
||||
}: Step1Props) {
|
||||
const suppliers = useSuppliers();
|
||||
const topics = useAppStore((s) => s.topics);
|
||||
const [selectedSupId, setSelectedSupId] = useState<string | null>(null);
|
||||
|
||||
const topicSups = useMemo(
|
||||
@@ -41,11 +42,11 @@ export function Step1TopicSupplier({
|
||||
|
||||
const counts = useMemo(() => {
|
||||
const out: Record<string, number> = {};
|
||||
TOPICS.forEach((t) => {
|
||||
topics.forEach((t) => {
|
||||
out[t.name] = suppliers.filter((s) => normalize(s.topic) === normalize(t.name)).length;
|
||||
});
|
||||
return out;
|
||||
}, [suppliers]);
|
||||
}, [topics, suppliers]);
|
||||
|
||||
// آیا تأمینکنندهی فعلی در لیست هست؟
|
||||
const matched = useMemo(() => {
|
||||
@@ -71,6 +72,7 @@ export function Step1TopicSupplier({
|
||||
اول موضوع کالای موردنیاز را از بین تصاویر انتخاب کنید. تأمینکنندهها بر اساس موضوع فیلتر میشوند.
|
||||
</div>
|
||||
<TopicGrid
|
||||
topics={topics}
|
||||
selectedTopic={selectedTopic}
|
||||
onSelect={onTopicSelect}
|
||||
showCounts
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
useStoreData,
|
||||
useSuppliers,
|
||||
} from "../../hooks";
|
||||
import { TOPICS } from "../../lib/constants";
|
||||
import { topicByName } from "../../lib/topics";
|
||||
import { useAppStore } from "../../store";
|
||||
import { normalize } from "../../lib/normalize";
|
||||
import { faNum } from "../../lib/formatters";
|
||||
import { specsKey } from "../../lib/quoteUtils";
|
||||
@@ -46,6 +47,7 @@ export function WizardPage() {
|
||||
const suppliers = useSuppliers();
|
||||
const materials = useMaterials();
|
||||
const projects = useProjects();
|
||||
const topics = useAppStore((s) => s.topics);
|
||||
const createQuote = useCreateQuote();
|
||||
const addSupplier = useAddSupplier();
|
||||
const addMaterial = useAddMaterial();
|
||||
@@ -86,7 +88,7 @@ export function WizardPage() {
|
||||
useEffect(() => {
|
||||
const t = params.get("topic");
|
||||
if (!t) return;
|
||||
const topic = TOPICS.find((x) => normalize(x.name) === normalize(t));
|
||||
const topic = topicByName(t, topics);
|
||||
if (!topic) return;
|
||||
setSelectedTopic(topic.name);
|
||||
const sid = params.get("supplier");
|
||||
@@ -97,7 +99,7 @@ export function WizardPage() {
|
||||
setSupplierInvalid(false);
|
||||
}
|
||||
}
|
||||
}, [params, suppliers]);
|
||||
}, [params, suppliers, topics]);
|
||||
|
||||
// ---------- اعتبارسنجی قبل از ترک هر مرحله ----------
|
||||
// مراحل تکمیلشده را بر اساس دادههای فعلی مشخص میکند
|
||||
|
||||
Reference in New Issue
Block a user