feat(sync): live cross-module cache invalidation (no manual refresh)

- procurement store refreshes right after a warehouse receipt (arrivals) and
  after role/project changes (organization/admin), so the ledger reflects
  warehouse actions immediately
- refetchOnWindowFocus enabled on the shared query client
- BroadcastChannel (lib/crossTab.ts): mutations broadcast 'quotes' /
  'inventory'; all warehouse pages + the store hook resync open tabs
  automatically (receipt in one tab updates the ledger in another)
- warehouse mutations (stock changes) notify other tabs; same-page refresh
  behavior unchanged
This commit is contained in:
ali_mohaghegh
2026-09-03 15:00:58 +03:30
parent f9ec68815e
commit e4ae34ddbc
9 changed files with 122 additions and 43 deletions
+23 -6
View File
@@ -4,12 +4,16 @@
import { useEffect } from "react";
import { useCallback } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import { api } from "../lib/api";
import { useAppStore } from "../store";
import { notifyDataChanged, useDataChanged } from "../lib/crossTab";
import { queryClient } from "../lib/queryClient";
export const STORE_KEY = ["store"] as const;
export { queryClient };
// یک بار در ریشه‌ی برنامه صدا زده می‌شود: لود + mirror در Zustand
export function useStoreData(): void {
const setData = useAppStore((s) => s.setData);
@@ -21,12 +25,25 @@ export function useStoreData(): void {
useEffect(() => {
if (data) setData(data);
}, [data, setData]);
// تب‌های دیگر (رسید انبار / تغییر نقش و...) → بازخوانی خودکار store
useDataChanged(
useCallback(() => {
void queryClient.invalidateQueries({ queryKey: STORE_KEY });
}, []),
["quotes"],
);
}
// بعد از هر mutation برای همگرایی با حقیقت
// بعد از هر mutation برای همگرایی با حقیقت (+ اعلام به تب‌های دیگر)
export function useInvalidateStore(): () => void {
const qc = useQueryClient();
return useCallback(() => {
void qc.invalidateQueries({ queryKey: STORE_KEY });
}, [qc]);
return useCallback(refreshProcurementStore, []);
}
// نسخه‌ی تابعی (خارج از کامپوننت هم قابل صدا زدن) — بعد از تغییر داده‌های
// مرتبط با پیشنهادها (مثلاً رسید انبار) خودِ store را تازه می‌کند تا دفتر
// بدون refresh دستی به‌روز شود؛ و به تب‌های دیگر هم اعلام می‌کند.
export function refreshProcurementStore(): void {
void queryClient.invalidateQueries({ queryKey: STORE_KEY });
notifyDataChanged("quotes");
}