@eintrek/erp-shell 0.1.58 → 0.1.60
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/help/help-store.d.ts +1 -1
- package/dist/hooks/use-organization-id-state.d.ts +5 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +392 -134
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +395 -130
- package/dist/index.js.map +1 -1
- package/dist/lib/plan-features.d.ts +4 -0
- package/dist/navigation/app-layout-shell.d.ts +16 -1
- package/dist/navigation/app-registry.d.ts +1 -1
- package/dist/navigation/nav-row.d.ts +24 -0
- package/dist/navigation/sidebar-components.d.ts +5 -0
- package/dist/navigation/url-org-gate.d.ts +56 -0
- package/dist/organization/active-organization.d.ts +33 -0
- package/dist/providers/query-scope.d.ts +25 -0
- package/dist/providers/react-query-config.d.ts +3 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -19,6 +19,10 @@ export declare const PLAN_FEATURES: {
|
|
|
19
19
|
readonly MODULE_INVENTORY: "module.inventory";
|
|
20
20
|
readonly MODULE_PROJECT: "module.project";
|
|
21
21
|
readonly MODULE_SECRETARIAT: "module.secretariat";
|
|
22
|
+
/** Sellx add-on: POS, members and chat commerce. */
|
|
23
|
+
readonly MODULE_SELLX: "module.sellx";
|
|
24
|
+
/** Carex add-on: help desk, SLA and knowledge base. */
|
|
25
|
+
readonly MODULE_CAREX: "module.carex";
|
|
22
26
|
/** AI pack (paid or trial): AI calls on AntNiti's key while credits remain. */
|
|
23
27
|
readonly AI: "ai";
|
|
24
28
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type ShellChrome, type WorkspaceHeaderTone } from "./chrome";
|
|
3
3
|
import { type HelpAppId } from "../help/help-store";
|
|
4
|
+
import { type OrganizationsStatus } from "./url-org-gate";
|
|
4
5
|
/**
|
|
5
6
|
* Shape the shell needs from an organization to wire the URL `[code]` segment
|
|
6
7
|
* back to the active org id. Hosts already keep richer types around — this is
|
|
@@ -40,6 +41,20 @@ export interface AppLayoutShellProps {
|
|
|
40
41
|
setOrganizationId: (id: string | number | null) => Promise<void> | void;
|
|
41
42
|
/** Orgs the user belongs to — used to resolve URL `[code]` → org `id`. */
|
|
42
43
|
organizations: AppLayoutShellOrganization[];
|
|
44
|
+
/**
|
|
45
|
+
* `status` of the query behind `organizations`. With it the page waits for
|
|
46
|
+
* the list before it mounts, and a code that is not among the user's
|
|
47
|
+
* organisations is refused. Without it the gate starts only once the list
|
|
48
|
+
* is non-empty, so requests made while it loads carry the previous
|
|
49
|
+
* organisation. Every host should pass it.
|
|
50
|
+
*/
|
|
51
|
+
organizationsStatus?: OrganizationsStatus;
|
|
52
|
+
/**
|
|
53
|
+
* Shown in the content area when the URL's code is not one of the user's
|
|
54
|
+
* organisations. Defaults to AccessDenied with a way to pick an
|
|
55
|
+
* organisation.
|
|
56
|
+
*/
|
|
57
|
+
notMemberComponent?: React.ReactNode;
|
|
43
58
|
/** Absolute path to redirect to when the session has no accessToken (e.g.
|
|
44
59
|
* token refresh failed). The shell appends `?error=NoAccessToken&callbackUrl=…`. */
|
|
45
60
|
signInUrl: string;
|
|
@@ -84,4 +99,4 @@ export interface AppLayoutShellProps {
|
|
|
84
99
|
* an erp-shell bump — domain components must NOT add their own root padding
|
|
85
100
|
* or content gets indented twice.
|
|
86
101
|
*/
|
|
87
|
-
export declare function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permissionProvider, organizationId, setOrganizationId, organizations, signInUrl, selectOrganizationUrl, chrome, workspaceHeader, headerEnd, mobileBrand, showHelp, helpApp, }: AppLayoutShellProps): import("react/jsx-runtime").JSX.Element;
|
|
102
|
+
export declare function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permissionProvider, organizationId, setOrganizationId, organizations, organizationsStatus, notMemberComponent, signInUrl, selectOrganizationUrl, chrome, workspaceHeader, headerEnd, mobileBrand, showHelp, helpApp, }: AppLayoutShellProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NavUserSystemLink } from "./nav-user";
|
|
2
2
|
/** The apps a signed-in user can jump between from the "แอปพลิเคชัน" menu. */
|
|
3
|
-
export type ErpAppId = "platform" | "accounx" | "invenx" | "plannex" | "peoplx";
|
|
3
|
+
export type ErpAppId = "platform" | "accounx" | "invenx" | "plannex" | "peoplx" | "sellx" | "carex";
|
|
4
4
|
export interface BuildSystemLinksOptions {
|
|
5
5
|
/** Organisation code from the route. Without it there is nowhere to link to. */
|
|
6
6
|
orgCode?: string | null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Module } from "./navigation-types";
|
|
2
|
+
/**
|
|
3
|
+
* How a sidebar row looks, apart from its layout.
|
|
4
|
+
*
|
|
5
|
+
* Exactly one row is lit — the page's own (resolveSidebarNav). Until 0.1.60 a
|
|
6
|
+
* row under the pointer took the lit row's fill and colour, and an open group
|
|
7
|
+
* (or a one-line row once clicked, which was a collapsible too) took its
|
|
8
|
+
* colour, so the menu showed several "current pages" at once.
|
|
9
|
+
*/
|
|
10
|
+
/** A row under the pointer: a faint neutral tint, never the lit row's look. */
|
|
11
|
+
export declare const NAV_ROW_HOVER = "hover:bg-sidebar-foreground/5 hover:text-sidebar-foreground";
|
|
12
|
+
/** The lit row: the accent fill and text. The pointer changes nothing. */
|
|
13
|
+
export declare const NAV_ROW_LIT = "bg-sidebar-accent text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground";
|
|
14
|
+
/** Fill and colour for a row, lit or not. */
|
|
15
|
+
export declare function navRowTone(lit: boolean): string;
|
|
16
|
+
/**
|
|
17
|
+
* A group opens and shuts; any other row is a plain link with no open state.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isNavGroup(item: Pick<Module, "subModules">): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The open groups after `id` is opened or shut. An id that is not a group's
|
|
22
|
+
* is ignored — a one-line row has nothing to open.
|
|
23
|
+
*/
|
|
24
|
+
export declare function setNavGroupOpen(openIds: ReadonlySet<string>, items: ReadonlyArray<Pick<Module, "id" | "subModules">>, id: string, isOpen: boolean): ReadonlySet<string>;
|
|
@@ -16,6 +16,11 @@ export declare const SidebarMenuItem: React.ForwardRefExoticComponent<Omit<React
|
|
|
16
16
|
export declare const SidebarMenuButton: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLButtonElement> & React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
17
17
|
asChild?: boolean;
|
|
18
18
|
isActive?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The row's name beside it, for a row that shows only its icon — the
|
|
21
|
+
* theme's tooltip on the right, never a native `title` (which the
|
|
22
|
+
* browser drops wherever it likes, over the logo included).
|
|
23
|
+
*/
|
|
19
24
|
tooltip?: string;
|
|
20
25
|
size?: "sm" | "default" | "lg";
|
|
21
26
|
}, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What AppLayoutShell does with the page while the organisation in the URL and
|
|
3
|
+
* the tab's active organisation are being reconciled. Pure, so it is tested
|
|
4
|
+
* without React (url-org-gate.test.ts).
|
|
5
|
+
*
|
|
6
|
+
* The URL's `[code]` is authoritative. The page — and with it every request
|
|
7
|
+
* the page makes — mounts only once the active organisation IS the one the
|
|
8
|
+
* URL names:
|
|
9
|
+
*
|
|
10
|
+
* ready the active organisation is the URL's: render the page.
|
|
11
|
+
* resolving the caller's organisations are still loading, so the code
|
|
12
|
+
* cannot be resolved yet: hold the page.
|
|
13
|
+
* switching the code is one of the caller's organisations but not the
|
|
14
|
+
* active one: switch to it (`targetId`), hold the page meanwhile.
|
|
15
|
+
* not-member the list loaded and the code is not in it: refuse.
|
|
16
|
+
* unverified the list could not be loaded: render as before the gate
|
|
17
|
+
* existed (the API still scopes every request), rather than
|
|
18
|
+
* leave the page on a loader for good.
|
|
19
|
+
*/
|
|
20
|
+
export interface UrlOrgGateOrganization {
|
|
21
|
+
id: string | number;
|
|
22
|
+
code?: string | null;
|
|
23
|
+
}
|
|
24
|
+
/** react-query's `status` of the host's organisations query. */
|
|
25
|
+
export type OrganizationsStatus = "pending" | "success" | "error";
|
|
26
|
+
export type UrlOrgGate = {
|
|
27
|
+
state: "ready";
|
|
28
|
+
targetId: string | null;
|
|
29
|
+
} | {
|
|
30
|
+
state: "resolving";
|
|
31
|
+
} | {
|
|
32
|
+
state: "switching";
|
|
33
|
+
targetId: string;
|
|
34
|
+
} | {
|
|
35
|
+
state: "not-member";
|
|
36
|
+
} | {
|
|
37
|
+
state: "unverified";
|
|
38
|
+
};
|
|
39
|
+
export interface UrlOrgGateInput {
|
|
40
|
+
/** The URL's `[code]` segment. */
|
|
41
|
+
code: string | undefined;
|
|
42
|
+
/** The caller's organisations. */
|
|
43
|
+
organizations: readonly UrlOrgGateOrganization[];
|
|
44
|
+
/**
|
|
45
|
+
* Status of the query that produced `organizations`. A host that does not
|
|
46
|
+
* pass it gets the gate only once the list is non-empty: an empty list then
|
|
47
|
+
* cannot be told apart from "still loading" or "failed", and holding the
|
|
48
|
+
* page on that would strand a host that never loads one.
|
|
49
|
+
*/
|
|
50
|
+
organizationsStatus?: OrganizationsStatus;
|
|
51
|
+
/** The active organisation (the host's ERP context). */
|
|
52
|
+
organizationId: string | number | null | undefined;
|
|
53
|
+
}
|
|
54
|
+
/** The organisation a `[code]` segment names, among the caller's. */
|
|
55
|
+
export declare function findOrganizationForCode<T extends UrlOrgGateOrganization>(code: string | undefined, organizations: readonly T[]): T | null;
|
|
56
|
+
export declare function resolveUrlOrgGate({ code, organizations, organizationsStatus, organizationId, }: UrlOrgGateInput): UrlOrgGate;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The organisation this browser tab is working in.
|
|
3
|
+
*
|
|
4
|
+
* Every request carries an X-Organization-ID header, and every cached query
|
|
5
|
+
* belongs to one organisation. Both used to come from the `erp.organizationId`
|
|
6
|
+
* cookie (then localStorage), which is the organisation the BROWSER last had
|
|
7
|
+
* active — shared by every tab and, on antniti.com, by every app. A tab
|
|
8
|
+
* showing /O26000021/… therefore sent whatever another tab, or another app,
|
|
9
|
+
* had picked last, and the URL lost.
|
|
10
|
+
*
|
|
11
|
+
* The tab's own value is set by `useOrganizationIdState` — the state that
|
|
12
|
+
* AppLayoutShell drives to the organisation the URL names — and read first by
|
|
13
|
+
* `getRequestOrganizationId()`. Until a tab has one (a cold load before the
|
|
14
|
+
* provider mounts), localStorage and then the cookie answer as before.
|
|
15
|
+
*
|
|
16
|
+
* Module state, so browser-only: on the server one module instance serves
|
|
17
|
+
* every request, and a value written there would leak between users.
|
|
18
|
+
*/
|
|
19
|
+
/** The organisation this tab is working in, or null before it has one. */
|
|
20
|
+
export declare function getTabOrganizationId(): string | null;
|
|
21
|
+
/** Record the organisation this tab is working in (browser only). */
|
|
22
|
+
export declare function setTabOrganizationId(id: string | number | null | undefined): void;
|
|
23
|
+
/**
|
|
24
|
+
* The organisation a request from this tab belongs to: the tab's own, then
|
|
25
|
+
* localStorage, then the cookie. Use it for the X-Organization-ID header —
|
|
26
|
+
* and read it before any `await` in the interceptor, so the header names the
|
|
27
|
+
* organisation that was active when the request was made.
|
|
28
|
+
*
|
|
29
|
+
* localStorage before the cookie: setOrganizationId writes localStorage
|
|
30
|
+
* synchronously and then awaits a server action for the cookie, so during
|
|
31
|
+
* that round trip the cookie still names the previous organisation.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getRequestOrganizationId(): string | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type QueryKey } from "@tanstack/react-query";
|
|
2
|
+
/**
|
|
3
|
+
* Every query is cached per organisation.
|
|
4
|
+
*
|
|
5
|
+
* Requests are scoped by the X-Organization-ID header, so almost no query key
|
|
6
|
+
* names the organisation — ["purchase-requests", "detail", id] is the same key
|
|
7
|
+
* in every company. A key that is the same in two organisations is one cache
|
|
8
|
+
* entry: what was fetched in one was a fresh cache hit in the other, and a
|
|
9
|
+
* page opened under another organisation's URL rendered the first one's
|
|
10
|
+
* document until something refetched it.
|
|
11
|
+
*
|
|
12
|
+
* The hash — the identity of a cache entry — therefore carries the
|
|
13
|
+
* organisation requests are sent with right now (getQueryClientConfig passes
|
|
14
|
+
* getRequestOrganizationId()). The key itself is untouched,
|
|
15
|
+
* so partial-key invalidation and every existing key keep working; a switch
|
|
16
|
+
* simply lands on a different set of entries.
|
|
17
|
+
*/
|
|
18
|
+
export declare function organizationScopedQueryHash(queryKey: QueryKey, organizationId: string | null): string;
|
|
19
|
+
/**
|
|
20
|
+
* Retry a failed query at most twice — unless the API answered with a 4xx.
|
|
21
|
+
* A 404 for a document that is not in this organisation will not appear on a
|
|
22
|
+
* second ask; retrying held the page on its skeleton for three seconds before
|
|
23
|
+
* it could say "not found". 408 and 429 are transient and still retried.
|
|
24
|
+
*/
|
|
25
|
+
export declare function shouldRetryQuery(failureCount: number, error: unknown): boolean;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { QueryClientConfig } from "@tanstack/react-query";
|
|
2
2
|
/**
|
|
3
3
|
* Default React Query config used across ERP apps:
|
|
4
|
-
* -
|
|
4
|
+
* - cache entries scoped by organisation (organizationScopedQueryHash)
|
|
5
|
+
* - 2 retries on queries except 4xx answers / none on mutations,
|
|
6
|
+
* exponential backoff
|
|
5
7
|
* - 5 min staleTime, refetchOnWindowFocus
|
|
6
8
|
* - swallows "no org context" errors to silence noise on the
|
|
7
9
|
* select-organization page
|
package/dist/version.d.ts
CHANGED