@eintrek/erp-shell 0.1.57 → 0.1.59
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/README.md +10 -0
- package/dist/help/help-store.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +156 -84
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +155 -82
- package/dist/index.js.map +1 -1
- package/dist/lib/plan-features.d.ts +4 -0
- package/dist/navigation/app-registry.d.ts +1 -1
- package/dist/navigation/navigation-data-helpers.d.ts +44 -9
- package/dist/navigation/navigation-types.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,16 @@ Shell now owns the **shapes + helpers** for navigation; apps own the data.
|
|
|
86
86
|
- Components: `NavTools` (reads Tool[] + buildUrlWithCode internally),
|
|
87
87
|
`AppBreadcrumb` (accepts `labels` prop so per-app Thai overrides stay local).
|
|
88
88
|
|
|
89
|
+
Sidebar highlight (0.1.58, `resolveSidebarNav`): an entry is lit only on
|
|
90
|
+
its own page — its URL or one of its `subPages`, exactly, after the org code,
|
|
91
|
+
ignoring a trailing slash — and at most one entry is lit. A record, a create
|
|
92
|
+
or an edit page below a menu page lights nothing; the group holding it (the
|
|
93
|
+
deepest URL the path sits below) opens. A page that should keep its entry lit
|
|
94
|
+
(a list's tab that is its own route) goes in that entry's `subPages`, with a
|
|
95
|
+
`permissionRouteKey` the user holds (usually the entry's own), since the
|
|
96
|
+
permission filter drops a sub-page without one. `getActiveModule` stays the
|
|
97
|
+
longest prefix; `getActiveSubModule` is exact like the sidebar.
|
|
98
|
+
|
|
89
99
|
Each app's `navigation-data.ts` now (1) imports types/helpers from shell,
|
|
90
100
|
(2) declares its own `navigationData` const + `breadcrumbLabels`, (3) wraps
|
|
91
101
|
`getActiveModule`/`getActiveSubModule` to keep the 1-arg signature, and
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* live in different subtrees. A tiny external store rather than a context so
|
|
5
5
|
* NavUser works the same whether or not it sits under the layout.
|
|
6
6
|
*/
|
|
7
|
-
export type HelpAppId = "platform" | "accounx" | "invenx" | "plannex" | "peoplx";
|
|
7
|
+
export type HelpAppId = "platform" | "accounx" | "invenx" | "plannex" | "peoplx" | "sellx" | "carex";
|
|
8
8
|
/** Opens the dialog — a no-op when no layout has mounted one. */
|
|
9
9
|
export declare function openHelp(): void;
|
|
10
10
|
export declare function closeHelp(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export { PermissionProvider, type PermissionProviderProps, type PermissionProbe,
|
|
|
38
38
|
export { getRequiredChildGroups, normalizePathnameForPermissionRules, resolveUiRoutePermission, type UiRoutePermissionLookup, } from "./auth/permission-utils";
|
|
39
39
|
export * from "./navigation/navigation-helpers";
|
|
40
40
|
export type { Module, SubPage, SubModule, Tool, Company, NavigationData, } from "./navigation/navigation-types";
|
|
41
|
-
export { canAccessNavPermissionRoute, filterNavigationToolsByPermission, filterNavigationModulesByPermission, getActiveModule, getActiveSubModule, buildUrlWithCode, resolveActiveNav, type ActiveNavEntry, type ResolveActiveNavOptions, } from "./navigation/navigation-data-helpers";
|
|
41
|
+
export { canAccessNavPermissionRoute, filterNavigationToolsByPermission, filterNavigationModulesByPermission, getActiveModule, getActiveSubModule, buildUrlWithCode, resolveActiveNav, resolveSidebarNav, type ActiveNavEntry, type SidebarNavState, type ResolveActiveNavOptions, } from "./navigation/navigation-data-helpers";
|
|
42
42
|
export { ThemeProvider } from "./providers/theme-provider";
|
|
43
43
|
export { FeatureFlagsProvider, useFeatureFlags, useFeatureFlag, type FeatureFlags, } from "./providers/feature-flags-provider";
|
|
44
44
|
export { ReactQueryProvider } from "./providers/react-query-provider";
|
package/dist/index.esm.js
CHANGED
|
@@ -5,7 +5,7 @@ import { useSession, signOut } from 'next-auth/react';
|
|
|
5
5
|
import { useQueryClient, useQuery, QueryClientContext, QueryClient, MutationCache, QueryCache, QueryClientProvider } from '@tanstack/react-query';
|
|
6
6
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
7
7
|
import { cn, Button, Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage, Avatar, AvatarImage, AvatarFallback, Collapsible, CollapsibleTrigger, CollapsibleContent, Badge, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator, Skeleton, Dialog, DialogContent, DialogHeader, DialogTitle, Label, Textarea, DialogFooter, Card, CardHeader, CardContent } from '@eintrek/erp-theme';
|
|
8
|
-
import { PanelLeftClose, Menu, ChevronRight, Building2, Palette, ChevronUp, Sun, Moon, Monitor, Globe, User, LayoutGrid, ExternalLink, CircleHelp, Loader2, LogOut, Calculator, Warehouse, FolderKanban, Users, ChevronDown, Plus, ChevronsUpDown, ImagePlus, X, CircleCheck, ArrowLeft, ArrowRight, Compass, ArrowUpRight, Ban, Home,
|
|
8
|
+
import { PanelLeftClose, Menu, ChevronRight, Building2, Palette, ChevronUp, Sun, Moon, Monitor, Globe, User, LayoutGrid, ExternalLink, CircleHelp, Loader2, LogOut, Calculator, Warehouse, FolderKanban, Users, Store, Headset, ChevronDown, Plus, ChevronsUpDown, ImagePlus, X, CircleCheck, ArrowLeft, ArrowRight, Compass, ArrowUpRight, Ban, Home, ContactRound, UserPlus } from 'lucide-react';
|
|
9
9
|
import { create } from 'zustand';
|
|
10
10
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
11
11
|
import { useRouter, usePathname, useParams } from 'next/navigation';
|
|
@@ -759,10 +759,23 @@ function longestMatch(path, modules) {
|
|
|
759
759
|
function getActiveModule(pathname, modules) {
|
|
760
760
|
return longestMatch(pathBelowCode(pathname), modules)?.module.id ?? null;
|
|
761
761
|
}
|
|
762
|
-
/**
|
|
762
|
+
/**
|
|
763
|
+
* Find the active sub-module id from `pathname`: the page whose own URL — or
|
|
764
|
+
* one of whose listed `subPages` — is the path itself (below the org code,
|
|
765
|
+
* ignoring a trailing slash). A record, a create or an edit page below a page
|
|
766
|
+
* is no page of the menu, so it has no active sub-module (0.1.58); the module
|
|
767
|
+
* (`getActiveModule`) still says where it belongs.
|
|
768
|
+
*
|
|
769
|
+
* Equal URLs in two modules go to the active module's page.
|
|
770
|
+
*/
|
|
763
771
|
function getActiveSubModule(pathname, modules) {
|
|
764
|
-
|
|
765
|
-
|
|
772
|
+
const path = strip(pathBelowCode(pathname));
|
|
773
|
+
const isPage = (url) => isMenuPath(url) && strip(url) === path;
|
|
774
|
+
const pagesOf = (m) => (m.subModules ?? []).filter(sub => isPage(sub.url) || (sub.subPages ?? []).some(p => isPage(p.url)));
|
|
775
|
+
const moduleId = getActiveModule(pathname, modules);
|
|
776
|
+
const own = modules.find(m => m.id === moduleId);
|
|
777
|
+
const hit = (own ? pagesOf(own)[0] : undefined) ?? modules.flatMap(pagesOf)[0];
|
|
778
|
+
return hit?.id ?? null;
|
|
766
779
|
}
|
|
767
780
|
/** Prepend `code` (org code) to a relative URL so `/sales` → `/O25.../sales`.
|
|
768
781
|
* Absolute `http(s)://` URLs are left intact; optional `{code}` placeholder is replaced.
|
|
@@ -785,100 +798,138 @@ function buildUrlWithCode(code, url) {
|
|
|
785
798
|
return `${base}${query}${hash}`;
|
|
786
799
|
}
|
|
787
800
|
const strip = (p) => (p.length > 1 ? p.replace(/\/+$/, "") : p);
|
|
801
|
+
/** A URL the path can be compared with: not query-only, not another site. */
|
|
802
|
+
const isMenuPath = (url) => !!url && !url.includes("?") && !/^https?:\/\//i.test(url);
|
|
788
803
|
/** Same check as `isPathActive`: the page itself or one below it. */
|
|
789
804
|
const pathIsAtOrBelow = (pathname, url) => pathname === url || pathname.startsWith(url + "/");
|
|
790
805
|
/** "/O1/" and "/O1" are the same page; a query string never is. */
|
|
791
806
|
function samePage(pathname, url) {
|
|
792
807
|
if (url.includes("?"))
|
|
793
808
|
return false;
|
|
794
|
-
return strip(pathname) === strip(url);
|
|
809
|
+
return strip(pathname.split("?")[0]) === strip(url);
|
|
795
810
|
}
|
|
796
811
|
/**
|
|
797
|
-
* How long
|
|
798
|
-
*
|
|
799
|
-
*
|
|
812
|
+
* How long `url` is when it is the page `pathname` shows (ignoring a trailing
|
|
813
|
+
* slash), or -1. Compared with and without the org code, as the menu URLs
|
|
814
|
+
* are written either way.
|
|
800
815
|
*/
|
|
801
|
-
function
|
|
816
|
+
function pageLength(pathname, url, code) {
|
|
817
|
+
if (!isMenuPath(url))
|
|
818
|
+
return -1;
|
|
802
819
|
const built = buildUrlWithCode(code, url);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
820
|
+
if (samePage(pathname, built))
|
|
821
|
+
return strip(built).length;
|
|
822
|
+
if (samePage(pathname, url))
|
|
823
|
+
return strip(url).length;
|
|
824
|
+
return -1;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* How long the URL that makes `url` cover `pathname` is — the page itself or
|
|
828
|
+
* one below it — or -1. The organisation's home ("/" → "/O1") covers only
|
|
829
|
+
* itself: every page sits below it, and a dashboard is not the parent of
|
|
830
|
+
* every page.
|
|
831
|
+
*/
|
|
832
|
+
function coverLength(pathname, url, code) {
|
|
833
|
+
if (!isMenuPath(url))
|
|
834
|
+
return -1;
|
|
835
|
+
const path = strip(pathname.split("?")[0]);
|
|
836
|
+
const built = strip(buildUrlWithCode(code, url));
|
|
837
|
+
const isRoot = url === "/" || (!!code && built === `/${code}`);
|
|
838
|
+
if (isRoot)
|
|
839
|
+
return pageLength(path, url, code);
|
|
840
|
+
if (pathIsAtOrBelow(path, built))
|
|
810
841
|
return built.length;
|
|
811
|
-
|
|
812
|
-
|
|
842
|
+
const raw = strip(url);
|
|
843
|
+
if (pathIsAtOrBelow(path, raw))
|
|
844
|
+
return raw.length;
|
|
813
845
|
return -1;
|
|
814
846
|
}
|
|
815
847
|
/**
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
848
|
+
* Where `pathname` sits in the sidebar: the entry to light and the group to
|
|
849
|
+
* open.
|
|
850
|
+
*
|
|
851
|
+
* Lit (0.1.58): only the entry whose own page this is — its URL or one of its
|
|
852
|
+
* `subPages`, exactly. A record, a create or an edit page below a menu page
|
|
853
|
+
* (Plannex /project/detail/<id>/schedule under "โครงการ" /project) lights
|
|
854
|
+
* nothing; an app lists such a page in `subPages` when it should keep its
|
|
855
|
+
* entry lit. At most one entry across every group (0.1.57).
|
|
856
|
+
*
|
|
857
|
+
* Open: the lit entry's group, or else the group with the deepest URL the
|
|
858
|
+
* path sits below.
|
|
820
859
|
*
|
|
821
860
|
* Equal lengths go to the group the app marks active (`item.isActive`, from
|
|
822
861
|
* its `activeModule`), then to a page over a group row, then to the first in
|
|
823
862
|
* menu order.
|
|
824
863
|
*/
|
|
825
|
-
function
|
|
826
|
-
const path = pathname ?? "";
|
|
864
|
+
function resolveSidebarNav(items, pathname, { code, highlightActiveModule = false } = {}) {
|
|
865
|
+
const path = (pathname ?? "").split("?")[0];
|
|
827
866
|
// The app named a one-line module as the current one: that is the answer.
|
|
828
867
|
if (highlightActiveModule) {
|
|
829
868
|
const leaf = items.findIndex(item => !item.subModules?.length && !!item.isActive);
|
|
830
|
-
if (leaf >= 0)
|
|
831
|
-
return {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
if (!best) {
|
|
836
|
-
best = c;
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
if (c.length !== best.length) {
|
|
840
|
-
if (c.length > best.length)
|
|
841
|
-
best = c;
|
|
842
|
-
return;
|
|
869
|
+
if (leaf >= 0) {
|
|
870
|
+
return {
|
|
871
|
+
active: { item: leaf, sub: null, subPage: null },
|
|
872
|
+
open: { item: leaf, sub: null },
|
|
873
|
+
};
|
|
843
874
|
}
|
|
875
|
+
}
|
|
876
|
+
const better = (c, best) => {
|
|
877
|
+
if (!best)
|
|
878
|
+
return true;
|
|
879
|
+
if (c.length !== best.length)
|
|
880
|
+
return c.length > best.length;
|
|
844
881
|
const cActive = !!items[c.item].isActive;
|
|
845
882
|
const bActive = !!items[best.item].isActive;
|
|
846
|
-
if (cActive !== bActive)
|
|
847
|
-
|
|
848
|
-
|
|
883
|
+
if (cActive !== bActive)
|
|
884
|
+
return cActive;
|
|
885
|
+
// The same sub-item by its own URL and by a listed page: the page.
|
|
886
|
+
if (c.item === best.item && c.sub === best.sub) {
|
|
887
|
+
return c.subPage !== null && best.subPage === null;
|
|
888
|
+
}
|
|
889
|
+
return c.sub !== null && best.sub === null;
|
|
890
|
+
};
|
|
891
|
+
let exact = null;
|
|
892
|
+
let within = null;
|
|
893
|
+
const consider = (c, isExact) => {
|
|
894
|
+
if (c.length < 0)
|
|
849
895
|
return;
|
|
896
|
+
if (isExact) {
|
|
897
|
+
if (better(c, exact))
|
|
898
|
+
exact = c;
|
|
899
|
+
}
|
|
900
|
+
else if (better(c, within)) {
|
|
901
|
+
within = c;
|
|
850
902
|
}
|
|
851
|
-
if (c.sub !== null && best.sub === null)
|
|
852
|
-
best = c;
|
|
853
903
|
};
|
|
854
904
|
items.forEach((item, i) => {
|
|
855
|
-
const itemUrl = buildUrlWithCode(code, item.url);
|
|
856
905
|
const subs = item.subModules ?? [];
|
|
906
|
+
const row = { item: i, sub: null, subPage: null };
|
|
857
907
|
// The row itself: only on its own URL. A one-line item under
|
|
858
908
|
// highlightActiveModule is lit by isActive alone (handled above).
|
|
859
909
|
if (!(highlightActiveModule && subs.length === 0)) {
|
|
860
|
-
|
|
861
|
-
if (own)
|
|
862
|
-
consider({ item: i, sub: null, subPage: null, length: strip(itemUrl).length });
|
|
910
|
+
consider({ ...row, length: pageLength(path, item.url, code) }, true);
|
|
863
911
|
}
|
|
912
|
+
consider({ ...row, length: coverLength(path, item.url, code) }, false);
|
|
864
913
|
subs.forEach((sub, j) => {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
914
|
+
const entry = { item: i, sub: j };
|
|
915
|
+
consider({ ...entry, subPage: null, length: pageLength(path, sub.url, code) }, true);
|
|
916
|
+
consider({ ...entry, subPage: null, length: coverLength(path, sub.url, code) }, false);
|
|
868
917
|
(sub.subPages ?? []).forEach((page, k) => {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
pageLength = l;
|
|
872
|
-
subPage = k;
|
|
873
|
-
}
|
|
918
|
+
consider({ ...entry, subPage: k, length: pageLength(path, page.url, code) }, true);
|
|
919
|
+
consider({ ...entry, subPage: k, length: coverLength(path, page.url, code) }, false);
|
|
874
920
|
});
|
|
875
|
-
const length = Math.max(coverLength(path, sub.url, code), pageLength);
|
|
876
|
-
if (length >= 0)
|
|
877
|
-
consider({ item: i, sub: j, subPage, length });
|
|
878
921
|
});
|
|
879
922
|
});
|
|
880
|
-
const
|
|
881
|
-
|
|
923
|
+
const lit = exact;
|
|
924
|
+
const holder = lit ?? within;
|
|
925
|
+
return {
|
|
926
|
+
active: lit ? { item: lit.item, sub: lit.sub, subPage: lit.subPage } : null,
|
|
927
|
+
open: holder ? { item: holder.item, sub: holder.sub } : null,
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
/** The entry lit for `pathname` — `resolveSidebarNav(...).active`. */
|
|
931
|
+
function resolveActiveNav(items, pathname, options = {}) {
|
|
932
|
+
return resolveSidebarNav(items, pathname, options).active;
|
|
882
933
|
}
|
|
883
934
|
|
|
884
935
|
function NavTools({ tools, closeOnNavigation = false }) {
|
|
@@ -1365,7 +1416,7 @@ function platformApiBase(app) {
|
|
|
1365
1416
|
* This package's version, attached to help messages so the back office can
|
|
1366
1417
|
* tell which shell a report came from. Keep in step with package.json.
|
|
1367
1418
|
*/
|
|
1368
|
-
const SHELL_VERSION = "0.1.
|
|
1419
|
+
const SHELL_VERSION = "0.1.59";
|
|
1369
1420
|
|
|
1370
1421
|
const HELP_APP_LABEL = {
|
|
1371
1422
|
platform: "แพลตฟอร์ม",
|
|
@@ -1373,6 +1424,8 @@ const HELP_APP_LABEL = {
|
|
|
1373
1424
|
peoplx: "Peoplx",
|
|
1374
1425
|
plannex: "Plannex",
|
|
1375
1426
|
invenx: "Invenx",
|
|
1427
|
+
sellx: "Sellx",
|
|
1428
|
+
carex: "Carex",
|
|
1376
1429
|
other: "อื่น ๆ",
|
|
1377
1430
|
};
|
|
1378
1431
|
function originOf(value) {
|
|
@@ -1407,6 +1460,10 @@ function detectHelpApp(explicit) {
|
|
|
1407
1460
|
return "plannex";
|
|
1408
1461
|
if (host.startsWith("invenx."))
|
|
1409
1462
|
return "invenx";
|
|
1463
|
+
if (host.startsWith("sellx."))
|
|
1464
|
+
return "sellx";
|
|
1465
|
+
if (host.startsWith("carex."))
|
|
1466
|
+
return "carex";
|
|
1410
1467
|
if (host.startsWith("platform.") || host === "antniti.com" || host === "www.antniti.com") {
|
|
1411
1468
|
return "platform";
|
|
1412
1469
|
}
|
|
@@ -1417,6 +1474,8 @@ function detectHelpApp(explicit) {
|
|
|
1417
1474
|
["invenx", process.env.NEXT_PUBLIC_INVENX_URL],
|
|
1418
1475
|
["plannex", process.env.NEXT_PUBLIC_PLANNEX_URL],
|
|
1419
1476
|
["peoplx", process.env.NEXT_PUBLIC_HR_URL],
|
|
1477
|
+
["sellx", process.env.NEXT_PUBLIC_SELLX_URL],
|
|
1478
|
+
["carex", process.env.NEXT_PUBLIC_CAREX_URL],
|
|
1420
1479
|
];
|
|
1421
1480
|
for (const [id, url] of configured) {
|
|
1422
1481
|
if (originOf(url) === here)
|
|
@@ -1804,6 +1863,10 @@ const PLAN_FEATURES = {
|
|
|
1804
1863
|
MODULE_INVENTORY: "module.inventory",
|
|
1805
1864
|
MODULE_PROJECT: "module.project",
|
|
1806
1865
|
MODULE_SECRETARIAT: "module.secretariat",
|
|
1866
|
+
/** Sellx add-on: POS, members and chat commerce. */
|
|
1867
|
+
MODULE_SELLX: "module.sellx",
|
|
1868
|
+
/** Carex add-on: help desk, SLA and knowledge base. */
|
|
1869
|
+
MODULE_CAREX: "module.carex",
|
|
1807
1870
|
/** AI pack (paid or trial): AI calls on AntNiti's key while credits remain. */
|
|
1808
1871
|
AI: "ai",
|
|
1809
1872
|
};
|
|
@@ -1888,6 +1951,20 @@ const APPS = [
|
|
|
1888
1951
|
features: [PLAN_FEATURES.MODULE_HR],
|
|
1889
1952
|
baseUrl: () => normaliseBase(process.env.NEXT_PUBLIC_HR_URL, "https://peoplx.antniti.com"),
|
|
1890
1953
|
},
|
|
1954
|
+
{
|
|
1955
|
+
id: "sellx",
|
|
1956
|
+
label: "Sellx",
|
|
1957
|
+
icon: jsx(Store, { className: "h-4 w-4 shrink-0" }),
|
|
1958
|
+
features: [PLAN_FEATURES.MODULE_SELLX],
|
|
1959
|
+
baseUrl: () => normaliseBase(process.env.NEXT_PUBLIC_SELLX_URL, "https://sellx.antniti.com"),
|
|
1960
|
+
},
|
|
1961
|
+
{
|
|
1962
|
+
id: "carex",
|
|
1963
|
+
label: "Carex",
|
|
1964
|
+
icon: jsx(Headset, { className: "h-4 w-4 shrink-0" }),
|
|
1965
|
+
features: [PLAN_FEATURES.MODULE_CAREX],
|
|
1966
|
+
baseUrl: () => normaliseBase(process.env.NEXT_PUBLIC_CAREX_URL, "https://carex.antniti.com"),
|
|
1967
|
+
},
|
|
1891
1968
|
];
|
|
1892
1969
|
/**
|
|
1893
1970
|
* Builds the cross-app link list for <NavUser systemLinks>.
|
|
@@ -1923,12 +2000,11 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
|
|
|
1923
2000
|
setOpen(false);
|
|
1924
2001
|
};
|
|
1925
2002
|
const code = orgCode ?? params?.code;
|
|
1926
|
-
// The one entry
|
|
1927
|
-
//
|
|
1928
|
-
//
|
|
1929
|
-
const active =
|
|
2003
|
+
// The one entry lit for this page — only on that entry's own page, never
|
|
2004
|
+
// on a record or a form below it — and the group holding the page, which
|
|
2005
|
+
// opens either way.
|
|
2006
|
+
const { active, open } = resolveSidebarNav(items, pathname, {
|
|
1930
2007
|
code,
|
|
1931
|
-
workspace,
|
|
1932
2008
|
highlightActiveModule,
|
|
1933
2009
|
});
|
|
1934
2010
|
const states = items.map((item, i) => {
|
|
@@ -1938,36 +2014,32 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
|
|
|
1938
2014
|
itemUrl,
|
|
1939
2015
|
activeSub: mine?.sub ?? null,
|
|
1940
2016
|
activeSubPage: mine?.subPage ?? null,
|
|
1941
|
-
|
|
2017
|
+
openSub: open?.item === i ? open.sub : null,
|
|
1942
2018
|
isItemActive: !!mine && mine.sub === null,
|
|
1943
2019
|
};
|
|
1944
2020
|
});
|
|
1945
|
-
// A group opens when
|
|
2021
|
+
// A group opens when the page moves into it and otherwise stays as the
|
|
1946
2022
|
// user left it. defaultOpen alone decided once, on the first render — and
|
|
1947
2023
|
// on a direct load the sub-items arrive after permissions do, so the group
|
|
1948
2024
|
// holding the current page stayed shut.
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
.map(item => item.id)
|
|
1952
|
-
.join(",");
|
|
1953
|
-
const [openIds, setOpenIds] = React.useState(() => new Set(activeKey ? activeKey.split(",") : []));
|
|
2025
|
+
const openId = open ? (items[open.item]?.id ?? "") : "";
|
|
2026
|
+
const [openIds, setOpenIds] = React.useState(() => new Set(openId ? [openId] : []));
|
|
1954
2027
|
React.useEffect(() => {
|
|
1955
|
-
if (!
|
|
2028
|
+
if (!openId)
|
|
1956
2029
|
return;
|
|
1957
2030
|
setOpenIds(prev => {
|
|
1958
|
-
|
|
1959
|
-
if (ids.every(id => prev.has(id)))
|
|
2031
|
+
if (prev.has(openId))
|
|
1960
2032
|
return prev;
|
|
1961
2033
|
const next = new Set(prev);
|
|
1962
|
-
|
|
2034
|
+
next.add(openId);
|
|
1963
2035
|
return next;
|
|
1964
2036
|
});
|
|
1965
|
-
}, [
|
|
1966
|
-
const setItemOpen = (id,
|
|
1967
|
-
if (prev.has(id) ===
|
|
2037
|
+
}, [openId]);
|
|
2038
|
+
const setItemOpen = (id, isOpen) => setOpenIds(prev => {
|
|
2039
|
+
if (prev.has(id) === isOpen)
|
|
1968
2040
|
return prev;
|
|
1969
2041
|
const next = new Set(prev);
|
|
1970
|
-
if (
|
|
2042
|
+
if (isOpen)
|
|
1971
2043
|
next.add(id);
|
|
1972
2044
|
else
|
|
1973
2045
|
next.delete(id);
|
|
@@ -1978,7 +2050,7 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
|
|
|
1978
2050
|
item.group &&
|
|
1979
2051
|
item.group !== items[index - 1]?.group ? (jsx("li", { className: "list-none px-3 pb-2.5 text-xs text-muted-foreground" +
|
|
1980
2052
|
(index > 0 ? " pt-5" : ""), children: item.group }, `group-${item.id}`)) : null;
|
|
1981
|
-
const { itemUrl, activeSub, activeSubPage, isItemActive } = states[index];
|
|
2053
|
+
const { itemUrl, activeSub, activeSubPage, openSub, isItemActive } = states[index];
|
|
1982
2054
|
const IconComp = item.icon;
|
|
1983
2055
|
const workspaceButton = workspace
|
|
1984
2056
|
? "h-auto gap-3 rounded-[10px] px-3 py-2.5 text-xs " +
|
|
@@ -1988,7 +2060,7 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
|
|
|
1988
2060
|
: "";
|
|
1989
2061
|
const icon = workspace && IconComp ? (jsx(IconComp, { className: "size-[18px] shrink-0" })) : null;
|
|
1990
2062
|
const badge = workspace && item.badge !== undefined && item.badge !== "" ? (jsx("span", { className: "ml-auto min-w-5 rounded-[5px] bg-primary/15 px-1 text-center text-xs leading-5 font-bold tabular-nums text-primary", children: item.badge })) : null;
|
|
1991
|
-
return (jsxs(React.Fragment, { children: [groupHeading, jsx(Collapsible, { asChild: true, open: openIds.has(item.id), onOpenChange:
|
|
2063
|
+
return (jsxs(React.Fragment, { children: [groupHeading, jsx(Collapsible, { asChild: true, open: openIds.has(item.id), onOpenChange: isOpen => setItemOpen(item.id, isOpen), className: "group/collapsible", children: jsxs(SidebarMenuItem, { children: [jsx(CollapsibleTrigger, { asChild: true, children: jsx(SidebarMenuButton, { asChild: !item.subModules || item.subModules.length === 0, isActive: isItemActive, className: "data-[state=open]:text-sidebar-accent-foreground " +
|
|
1992
2064
|
workspaceButton, children: item.subModules && item.subModules.length > 0 ? (jsxs(Fragment$1, { children: [icon, jsx("span", { id: item.id, children: item.titleTh }), jsx(ChevronRight, { className: "ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" })] })) : (jsxs(Link, { href: itemUrl, prefetch: false, className: "flex items-center w-full min-w-0 flex-1 " +
|
|
1993
2065
|
(workspace ? "gap-3" : "gap-2"), onClick: handleNavLinkClick, "aria-current": isItemActive ? "page" : undefined, children: [icon, jsx("span", { id: item.id, className: "flex-1 truncate", title: item.titleTh, children: item.titleTh }), badge] })) }) }), item.subModules && item.subModules.length > 0 && (jsx(CollapsibleContent, { children: jsx(SidebarMenuSub, { className: workspace
|
|
1994
2066
|
? "mx-0 mt-1 mb-3 ml-[21px] gap-0.5 border-l border-sidebar-border pl-2.5 [&_a]:h-auto [&_a]:gap-[11px] [&_a]:px-3 [&_a]:py-2 [&_a]:text-xs [&_svg]:!size-[17px]"
|
|
@@ -2002,7 +2074,7 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
|
|
|
2002
2074
|
if (subItem.isCollapsible &&
|
|
2003
2075
|
subItem.subPages &&
|
|
2004
2076
|
subItem.subPages.length > 0) {
|
|
2005
|
-
return (jsx(Collapsible, { asChild: true, defaultOpen: isSubItemActive, className: "group/sub-collapsible", children: jsxs(SidebarMenuSubItem, { children: [jsx(CollapsibleTrigger, { asChild: true, children: jsxs(SidebarMenuSubButton, { className: "w-full", isActive: isSubItemActive, children: [SubIcon && (jsx(SubIcon, { className: "sidebar-submodule-icon flex-shrink-0" })), jsx("span", { className: "flex-1 truncate", title: subItem.titleTh, children: subItem.titleTh }), jsx(ChevronDown, { className: "ml-auto size-4 transition-transform duration-200 group-data-[state=open]/sub-collapsible:rotate-180" })] }) }), jsx(CollapsibleContent, { children: jsx(SidebarMenuSub, { className: "ml-4", children: subItem.subPages.map((subPage, subPageIndex) => {
|
|
2077
|
+
return (jsx(Collapsible, { asChild: true, defaultOpen: isSubItemActive || openSub === subIndex, className: "group/sub-collapsible", children: jsxs(SidebarMenuSubItem, { children: [jsx(CollapsibleTrigger, { asChild: true, children: jsxs(SidebarMenuSubButton, { className: "w-full", isActive: isSubItemActive, children: [SubIcon && (jsx(SubIcon, { className: "sidebar-submodule-icon flex-shrink-0" })), jsx("span", { className: "flex-1 truncate", title: subItem.titleTh, children: subItem.titleTh }), jsx(ChevronDown, { className: "ml-auto size-4 transition-transform duration-200 group-data-[state=open]/sub-collapsible:rotate-180" })] }) }), jsx(CollapsibleContent, { children: jsx(SidebarMenuSub, { className: "ml-4", children: subItem.subPages.map((subPage, subPageIndex) => {
|
|
2006
2078
|
const subPageUrl = buildUrlWithCode(code, subPage.url);
|
|
2007
2079
|
const isSubPageActive = isSubItemActive &&
|
|
2008
2080
|
activeSubPage === subPageIndex;
|
|
@@ -3721,5 +3793,5 @@ const getAxiosErrorMessage = (error) => {
|
|
|
3721
3793
|
return String(error);
|
|
3722
3794
|
};
|
|
3723
3795
|
|
|
3724
|
-
export { AccessDenied, ApiError, AppBreadcrumb, AppLayoutShell, AppSidebar, EMPLOYEE_RECORD_OK_EVENT, EMPLOYEE_RECORD_REQUIRED, EMPLOYEE_RECORD_REQUIRED_EVENT, EmployeeRequiredGate, FeatureFlagsProvider, HeaderBackground, HelpButton, HelpDialogHost, NavMain, NavTools, NavUser, OWNER_CHIP, OrganizationSwitcher, PLAN_FEATURES, PermissionGateShell, PermissionProvider, PermissionRulesProvider, ReactQueryProvider, SHELL_VERSION, SessionBackedProfileCacheSeeder, SessionRefreshTrigger, SidebarContent, SidebarFooter, SidebarHeader, SidebarHelpCard, SidebarHelpLink, SidebarProvider, SidebarTrigger, TapMenu, ThemeProvider, WORKSPACE_HEADER_VARS, WORKSPACE_LIGHT_HEADER_VARS, WorkspaceBrand, instance as axiosInstance, buildSystemLinks, buildUrlWithCode, canAccessNavPermissionRoute, clearAccessTokenCache, clearOrganizationId, closeHelp, decodeJWT, describeAttached, describeBrowser, detectHelpApp, filterNavigationModulesByPermission, filterNavigationToolsByPermission, getAccessToken, getActiveModule, getActiveSubModule, getAxiosErrorMessage, getOrganizationId, getQueryClientConfig, getRequiredChildGroups, hasPlanFeature, isLikelyTransientNetworkError, isPathActive, myRolesQueryKey, normalizePathnameForPermissionRules, openHelp, parseMyRoles, resolveActiveNav, resolveUiRoutePermission, roleSummary, runGlobalRequestPrecheck, runWithTransientRetry, setOrganizationId, toApiError, unwrapApiResponse, updateOrganizationId, useClearOrgOnUserChange, useFeatureFlag, useFeatureFlags, useInvalidateQueriesOnOrgChange, useKeycloakRoles, useMyRoles, useOrganizationIdState, usePermissionRules, useSessionRefresh, useSidebar, useSidebarToggle };
|
|
3796
|
+
export { AccessDenied, ApiError, AppBreadcrumb, AppLayoutShell, AppSidebar, EMPLOYEE_RECORD_OK_EVENT, EMPLOYEE_RECORD_REQUIRED, EMPLOYEE_RECORD_REQUIRED_EVENT, EmployeeRequiredGate, FeatureFlagsProvider, HeaderBackground, HelpButton, HelpDialogHost, NavMain, NavTools, NavUser, OWNER_CHIP, OrganizationSwitcher, PLAN_FEATURES, PermissionGateShell, PermissionProvider, PermissionRulesProvider, ReactQueryProvider, SHELL_VERSION, SessionBackedProfileCacheSeeder, SessionRefreshTrigger, SidebarContent, SidebarFooter, SidebarHeader, SidebarHelpCard, SidebarHelpLink, SidebarProvider, SidebarTrigger, TapMenu, ThemeProvider, WORKSPACE_HEADER_VARS, WORKSPACE_LIGHT_HEADER_VARS, WorkspaceBrand, instance as axiosInstance, buildSystemLinks, buildUrlWithCode, canAccessNavPermissionRoute, clearAccessTokenCache, clearOrganizationId, closeHelp, decodeJWT, describeAttached, describeBrowser, detectHelpApp, filterNavigationModulesByPermission, filterNavigationToolsByPermission, getAccessToken, getActiveModule, getActiveSubModule, getAxiosErrorMessage, getOrganizationId, getQueryClientConfig, getRequiredChildGroups, hasPlanFeature, isLikelyTransientNetworkError, isPathActive, myRolesQueryKey, normalizePathnameForPermissionRules, openHelp, parseMyRoles, resolveActiveNav, resolveSidebarNav, resolveUiRoutePermission, roleSummary, runGlobalRequestPrecheck, runWithTransientRetry, setOrganizationId, toApiError, unwrapApiResponse, updateOrganizationId, useClearOrgOnUserChange, useFeatureFlag, useFeatureFlags, useInvalidateQueriesOnOrgChange, useKeycloakRoles, useMyRoles, useOrganizationIdState, usePermissionRules, useSessionRefresh, useSidebar, useSidebarToggle };
|
|
3725
3797
|
//# sourceMappingURL=index.esm.js.map
|