@eintrek/erp-shell 0.1.35 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -382,7 +382,22 @@ function useInvalidateQueriesOnOrgChange(organizationId) {
382
382
  // still hit the invalidate path below.
383
383
  if (wasEmpty)
384
384
  return;
385
- queryClient.invalidateQueries();
385
+ // Remove, not invalidate.
386
+ //
387
+ // invalidateQueries marks entries stale and refetches the mounted ones,
388
+ // which leaves the previous organisation's rows sitting in the cache and
389
+ // on screen until a refetch lands. Any query whose key does not carry the
390
+ // organisation — and the request is scoped by the X-Organization-ID
391
+ // header, so most keys have no reason to — then reads as a cache hit for
392
+ // the new organisation and renders the old one's data under the new
393
+ // one's name. A colleague found /O26000021/employee showing three
394
+ // employees belonging to O26000033, with the breadcrumb and the title
395
+ // both correct.
396
+ //
397
+ // Dropping the entries costs a loading state after every switch, which is
398
+ // the honest thing to show: nothing fetched under one tenant is worth
399
+ // keeping once the tenant changes.
400
+ queryClient.removeQueries();
386
401
  }, [organizationId, queryClient]);
387
402
  }
388
403
  /**
@@ -1133,6 +1148,23 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1133
1148
  lastOrgKeyRef.current = currentOrgKey;
1134
1149
  }
1135
1150
  const contentOrgKey = lastOrgKeyRef.current;
1151
+ // Hold the page back until the active organisation is the one the URL names.
1152
+ //
1153
+ // The sync effect above writes the cookie through a server action, which is
1154
+ // a round trip. Until it lands, getOrgFromCookie() still answers with the
1155
+ // previous organisation and every request the page fires on mount carries
1156
+ // it — so /O26000021/work-record asked about O26000033 and was correctly
1157
+ // told there was nothing there. An empty table is the mild version: the
1158
+ // create form posts to the same header, so a record could be filed against
1159
+ // a company the user was not looking at.
1160
+ //
1161
+ // Only while the two genuinely disagree. A code naming no organisation of
1162
+ // the caller's is left to the app to refuse, and a page with no code in its
1163
+ // path is unaffected.
1164
+ const orgContextIsStale = !!orgFromCode &&
1165
+ organizationId !== null &&
1166
+ organizationId !== undefined &&
1167
+ String(organizationId) !== String(orgFromCode.id);
1136
1168
  // ≤1024px: sidebar renders as a modal overlay (backdrop dims content). >1024px:
1137
1169
  // sidebar is docked and the content gets a left margin to clear it.
1138
1170
  const [isSidebarModalLayout, setIsSidebarModalLayout] = React__namespace.useState(false);
@@ -1179,7 +1211,7 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
1179
1211
  const contentMargin = open && !isSidebarModalLayout ? "ml-[21.5rem]" : "";
1180
1212
  const needsLeftGutter = !(open && !isSidebarModalLayout);
1181
1213
  const wrap = permissionProvider ?? ((c) => c);
1182
- return (jsxRuntime.jsxs("div", { className: "flex min-h-screen w-full bg-background", children: [open && (jsxRuntime.jsx("div", { className: "fixed left-0 top-0 z-50 h-screen", children: sidebar })), open && isSidebarModalLayout && (jsxRuntime.jsx("button", { type: "button", "aria-label": "\u0E1B\u0E34\u0E14\u0E40\u0E21\u0E19\u0E39", className: "fixed inset-0 z-40 bg-black/50", onClick: () => setOpen(false) })), jsxRuntime.jsx("div", { className: cx("relative flex min-w-0 flex-1 flex-col transition-all", contentMargin), children: jsxRuntime.jsxs("main", { className: "flex flex-1 flex-col min-h-screen gap-4", children: [jsxRuntime.jsxs("div", { className: "sticky top-0 z-30 shrink-0 w-full", children: [jsxRuntime.jsx(HeaderBackground, {}), jsxRuntime.jsxs("header", { className: "relative shrink-0 gap-2 py-4 h-auto min-h-[123.5px] w-full z-10", children: [jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2 px-3 sm:px-4 lg:px-6", children: [jsxRuntime.jsx(SidebarTrigger, { className: "shrink-0 text-white hover:bg-white/10" }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: breadcrumb })] }), jsxRuntime.jsx("div", { id: "header-title" })] })] }), jsxRuntime.jsx(React.Suspense, { fallback: jsxRuntime.jsx("div", { className: "relative z-10 min-h-[calc(100vh-8rem)]", children: jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: jsxRuntime.jsxs("div", { className: "space-y-4 text-center", children: [jsxRuntime.jsx("div", { className: "mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-primary" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..." })] }) }) }), children: jsxRuntime.jsx("div", { className: cx("relative z-10 pr-6", needsLeftGutter && "pl-6"), children: wrap(children) }, `org-${contentOrgKey}`) })] }) })] }));
1214
+ return (jsxRuntime.jsxs("div", { className: "flex min-h-screen w-full bg-background", children: [open && (jsxRuntime.jsx("div", { className: "fixed left-0 top-0 z-50 h-screen", children: sidebar })), open && isSidebarModalLayout && (jsxRuntime.jsx("button", { type: "button", "aria-label": "\u0E1B\u0E34\u0E14\u0E40\u0E21\u0E19\u0E39", className: "fixed inset-0 z-40 bg-black/50", onClick: () => setOpen(false) })), jsxRuntime.jsx("div", { className: cx("relative flex min-w-0 flex-1 flex-col transition-all", contentMargin), children: jsxRuntime.jsxs("main", { className: "flex flex-1 flex-col min-h-screen gap-4", children: [jsxRuntime.jsxs("div", { className: "sticky top-0 z-30 shrink-0 w-full", children: [jsxRuntime.jsx(HeaderBackground, {}), jsxRuntime.jsxs("header", { className: "relative shrink-0 gap-2 py-4 h-auto min-h-[123.5px] w-full z-10", children: [jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2 px-3 sm:px-4 lg:px-6", children: [jsxRuntime.jsx(SidebarTrigger, { className: "shrink-0 text-white hover:bg-white/10" }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: breadcrumb })] }), jsxRuntime.jsx("div", { id: "header-title" })] })] }), jsxRuntime.jsx(React.Suspense, { fallback: jsxRuntime.jsx("div", { className: "relative z-10 min-h-[calc(100vh-8rem)]", children: jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: jsxRuntime.jsxs("div", { className: "space-y-4 text-center", children: [jsxRuntime.jsx("div", { className: "mx-auto h-8 w-8 animate-spin rounded-full border-b-2 border-primary" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..." })] }) }) }), children: jsxRuntime.jsx("div", { className: cx("relative z-10 pr-6", needsLeftGutter && "pl-6"), children: orgContextIsStale ? loadingComponent : wrap(children) }, `org-${contentOrgKey}`) })] }) })] }));
1183
1215
  }
1184
1216
  /** Local tiny classnames helper — avoids pulling clsx/tailwind-merge into
1185
1217
  * the shell just for the layout file. Falsy values are skipped. */