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:
@@ -0,0 +1,54 @@
|
||||
// ============================================================
|
||||
// همگامسازی بین تبها (BroadcastChannel) — بعد از هر mutation که روی
|
||||
// دادههای «پیشنهادها» یا «انبار» اثر میگذارد، به تبهای دیگر اعلام میشود
|
||||
// تا آنها هم دادههای خود را تازه کنند (بدون نیاز به refresh دستی).
|
||||
// BroadcastChannel در خودِ همان تب پیام را نمیدهد — همتبهها همانجا
|
||||
// refresh میشوند و این فقط برای تبهای دیگر است.
|
||||
// ============================================================
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
const CHANNEL = "procurement-data-sync";
|
||||
|
||||
export type DataScope = "quotes" | "inventory";
|
||||
|
||||
let _channel: BroadcastChannel | null = null;
|
||||
|
||||
function channel(): BroadcastChannel | null {
|
||||
if (typeof BroadcastChannel === "undefined") return null;
|
||||
if (!_channel) {
|
||||
try {
|
||||
_channel = new BroadcastChannel(CHANNEL);
|
||||
} catch {
|
||||
_channel = null;
|
||||
}
|
||||
}
|
||||
return _channel;
|
||||
}
|
||||
|
||||
/** بعد از تغییر داده در این تب، به بقیهی تبها اعلام کن. */
|
||||
export function notifyDataChanged(scope: DataScope): void {
|
||||
const ch = channel();
|
||||
if (!ch) return;
|
||||
try {
|
||||
ch.postMessage({ scope });
|
||||
} catch {
|
||||
/* نادیده — پیام فقط بهروزرسانی است */
|
||||
}
|
||||
}
|
||||
|
||||
/** گوش دادن به تغییرات تبهای دیگر (scope دلخواه یا همه). */
|
||||
export function useDataChanged(onEvent: (scope: DataScope) => void, scopes?: DataScope[]): void {
|
||||
useEffect(() => {
|
||||
const ch = channel();
|
||||
if (!ch) return;
|
||||
const handler = (ev: MessageEvent) => {
|
||||
const scope = (ev.data as { scope?: DataScope } | null)?.scope;
|
||||
if (!scope) return;
|
||||
if (scopes && !scopes.includes(scope)) return;
|
||||
onEvent(scope);
|
||||
};
|
||||
ch.addEventListener("message", handler);
|
||||
return () => ch.removeEventListener("message", handler);
|
||||
}, [onEvent, scopes?.join(",")]);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// ============================================================
|
||||
// TanStack Query client — مشترک در کل برنامه (و helperهای سوییچر ماژولها)
|
||||
// ============================================================
|
||||
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
// بازخوانی هنگام بازگشت به تب — تا دادههای ماژول دیگر تازه بماند
|
||||
refetchOnWindowFocus: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user