feat(frontend): add React UI — procurement office, wizard, suppliers, inventory panel (dash port), role-based login

This commit is contained in:
ali_mohaghegh
2026-08-25 19:08:15 +03:30
parent 170aed66ea
commit 4e105c5cd5
103 changed files with 16352 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
export { useStoreData, useInvalidateStore } from "./useStoreData";
export { useQuotes, useCreateQuote, useUpdateQuote, useDeleteQuote } from "./useQuotes";
export { useSuppliers, useAddSupplier, useUpdateSupplier, useRemoveSupplier } from "./useSuppliers";
export { useMaterials, useAddMaterial } from "./useMaterials";
export { useProjects, useAddProject } from "./useProjects";
export {
useUnits,
useSaveRefUnit,
useSaveConversion,
useDeleteConversion,
useSaveStandardConversion,
useDeleteStandardConversion,
} from "./useUnits";
+23
View File
@@ -0,0 +1,23 @@
// ============================================================
// hooks مواد (کالاها) — دسته‌ی جدید هم یک material است
// ============================================================
import { useMutation } from "@tanstack/react-query";
import type { Material } from "../lib/types";
import { api } from "../lib/api";
import { useAppStore } from "../store";
import { useInvalidateStore } from "./useStoreData";
export function useMaterials(): Material[] {
return useAppStore((s) => s.materials);
}
export function useAddMaterial() {
const addMaterial = useAppStore((s) => s.addMaterial);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (m: Material) => api.materials.create(m),
onMutate: (m) => addMaterial(m),
onSettled: () => invalidate(),
});
}
+23
View File
@@ -0,0 +1,23 @@
// ============================================================
// hooks پروژه‌ها
// ============================================================
import { useMutation } from "@tanstack/react-query";
import type { Project } from "../lib/types";
import { api } from "../lib/api";
import { useAppStore } from "../store";
import { useInvalidateStore } from "./useStoreData";
export function useProjects(): Project[] {
return useAppStore((s) => s.projects);
}
export function useAddProject() {
const addProject = useAppStore((s) => s.addProject);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (p: Project) => api.projects.create(p),
onMutate: (p) => addProject(p),
onSettled: () => invalidate(),
});
}
+44
View File
@@ -0,0 +1,44 @@
// ============================================================
// hooks پیشنهادها — خواندن + mutationهای خوش‌بینانه
// ============================================================
import { useMutation } from "@tanstack/react-query";
import type { Quote } from "../lib/types";
import { api } from "../lib/api";
import { useAppStore } from "../store";
import { useInvalidateStore } from "./useStoreData";
export function useQuotes(): Quote[] {
return useAppStore((s) => s.quotes);
}
export function useCreateQuote() {
const addQuote = useAppStore((s) => s.addQuote);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (q: Quote) => api.quotes.create(q),
onMutate: (q) => addQuote(q),
onSettled: () => invalidate(),
});
}
export function useUpdateQuote() {
const updateQuote = useAppStore((s) => s.updateQuote);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (args: { id: string; patch: Partial<Quote> }) =>
api.quotes.update(args.id, args.patch),
onMutate: (args) => updateQuote(args.id, args.patch),
onSettled: () => invalidate(),
});
}
export function useDeleteQuote() {
const deleteQuote = useAppStore((s) => s.deleteQuote);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (id: string) => api.quotes.delete(id),
onMutate: (id) => deleteQuote(id),
onSettled: () => invalidate(),
});
}
+32
View File
@@ -0,0 +1,32 @@
// ============================================================
// پایه‌ی داده: یک query واحد که کل store را لود می‌کند
// ============================================================
import { useEffect } from "react";
import { useCallback } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { api } from "../lib/api";
import { useAppStore } from "../store";
export const STORE_KEY = ["store"] as const;
// یک بار در ریشه‌ی برنامه صدا زده می‌شود: لود + mirror در Zustand
export function useStoreData(): void {
const setData = useAppStore((s) => s.setData);
const { data } = useQuery({
queryKey: STORE_KEY,
queryFn: () => api.listAll(),
staleTime: Infinity,
});
useEffect(() => {
if (data) setData(data);
}, [data, setData]);
}
// بعد از هر mutation برای همگرایی با حقیقت
export function useInvalidateStore(): () => void {
const qc = useQueryClient();
return useCallback(() => {
void qc.invalidateQueries({ queryKey: STORE_KEY });
}, [qc]);
}
+43
View File
@@ -0,0 +1,43 @@
// ============================================================
// hooks تأمین‌کننده‌ها و دسته‌ها
// ============================================================
import { useMutation } from "@tanstack/react-query";
import type { Supplier } from "../lib/types";
import { api } from "../lib/api";
import { useAppStore } from "../store";
import { useInvalidateStore } from "./useStoreData";
export function useSuppliers(): Supplier[] {
return useAppStore((s) => s.suppliers);
}
export function useAddSupplier() {
const addSupplier = useAppStore((s) => s.addSupplier);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (s: Supplier) => api.suppliers.create(s),
onMutate: (s) => addSupplier(s),
onSettled: () => invalidate(),
});
}
export function useUpdateSupplier() {
const updateSupplier = useAppStore((s) => s.updateSupplier);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (args: { id: string; patch: Partial<Supplier> }) => api.suppliers.update(args.id, args.patch),
onMutate: (args) => updateSupplier(args.id, args.patch),
onSettled: () => invalidate(),
});
}
export function useRemoveSupplier() {
const deleteSupplier = useAppStore((s) => s.deleteSupplier);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (id: string) => api.suppliers.delete(id),
onMutate: (id) => deleteSupplier(id),
onSettled: () => invalidate(),
});
}
+70
View File
@@ -0,0 +1,70 @@
// ============================================================
// hooks واحدها — واحد مرجع، ضرایب دسته‌ای و ثابت
// ============================================================
import { useMutation } from "@tanstack/react-query";
import type { CategoryUnit, StandardConversion, UnitConversion } from "../lib/types";
import { api } from "../lib/api";
import { useAppStore } from "../store";
import { useInvalidateStore } from "./useStoreData";
export function useUnits(): {
categoryUnits: CategoryUnit[];
unitConversions: UnitConversion[];
standardConversions: StandardConversion[];
} {
const categoryUnits = useAppStore((s) => s.categoryUnits);
const unitConversions = useAppStore((s) => s.unitConversions);
const standardConversions = useAppStore((s) => s.standardConversions);
return { categoryUnits, unitConversions, standardConversions };
}
export function useSaveRefUnit() {
const setCategoryUnit = useAppStore((s) => s.setCategoryUnit);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (cu: CategoryUnit) => api.units.saveCategoryUnit(cu),
onMutate: (cu) => setCategoryUnit(cu),
onSettled: () => invalidate(),
});
}
export function useSaveConversion() {
const setConversion = useAppStore((s) => s.setConversion);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (c: UnitConversion) => api.units.saveConversion(c),
onMutate: (c) => setConversion(c),
onSettled: () => invalidate(),
});
}
export function useDeleteConversion() {
const deleteConversion = useAppStore((s) => s.deleteConversion);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (id: string) => api.units.deleteConversion(id),
onMutate: (id) => deleteConversion(id),
onSettled: () => invalidate(),
});
}
export function useSaveStandardConversion() {
const setStandardConversion = useAppStore((s) => s.setStandardConversion);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (sc: StandardConversion) => api.units.saveStandardConversion(sc),
onMutate: (sc) => setStandardConversion(sc),
onSettled: () => invalidate(),
});
}
export function useDeleteStandardConversion() {
const deleteStandardConversion = useAppStore((s) => s.deleteStandardConversion);
const invalidate = useInvalidateStore();
return useMutation({
mutationFn: (id: string) => api.units.deleteStandardConversion(id),
onMutate: (id) => deleteStandardConversion(id),
onSettled: () => invalidate(),
});
}