@eintrek/erp-shell 0.1.54 → 0.1.56

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
@@ -762,6 +762,7 @@ function getActiveSubModule(pathname, modules) {
762
762
  }
763
763
  /** Prepend `code` (org code) to a relative URL so `/sales` → `/O25.../sales`.
764
764
  * Absolute `http(s)://` URLs are left intact; optional `{code}` placeholder is replaced.
765
+ * Query/hash are preserved: `/?start=1` → `/O25...?start=1` (not `/O25.../?start=1`).
765
766
  */
766
767
  function buildUrlWithCode(code, url) {
767
768
  if (/^https?:\/\//i.test(url)) {
@@ -771,8 +772,13 @@ function buildUrlWithCode(code, url) {
771
772
  }
772
773
  if (!code)
773
774
  return url;
774
- const cleanUrl = url.startsWith("/") ? url.slice(1) : url;
775
- return `/${code}/${cleanUrl}`;
775
+ const match = url.match(/^([^?#]*)(\?[^#]*)?(#.*)?$/);
776
+ const path = match?.[1] ?? url;
777
+ const query = match?.[2] ?? "";
778
+ const hash = match?.[3] ?? "";
779
+ const cleanPath = path.startsWith("/") ? path.slice(1) : path;
780
+ const base = cleanPath ? `/${code}/${cleanPath}` : `/${code}`;
781
+ return `${base}${query}${hash}`;
776
782
  }
777
783
 
778
784
  function NavTools({ tools, closeOnNavigation = false }) {
@@ -955,6 +961,13 @@ function cardRoleLine(roles, limit = CARD_ROLE_LIMIT) {
955
961
  const names = roles.map(r => r.name_th);
956
962
  return { shown: names.slice(0, limit), more: Math.max(0, names.length - limit) };
957
963
  }
964
+ /**
965
+ * Workspace profile card: owner chip + "อีก N บทบาท", without listing role
966
+ * names on the trigger (full names stay in the account menu).
967
+ */
968
+ function workspaceRoleExtra(my) {
969
+ return my.roles.length;
970
+ }
958
971
  /** Everything, for the tooltip: "เจ้าของ · ผู้ดูแลสูงสุด · พนักงานทั่วไป". */
959
972
  function roleSummary(my) {
960
973
  return [...(my.isOwner ? [OWNER_CHIP] : []), ...my.roles.map(r => r.name_th)].join(" · ");
@@ -1494,11 +1507,16 @@ function OwnerChip({ size = "card" }) {
1494
1507
  : "inline-flex shrink-0 items-center rounded-full bg-primary/10 px-1.5 text-xs font-semibold leading-[17px] text-primary", children: OWNER_CHIP }));
1495
1508
  }
1496
1509
  /**
1497
- * The card's role line: the owner's chip, then two role names and a count of
1498
- * the rest. Wraps between names rather than inside one, and truncates a name
1499
- * that is wider than the card on its own; the tooltip has everything.
1510
+ * Brand card: owner chip, then two role names and a count of the rest.
1511
+ * Workspace card: owner chip + "อีก N บทบาท" (names live in the menu).
1500
1512
  */
1501
- function CardRoles({ my, className }) {
1513
+ function CardRoles({ my, className, variant = "brand", }) {
1514
+ if (variant === "workspace") {
1515
+ const extra = workspaceRoleExtra(my);
1516
+ return (jsxRuntime.jsxs("span", { className: `flex min-w-0 flex-wrap items-center gap-1.5 ${className}`, title: roleSummary(my), children: [my.isOwner ? jsxRuntime.jsx(OwnerChip, {}) : null, !my.isOwner && my.roles[0] ? (jsxRuntime.jsx("span", { className: "truncate", children: my.roles[0].name_th })) : null, extra > 0 && (my.isOwner || my.roles.length > 1) ? (jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: my.isOwner
1517
+ ? `อีก ${extra} บทบาท`
1518
+ : `อีก ${extra - 1} บทบาท` })) : null] }));
1519
+ }
1502
1520
  const { shown, more } = cardRoleLine(my.roles);
1503
1521
  return (jsxRuntime.jsxs("span", { className: `flex min-w-0 flex-wrap items-center gap-x-1 gap-y-0.5 ${className}`, title: roleSummary(my), children: [my.isOwner ? jsxRuntime.jsx(OwnerChip, {}) : null, shown.map((name, i) => {
1504
1522
  const last = i === shown.length - 1;
@@ -1656,8 +1674,8 @@ function NavUser({ organization, avatarSrc, profileHref, selectOrganizationHref
1656
1674
  openHelp();
1657
1675
  }, className: "flex w-full items-center gap-3 px-3 py-2.5 text-sm text-sidebar-foreground hover:bg-sidebar-accent rounded-md transition-colors", children: [jsxRuntime.jsx(lucideReact.CircleHelp, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D" })] })), jsxRuntime.jsx("button", { onClick: handleSignOut, disabled: isSigningOut, className: "flex w-full items-center gap-3 px-3 py-2.5 text-sm text-sidebar-foreground hover:bg-sidebar-accent rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed mt-1", children: isSigningOut ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E23\u0E30\u0E1A\u0E1A..." })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(lucideReact.LogOut, { className: "h-4 w-4" }), jsxRuntime.jsx("span", { className: "flex-1 text-left", children: "\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E23\u0E30\u0E1A\u0E1A" })] })) })] })] })) : null;
1658
1676
  if (variant === "workspace") {
1659
- return (jsxRuntime.jsx("div", { className: "mt-3.5 border-t border-sidebar-border pt-3", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { ref: buttonRef, onClick: () => setIsOpen(!isOpen), "aria-expanded": isOpen, className: `relative flex w-full gap-[11px] rounded-lg px-3 py-1.5 text-left transition-colors hover:bg-sidebar-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring ${cardTall ? "items-start" : "items-center"}`, children: [jsxRuntime.jsxs(erpTheme.Avatar, { className: "size-[34px] shrink-0 rounded-full border-[3px] border-sidebar-accent", children: [user.avatar ? (jsxRuntime.jsx(erpTheme.AvatarImage, { src: user.avatar, alt: user.name })) : null, jsxRuntime.jsx(erpTheme.AvatarFallback, { className: "rounded-full bg-sidebar-accent text-sm font-medium text-sidebar-accent-foreground", children: getInitials(user.name) })] }), jsxRuntime.jsxs("span", { className: `grid min-w-0 flex-1 leading-tight ${cardTall ? "gap-0.5" : ""}`, children: [jsxRuntime.jsx("strong", { className: `truncate text-xs font-semibold ${cardTall ? "pr-5" : ""}`, title: user.name, children: user.name }), myRoles ? (jsxRuntime.jsx(CardRoles, { my: myRoles, className: "text-xs font-medium leading-[17px] text-sidebar-foreground/80" })) : rolesLoading ? (jsxRuntime.jsx("span", { className: "my-[3px] block h-[11px] w-20 animate-pulse rounded bg-sidebar-accent" })) : null, jsxRuntime.jsx("small", { className: "truncate text-xs text-muted-foreground", title: user.email, children: user.email })] }), jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: cardTall
1660
- ? "absolute right-3 top-[9px] size-3.5 text-muted-foreground"
1677
+ return (jsxRuntime.jsx("div", { className: "mt-2.5 border-t border-sidebar-border pt-3.5", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsxs("button", { ref: buttonRef, onClick: () => setIsOpen(!isOpen), "aria-expanded": isOpen, className: `relative flex w-full gap-2.5 rounded-[14px] border border-transparent bg-sidebar-accent/50 px-2.5 py-2.5 text-left transition-colors hover:border-sidebar-border hover:bg-sidebar-accent/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring ${cardTall ? "items-start" : "items-center"}`, children: [jsxRuntime.jsxs(erpTheme.Avatar, { className: "size-[38px] shrink-0 rounded-full border border-sidebar-border", children: [user.avatar ? (jsxRuntime.jsx(erpTheme.AvatarImage, { src: user.avatar, alt: user.name })) : null, jsxRuntime.jsx(erpTheme.AvatarFallback, { className: "rounded-full bg-primary/10 text-[17px] font-medium text-primary", children: getInitials(user.name) })] }), jsxRuntime.jsxs("span", { className: "grid min-w-0 flex-1 gap-0.5 leading-tight", children: [jsxRuntime.jsx("strong", { className: `truncate text-[13px] font-semibold ${cardTall ? "pr-5" : ""}`, title: user.name, children: user.name }), jsxRuntime.jsx("small", { className: "truncate text-[11px] text-muted-foreground", title: user.email, children: user.email }), myRoles ? (jsxRuntime.jsx(CardRoles, { my: myRoles, variant: "workspace", className: "mt-0.5 text-[11px] font-medium leading-[17px] text-sidebar-foreground/80" })) : rolesLoading ? (jsxRuntime.jsx("span", { className: "my-[3px] block h-[11px] w-20 animate-pulse rounded bg-sidebar-accent" })) : null] }), jsxRuntime.jsx(lucideReact.ChevronUp, { className: cardTall
1678
+ ? "absolute right-2.5 top-3 size-3.5 text-muted-foreground"
1661
1679
  : "size-3.5 shrink-0 text-muted-foreground" })] }), typeof window !== "undefined" &&
1662
1680
  reactDom.createPortal(dropdownContent, document.body)] }) }));
1663
1681
  }
@@ -1851,10 +1869,14 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
1851
1869
  !!item.subModules?.some(subItem => subItem.subPages?.some(subPage => isPathActive(pathname, buildUrlWithCode(code, subPage.url)) ||
1852
1870
  isPathActive(pathname, subPage.url)));
1853
1871
  // Parent only active when path matches its URL exactly AND no child
1854
- // is active (so parent+child don't both light up).
1855
- const isItemActive = (highlightActiveModule && !item.subModules?.length && !!item.isActive) ||
1856
- ((workspace ? samePage(pathname, itemUrl) : pathname === itemUrl) &&
1857
- !hasActiveSubItem);
1872
+ // is active (so parent+child don't both light up). When the app opts
1873
+ // into highlightActiveModule, leaf items trust `item.isActive` alone —
1874
+ // needed for query-only hub actions (`/?start=1`) that share a path
1875
+ // with another leaf (e.g. "แอปของฉัน" at `/`).
1876
+ const isItemActive = highlightActiveModule && !item.subModules?.length
1877
+ ? !!item.isActive
1878
+ : (workspace ? samePage(pathname, itemUrl) : pathname === itemUrl) &&
1879
+ !hasActiveSubItem;
1858
1880
  return { itemUrl, activeSubItemUrl, hasActiveSubItem, isItemActive };
1859
1881
  });
1860
1882
  // A group opens when its page becomes current and otherwise stays as the
@@ -2022,7 +2044,7 @@ function AppSidebar({ modules, tools, activeModule, orgCode, approvalCounts, clo
2022
2044
  if (!open)
2023
2045
  return null;
2024
2046
  if (chrome === "workspace") {
2025
- return (jsxRuntime.jsxs("div", { className: erpTheme.cn("flex h-screen w-[244px] shrink-0 flex-col overflow-hidden border-r border-sidebar-border bg-sidebar text-sidebar-foreground",
2047
+ return (jsxRuntime.jsxs("div", { className: erpTheme.cn("flex h-screen w-[264px] shrink-0 flex-col overflow-hidden border-r border-sidebar-border bg-sidebar text-sidebar-foreground",
2026
2048
  // As a drawer (≤1024px) it floats over the page and needs the lift.
2027
2049
  "max-[1024px]:shadow-2xl", className), children: [header ? jsxRuntime.jsx("div", { className: "shrink-0 px-[18px] pt-8 pb-6", children: header }) : null, jsxRuntime.jsxs(SidebarContent, { className: "flex-1 overflow-y-auto px-[18px]", children: [jsxRuntime.jsx(NavMain, { items: modulesWithActiveState, orgCode: orgCode, approvalCounts: approvalCounts, closeOnNavigation: closeOnNavigation, chrome: "workspace", highlightActiveModule: highlightActiveModule }), jsxRuntime.jsx(NavTools, { tools: tools, closeOnNavigation: closeOnNavigation })] }), bottom ? jsxRuntime.jsx("div", { className: "shrink-0 px-[18px] pt-6", children: bottom }) : null, jsxRuntime.jsx("div", { className: "shrink-0 px-[18px] pb-3", children: footer })] }));
2028
2050
  }
@@ -3072,7 +3094,7 @@ function cx(...parts) {
3072
3094
  * name, and a spaced caption beneath it.
3073
3095
  */
3074
3096
  function WorkspaceBrand({ name, caption, mark, href, compact = false, className, }) {
3075
- const body = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: erpTheme.cn("grid shrink-0 place-items-center overflow-hidden rounded-[14px] [&>img]:size-full [&>img]:object-cover", compact ? "size-9 rounded-[11px]" : "size-11"), children: mark }), compact ? (jsxRuntime.jsx("span", { className: "sr-only", children: name })) : (jsxRuntime.jsxs("span", { className: "min-w-0", children: [jsxRuntime.jsx("span", { className: "block truncate text-2xl leading-tight font-extrabold tracking-[-0.05em]", children: name }), caption ? (jsxRuntime.jsx("small", { className: "-mt-0.5 block truncate text-xs font-semibold tracking-[0.29em] text-muted-foreground", children: caption })) : null] }))] }));
3097
+ const body = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: erpTheme.cn("grid shrink-0 place-items-center overflow-hidden rounded-[12px] bg-transparent [&>img]:size-full [&>img]:object-contain", compact ? "size-9 rounded-[11px]" : "size-11"), children: mark }), compact ? (jsxRuntime.jsx("span", { className: "sr-only", children: name })) : (jsxRuntime.jsxs("span", { className: "min-w-0", children: [jsxRuntime.jsx("span", { className: "block truncate text-2xl leading-tight font-medium tracking-[-0.05em]", children: name }), caption ? (jsxRuntime.jsx("small", { className: "mt-0.5 block truncate text-[9px] font-semibold tracking-[0.3em] text-muted-foreground", children: caption })) : null] }))] }));
3076
3098
  const cls = erpTheme.cn("flex items-center gap-[11px] rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", !compact && "px-4", className);
3077
3099
  return href ? (jsxRuntime.jsx(Link, { href: href, className: cls, "aria-label": compact ? name : undefined, children: body })) : (jsxRuntime.jsx("div", { className: cls, children: body }));
3078
3100
  }