- 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
138 lines
4.7 KiB
TypeScript
138 lines
4.7 KiB
TypeScript
// ============================================================
|
|
// Zustand store — دادهها + state UI
|
|
// دادهها از React Query میآیند (api client) و اینجا mirror میشوند؛
|
|
// mutationها اول خوشبینانه اینجا آپدیت میکنند، بعد API را صدا
|
|
// میزنند و آخر cache را invalidate میکنند تا با حقیقت همگرا شود.
|
|
// ============================================================
|
|
|
|
import { create } from "zustand";
|
|
import type {
|
|
CategoryUnit,
|
|
Material,
|
|
Project,
|
|
Quote,
|
|
StandardConversion,
|
|
Store,
|
|
Supplier,
|
|
Topic,
|
|
UnitConversion,
|
|
} from "../lib/types";
|
|
|
|
interface AppStore {
|
|
// دادهها (همگام با API)
|
|
suppliers: Supplier[];
|
|
materials: Material[];
|
|
projects: Project[];
|
|
quotes: Quote[];
|
|
categoryUnits: CategoryUnit[];
|
|
unitConversions: UnitConversion[];
|
|
standardConversions: StandardConversion[];
|
|
topics: Topic[]; // دستهبندیهای ۸گانه — از /material-categories
|
|
|
|
// UI state
|
|
expandedQuoteIds: Set<string>;
|
|
// wizard
|
|
wizardStep: number;
|
|
wizardHighestVisited: number;
|
|
|
|
// actions — data
|
|
setData: (store: Store) => void;
|
|
addQuote: (q: Quote) => void;
|
|
updateQuote: (id: string, patch: Partial<Quote>) => void;
|
|
deleteQuote: (id: string) => void;
|
|
addSupplier: (s: Supplier) => void;
|
|
updateSupplier: (id: string, patch: Partial<Supplier>) => void;
|
|
deleteSupplier: (id: string) => void;
|
|
addMaterial: (m: Material) => void;
|
|
addProject: (p: Project) => void;
|
|
setCategoryUnit: (cu: CategoryUnit) => void;
|
|
setConversion: (c: UnitConversion) => void;
|
|
deleteConversion: (id: string) => void;
|
|
setStandardConversion: (sc: StandardConversion) => void;
|
|
deleteStandardConversion: (id: string) => void;
|
|
|
|
// actions — UI
|
|
toggleExpand: (id: string) => void;
|
|
setWizardStep: (step: number) => void;
|
|
setWizardHighestVisited: (n: number) => void;
|
|
}
|
|
|
|
export const useAppStore = create<AppStore>()((set) => ({
|
|
suppliers: [],
|
|
materials: [],
|
|
projects: [],
|
|
quotes: [],
|
|
categoryUnits: [],
|
|
unitConversions: [],
|
|
standardConversions: [],
|
|
topics: [],
|
|
|
|
expandedQuoteIds: new Set<string>(),
|
|
wizardStep: 1,
|
|
wizardHighestVisited: 1,
|
|
|
|
setData: (store) =>
|
|
set({
|
|
suppliers: store.suppliers,
|
|
materials: store.materials,
|
|
projects: store.projects,
|
|
quotes: store.quotes,
|
|
categoryUnits: store.categoryUnits,
|
|
unitConversions: store.unitConversions,
|
|
standardConversions: store.standardConversions,
|
|
topics: store.topics,
|
|
}),
|
|
addQuote: (q) => set((s) => ({ quotes: [...s.quotes, q] })),
|
|
updateQuote: (id, patch) =>
|
|
set((s) => ({
|
|
quotes: s.quotes.map((q) => (q.id === id ? { ...q, ...patch } : q)),
|
|
})),
|
|
deleteQuote: (id) => set((s) => ({ quotes: s.quotes.filter((q) => q.id !== id) })),
|
|
addSupplier: (sup) => set((s) => ({ suppliers: [...s.suppliers, sup] })),
|
|
updateSupplier: (id, patch) =>
|
|
set((s) => ({
|
|
suppliers: s.suppliers.map((x) => (x.id === id ? { ...x, ...patch } : x)),
|
|
})),
|
|
deleteSupplier: (id) => set((s) => ({ suppliers: s.suppliers.filter((x) => x.id !== id) })),
|
|
addMaterial: (m) => set((s) => ({ materials: [...s.materials, m] })),
|
|
addProject: (p) => set((s) => ({ projects: [...s.projects, p] })),
|
|
setCategoryUnit: (cu) =>
|
|
set((s) => {
|
|
const idx = s.categoryUnits.findIndex((x) => x.id === cu.id);
|
|
const categoryUnits = [...s.categoryUnits];
|
|
if (idx >= 0) categoryUnits[idx] = cu;
|
|
else categoryUnits.push(cu);
|
|
return { categoryUnits };
|
|
}),
|
|
setConversion: (c) =>
|
|
set((s) => {
|
|
const idx = s.unitConversions.findIndex((x) => x.id === c.id);
|
|
const unitConversions = [...s.unitConversions];
|
|
if (idx >= 0) unitConversions[idx] = c;
|
|
else unitConversions.push(c);
|
|
return { unitConversions };
|
|
}),
|
|
deleteConversion: (id) =>
|
|
set((s) => ({ unitConversions: s.unitConversions.filter((x) => x.id !== id) })),
|
|
setStandardConversion: (sc) =>
|
|
set((s) => {
|
|
const idx = s.standardConversions.findIndex((x) => x.id === sc.id);
|
|
const standardConversions = [...s.standardConversions];
|
|
if (idx >= 0) standardConversions[idx] = sc;
|
|
else standardConversions.push(sc);
|
|
return { standardConversions };
|
|
}),
|
|
deleteStandardConversion: (id) =>
|
|
set((s) => ({ standardConversions: s.standardConversions.filter((x) => x.id !== id) })),
|
|
|
|
toggleExpand: (id) =>
|
|
set((s) => {
|
|
const next = new Set(s.expandedQuoteIds);
|
|
if (next.has(id)) next.delete(id);
|
|
else next.add(id);
|
|
return { expandedQuoteIds: next };
|
|
}),
|
|
setWizardStep: (step) => set({ wizardStep: step }),
|
|
setWizardHighestVisited: (n) => set({ wizardHighestVisited: n }),
|
|
}));
|