@eintrek/erp-shell 0.1.73 → 0.1.75

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.
@@ -41,6 +41,12 @@ export interface GuideContext {
41
41
  can: (policyKey: string) => boolean;
42
42
  /** Whether an anchor is on screen now. */
43
43
  visible: (anchor: GuideAnchorKey) => boolean;
44
+ /**
45
+ * A section guide's live values, as the app last rendered them
46
+ * (`useSectionGuideValues` / `SectionGuideButton values`); empty
47
+ * elsewhere. Shell ≥ 0.1.75.
48
+ */
49
+ values?: Readonly<Record<string, unknown>>;
44
50
  }
45
51
  /** Text that may depend on the context. */
46
52
  export type GuideText = string | ((ctx: GuideContext) => string);
@@ -171,6 +177,12 @@ export interface GuideStep {
171
177
  status?: (ctx: GuideContext) => string | null | undefined;
172
178
  /** Skip the step (silently) when true. Runs every time it is entered. */
173
179
  skipIf?: (ctx: GuideContext) => boolean;
180
+ /**
181
+ * Section guides: the live values this step's copy uses
182
+ * (`ctx.values`). The step is skipped while any is missing (undefined,
183
+ * null, "" or a number that is not finite). Shell ≥ 0.1.75.
184
+ */
185
+ needs?: string[];
174
186
  /** A policy key the step needs (checked with `guides.can`); skipped when
175
187
  * the user lacks it. */
176
188
  requires?: string;
@@ -322,6 +334,95 @@ export interface GuideGate {
322
334
  /** The line on each mission in the panel while waiting: "ต้องมีพนักงาน 2 คน". */
323
335
  pending: string;
324
336
  }
337
+ /**
338
+ * A page guide ("คู่มือหน้านี้", shell ≥ 0.1.74): a read-only walk through one
339
+ * page's regions and buttons — what the page is for, what each column,
340
+ * filter and status means, what each button does. It never saves: only
341
+ * "view" and "click" steps (the engine drops a page guide with a "commit",
342
+ * "input" or "select" step, with a warning in development), and it runs as
343
+ * a replay, so nothing it touches writes tour progress. Authors:
344
+ * _claude-work/guides-r4/GUIDE-PAGES.md.
345
+ *
346
+ * Progress is one row per page per user, `<app>.page.<key>`, written once:
347
+ * when the first-visit offer is answered or ignored, or when the guide ends
348
+ * (finished → completed; exited or left → skipped). Opened again from "?",
349
+ * it writes nothing.
350
+ */
351
+ export interface GuidePage {
352
+ /** Stable, [a-z0-9-]: the row key is `${suite.app}.page.${key}` (≤ 64 characters). */
353
+ key: string;
354
+ /** Raise it to offer this page's guide again to everyone (this page only). */
355
+ version: number;
356
+ /**
357
+ * The page, as GuideTrigger.route ("/[code]/project/budgets",
358
+ * "/[code]/project/detail/[id]?view=gantt"); several for one page. When
359
+ * several page guides match, the most specific wins: one with `when`, then
360
+ * the most `?k=v` constraints, then the most static segments. If the
361
+ * winner is not for this user (`visible`, `requires`), there is none here.
362
+ */
363
+ route: string | string[];
364
+ /**
365
+ * An anchor that must be on screen for this guide to apply: sub-screens
366
+ * that share a URL (LIFF's leave list vs its form). The offer waits for it,
367
+ * and the guide ends when it goes.
368
+ */
369
+ when?: GuideAnchorKey;
370
+ /** "งบประมาณ": the offer card, "?" → "คู่มือหน้านี้" and the panel's list. ≤ 30 characters. */
371
+ title: string;
372
+ /**
373
+ * Where the panel's "คู่มือแต่ละหน้า" list opens it (`[code]` filled in).
374
+ * Omit it for pages that need an id (`[id]`): those are listed only on
375
+ * the page itself, through "คู่มือหน้านี้".
376
+ */
377
+ href?: GuideText;
378
+ /** Only `true` shows it (as GuideMission.visible): role, plan, module, menu. */
379
+ visible?: (ctx: GuideContext) => boolean | undefined;
380
+ /** A policy key the page needs (checked with `guides.can`). */
381
+ requires?: string;
382
+ /** false: never offered by itself, only opened from "?". Default true. */
383
+ offer?: boolean;
384
+ /**
385
+ * "view" and "click" steps only, ≤ 9 (the app's tests check the count).
386
+ * Steps take no `route`: the guide ends when the page is left. Every step
387
+ * is a checkpoint unless it says `checkpoint: false` (a step inside a
388
+ * dialog or menu a click opened: a closed dialog goes back to the click,
389
+ * and closing the guide there presses Escape).
390
+ */
391
+ steps: GuideStep[];
392
+ }
393
+ /**
394
+ * A section guide ("อธิบายส่วนนี้", shell ≥ 0.1.75): explains one section of
395
+ * a page — a number-heavy card or table — row by row, with the formula and
396
+ * this record's own numbers. Read-only ("view" steps only; a step may point
397
+ * at a row or cell inside the section with `targetId`), never offered by
398
+ * itself: it opens from the section's `SectionGuideButton` or from "?" →
399
+ * "อธิบายส่วนนี้ · <title>". Same switch and audience as page guides
400
+ * (`guides.<app>.pages` not off); one row per section guide per person,
401
+ * `<app>.section.<key>`, written once. Authors:
402
+ * _claude-work/guides-r4/GUIDE-PAGES.md §10.
403
+ */
404
+ export interface GuideSection {
405
+ /** Stable, [a-z0-9-]: the row key is `${suite.app}.section.${key}` (≤ 64 characters). */
406
+ key: string;
407
+ version: number;
408
+ /** The page(s) the section is on, as GuidePage.route. */
409
+ route: string | string[];
410
+ /** The section's root: the guide is there only while it is on screen. */
411
+ anchor: GuideAnchorKey;
412
+ /** "สรุปกระแสเงินสด": the "?" entry and the bubble's heading. ≤ 40 characters. */
413
+ title: string;
414
+ /** Only `true` shows it (as GuideMission.visible). */
415
+ visible?: (ctx: GuideContext) => boolean | undefined;
416
+ /** A policy key the section needs (checked with `guides.can`). */
417
+ requires?: string;
418
+ /**
419
+ * "view" steps (the default here), about 12 at most; titles ≤ 40 and whys
420
+ * ≤ 90 characters (the app's tests check). Copy may be a function of
421
+ * `ctx.values`; list what it uses in `needs`, and a step whose value is
422
+ * missing is skipped (so is one whose title comes out empty).
423
+ */
424
+ steps: GuideStep[];
425
+ }
325
426
  /** One app's whole tour: the welcome, the missions, the finish. */
326
427
  export interface GuideSuite {
327
428
  /** Progress key on the server, "<app>.<name>": "peoplx.first-run". */
@@ -365,6 +466,10 @@ export interface GuideSuite {
365
466
  };
366
467
  };
367
468
  missions: GuideMission[];
469
+ /** Page guides ("คู่มือหน้านี้"), shell ≥ 0.1.74. */
470
+ pages?: GuidePage[];
471
+ /** Section guides ("อธิบายส่วนนี้"), shell ≥ 0.1.75. */
472
+ sections?: GuideSection[];
368
473
  }
369
474
  /** AppLayoutShell's `guides` prop. */
370
475
  export interface GuideConfig {
@@ -378,4 +483,10 @@ export interface GuideConfig {
378
483
  * Default true.
379
484
  */
380
485
  ready?: boolean;
486
+ /**
487
+ * False holds the page guides alone; default: as `ready`. Lets pages run
488
+ * while the missions wait (Accounx's settings pages are open before
489
+ * `module.accounting` is known). Shell ≥ 0.1.74.
490
+ */
491
+ pagesReady?: boolean;
381
492
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var guideRuntime = require('./chunks/guide-runtime-CLVSNkTN.cjs');
4
+ var format = require('./chunks/format-CVazK0hP.cjs');
5
5
  require('react/jsx-runtime');
6
6
  require('react');
7
7
  require('react-dom');
@@ -9,22 +9,35 @@ require('@floating-ui/dom');
9
9
 
10
10
 
11
11
 
12
- exports.GUIDE_API_EVENT = guideRuntime.API_EVENT;
13
- exports.GUIDE_EVENT_PREFIX = guideRuntime.GUIDE_EVENT_PREFIX;
14
- exports.GUIDE_LAYER_ATTRIBUTE = guideRuntime.GUIDE_LAYER_ATTRIBUTE;
15
- exports.GuideProgressConflict = guideRuntime.GuideProgressConflict;
16
- exports.GuideRuntime = guideRuntime.GuideRuntime;
17
- exports.apiCallDetail = guideRuntime.apiCallDetail;
18
- exports.emitGuideEvent = guideRuntime.emitGuideEvent;
19
- exports.fillRoute = guideRuntime.fillRoute;
20
- exports.guideAnchor = guideRuntime.guideAnchor;
21
- exports.guideMenuShown = guideRuntime.guideMenuShown;
22
- exports.loadGuides = guideRuntime.loadGuides;
23
- exports.matchApiPath = guideRuntime.matchApiPath;
24
- exports.matchRoute = guideRuntime.matchRoute;
25
- exports.openGuidePanel = guideRuntime.openGuidePanel;
26
- exports.reportApiCall = guideRuntime.reportApiCall;
27
- exports.reportApiEvent = guideRuntime.reportApiEvent;
28
- exports.storeGuides = guideRuntime.storeGuides;
29
- exports.useGuideEntry = guideRuntime.useGuideEntry;
12
+ exports.GUIDE_API_EVENT = format.API_EVENT;
13
+ exports.GUIDE_EVENT_PREFIX = format.GUIDE_EVENT_PREFIX;
14
+ exports.GUIDE_LAYER_ATTRIBUTE = format.GUIDE_LAYER_ATTRIBUTE;
15
+ exports.GuideProgressConflict = format.GuideProgressConflict;
16
+ exports.GuideRuntime = format.GuideRuntime;
17
+ exports.SectionGuideButton = format.SectionGuideButton;
18
+ exports.THAI_MONTHS_SHORT = format.THAI_MONTHS_SHORT;
19
+ exports.apiCallDetail = format.apiCallDetail;
20
+ exports.emitGuideEvent = format.emitGuideEvent;
21
+ exports.fillRoute = format.fillRoute;
22
+ exports.formatBaht = format.formatBaht;
23
+ exports.guideAnchor = format.guideAnchor;
24
+ exports.guideMenuItems = format.guideMenuItems;
25
+ exports.guideMenuShown = format.guideMenuShown;
26
+ exports.loadGuides = format.loadGuides;
27
+ exports.matchApiPath = format.matchApiPath;
28
+ exports.matchRoute = format.matchRoute;
29
+ exports.openGuidePanel = format.openGuidePanel;
30
+ exports.openPageGuide = format.openPageGuide;
31
+ exports.openPagesPanel = format.openPagesPanel;
32
+ exports.openSectionGuide = format.openSectionGuide;
33
+ exports.reportApiCall = format.reportApiCall;
34
+ exports.reportApiEvent = format.reportApiEvent;
35
+ exports.setSectionGuideValues = format.setSectionGuideValues;
36
+ exports.storeGuides = format.storeGuides;
37
+ exports.thaiMonth = format.thaiMonth;
38
+ exports.thaiMonthYear = format.thaiMonthYear;
39
+ exports.useGuideEntry = format.useGuideEntry;
40
+ exports.usePageGuideEntry = format.usePageGuideEntry;
41
+ exports.usePageGuides = format.usePageGuides;
42
+ exports.useSectionGuideValues = format.useSectionGuideValues;
30
43
  //# sourceMappingURL=guide-core.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"guide-core.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"guide-core.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- export { A as GUIDE_API_EVENT, G as GUIDE_EVENT_PREFIX, a as GUIDE_LAYER_ATTRIBUTE, c as GuideProgressConflict, d as GuideRuntime, e as apiCallDetail, f as emitGuideEvent, g as fillRoute, h as guideAnchor, i as guideMenuShown, l as loadGuides, m as matchApiPath, j as matchRoute, o as openGuidePanel, r as reportApiCall, k as reportApiEvent, s as storeGuides, u as useGuideEntry } from './chunks/guide-runtime-C7Z00QwM.esm.js';
2
+ export { A as GUIDE_API_EVENT, G as GUIDE_EVENT_PREFIX, a as GUIDE_LAYER_ATTRIBUTE, c as GuideProgressConflict, d as GuideRuntime, S as SectionGuideButton, T as THAI_MONTHS_SHORT, e as apiCallDetail, f as emitGuideEvent, g as fillRoute, h as formatBaht, i as guideAnchor, j as guideMenuItems, k as guideMenuShown, l as loadGuides, m as matchApiPath, n as matchRoute, o as openGuidePanel, p as openPageGuide, q as openPagesPanel, r as openSectionGuide, s as reportApiCall, t as reportApiEvent, u as setSectionGuideValues, v as storeGuides, w as thaiMonth, x as thaiMonthYear, y as useGuideEntry, z as usePageGuideEntry, B as usePageGuides, C as useSectionGuideValues } from './chunks/format-DCTuLpBU.esm.js';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
5
5
  import 'react-dom';
@@ -5,8 +5,11 @@ export interface HelpButtonProps {
5
5
  * The ช่วยเหลือ button in the top bar. Drawn in the header's ink (the
6
6
  * --header-* variables of the workspace chrome, white on the brand chrome).
7
7
  *
8
- * When this user has a guided tour in this app, it opens a small menu:
9
- * "ไกด์เริ่มต้น n/N" (the quest panel) and "ติดต่อทีมช่วยเหลือ" (the help
10
- * dialog). Otherwise one press opens the help dialog, as before.
8
+ * When this user has something to open here, it opens a small menu:
9
+ * "คู่มือหน้านี้" (this page's guide), "อธิบายส่วนนี้ · <title>" for each
10
+ * section guide on screen, "ไกด์เริ่มต้น n/N" (the quest
11
+ * panel), "คู่มือทุกหน้า" (the panel's page guides) and "ติดต่อทีมช่วยเหลือ"
12
+ * (the help dialog), each only when it has something. Otherwise one press
13
+ * opens the help dialog, as before.
11
14
  */
12
15
  export declare function HelpButton({ className }: HelpButtonProps): import("react/jsx-runtime").JSX.Element;
package/dist/index.cjs CHANGED
@@ -14,7 +14,7 @@ var Link = require('next/link');
14
14
  var reactDom = require('react-dom');
15
15
  var nextThemes = require('next-themes');
16
16
  var axios = require('axios');
17
- var guideRuntime = require('./chunks/guide-runtime-CLVSNkTN.cjs');
17
+ var format = require('./chunks/format-CVazK0hP.cjs');
18
18
  var reactQueryDevtools = require('@tanstack/react-query-devtools');
19
19
  require('@floating-ui/dom');
20
20
 
@@ -1680,7 +1680,7 @@ instance.interceptors.response.use(response => {
1680
1680
  }
1681
1681
  // The guided tour advances on saves that succeeded (method, path, status
1682
1682
  // and the record's id only; never the body).
1683
- guideRuntime.reportApiEvent(response);
1683
+ format.reportApiEvent(response);
1684
1684
  return response;
1685
1685
  }, async (error) => {
1686
1686
  const originalRequest = error.config;
@@ -1697,7 +1697,7 @@ instance.interceptors.response.use(response => {
1697
1697
  return instance(originalRequest);
1698
1698
  }
1699
1699
  await redirectToLogout();
1700
- guideRuntime.reportApiEvent(error);
1700
+ format.reportApiEvent(error);
1701
1701
  return Promise.reject(toApiError(error));
1702
1702
  }
1703
1703
  // The server refuses every organisation-scoped call when the account has
@@ -1713,7 +1713,7 @@ instance.interceptors.response.use(response => {
1713
1713
  if (listed !== undefined)
1714
1714
  return listed;
1715
1715
  // A refused save keeps the tour's step and shows the server's message.
1716
- guideRuntime.reportApiEvent(error);
1716
+ format.reportApiEvent(error);
1717
1717
  return Promise.reject(toApiError(error));
1718
1718
  });
1719
1719
 
@@ -1740,7 +1740,7 @@ function platformApiBase(app) {
1740
1740
  * This package's version, attached to help messages so the back office can
1741
1741
  * tell which shell a report came from. Keep in step with package.json.
1742
1742
  */
1743
- const SHELL_VERSION = "0.1.73";
1743
+ const SHELL_VERSION = "0.1.75";
1744
1744
 
1745
1745
  const HELP_APP_LABEL = {
1746
1746
  platform: "แพลตฟอร์ม",
@@ -2412,11 +2412,11 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
2412
2412
  // A one-line row: a link, lit on its own page only. No open
2413
2413
  // state, no title tooltip.
2414
2414
  return (jsxRuntime.jsxs(React__namespace.Fragment, { children: [groupHeading, jsxRuntime.jsx(SidebarMenuItem, { children: jsxRuntime.jsx(SidebarMenuButton, { asChild: true, isActive: isItemActive, className: workspaceButton, children: jsxRuntime.jsxs(Link, { href: itemUrl, prefetch: false, className: "flex items-center w-full min-w-0 flex-1 " +
2415
- (workspace ? "gap-3" : "gap-2"), onClick: handleNavLinkClick, "aria-current": isItemActive ? "page" : undefined, ...guideRuntime.guideAnchor(`nav.${item.id}`), children: [icon, jsxRuntime.jsx("span", { id: item.id, className: "flex-1 truncate", children: item.titleTh }), badge] }) }) })] }, item.id));
2415
+ (workspace ? "gap-3" : "gap-2"), onClick: handleNavLinkClick, "aria-current": isItemActive ? "page" : undefined, ...format.guideAnchor(`nav.${item.id}`), children: [icon, jsxRuntime.jsx("span", { id: item.id, className: "flex-1 truncate", children: item.titleTh }), badge] }) }) })] }, item.id));
2416
2416
  }
2417
2417
  // A group: its header opens and shuts it (the chevron says which).
2418
2418
  // An open group is not the current page — only a row in it is.
2419
- return (jsxRuntime.jsxs(React__namespace.Fragment, { children: [groupHeading, jsxRuntime.jsx(erpTheme.Collapsible, { asChild: true, open: openIds.has(item.id), onOpenChange: isOpen => setItemOpen(item.id, isOpen), className: "group/collapsible", children: jsxRuntime.jsxs(SidebarMenuItem, { children: [jsxRuntime.jsx(erpTheme.CollapsibleTrigger, { asChild: true, children: jsxRuntime.jsxs(SidebarMenuButton, { isActive: isItemActive, className: workspaceButton, ...guideRuntime.guideAnchor(`nav.${item.id}`), children: [icon, jsxRuntime.jsx("span", { id: item.id, className: "flex-1 truncate text-left", children: item.titleTh }), jsxRuntime.jsx(lucideReact.ChevronRight, { className: "ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" })] }) }), item.subModules && (jsxRuntime.jsx(erpTheme.CollapsibleContent, { children: jsxRuntime.jsx(SidebarMenuSub, { className: workspace
2419
+ return (jsxRuntime.jsxs(React__namespace.Fragment, { children: [groupHeading, jsxRuntime.jsx(erpTheme.Collapsible, { asChild: true, open: openIds.has(item.id), onOpenChange: isOpen => setItemOpen(item.id, isOpen), className: "group/collapsible", children: jsxRuntime.jsxs(SidebarMenuItem, { children: [jsxRuntime.jsx(erpTheme.CollapsibleTrigger, { asChild: true, children: jsxRuntime.jsxs(SidebarMenuButton, { isActive: isItemActive, className: workspaceButton, ...format.guideAnchor(`nav.${item.id}`), children: [icon, jsxRuntime.jsx("span", { id: item.id, className: "flex-1 truncate text-left", children: item.titleTh }), jsxRuntime.jsx(lucideReact.ChevronRight, { className: "ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" })] }) }), item.subModules && (jsxRuntime.jsx(erpTheme.CollapsibleContent, { children: jsxRuntime.jsx(SidebarMenuSub, { className: workspace
2420
2420
  ? "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]"
2421
2421
  : undefined, children: item.subModules.map((subItem, subIndex) => {
2422
2422
  const subItemUrl = buildUrlWithCode(code, subItem.url);
@@ -2429,7 +2429,7 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
2429
2429
  subItem.subPages &&
2430
2430
  subItem.subPages.length > 0) {
2431
2431
  return (jsxRuntime.jsx(erpTheme.Collapsible, { asChild: true, defaultOpen: isSubItemActive || openSub === subIndex, className: "group/sub-collapsible", children: jsxRuntime.jsxs(SidebarMenuSubItem, { children: [jsxRuntime.jsx(erpTheme.CollapsibleTrigger, { asChild: true, children: jsxRuntime.jsxs(SidebarMenuSubButton, { className: "w-full", isActive: isSubItemActive &&
2432
- activeSubPage === null, ...guideRuntime.guideAnchor(`nav.${subItem.id}`), children: [SubIcon && (jsxRuntime.jsx(SubIcon, { className: "sidebar-submodule-icon flex-shrink-0" })), jsxRuntime.jsx("span", { className: "flex-1 truncate", children: subItem.titleTh }), jsxRuntime.jsx(lucideReact.ChevronDown, { className: "ml-auto size-4 transition-transform duration-200 group-data-[state=open]/sub-collapsible:rotate-180" })] }) }), jsxRuntime.jsx(erpTheme.CollapsibleContent, { children: jsxRuntime.jsx(SidebarMenuSub, { className: "ml-4", children: subItem.subPages.map((subPage, subPageIndex) => {
2432
+ activeSubPage === null, ...format.guideAnchor(`nav.${subItem.id}`), children: [SubIcon && (jsxRuntime.jsx(SubIcon, { className: "sidebar-submodule-icon flex-shrink-0" })), jsxRuntime.jsx("span", { className: "flex-1 truncate", children: subItem.titleTh }), jsxRuntime.jsx(lucideReact.ChevronDown, { className: "ml-auto size-4 transition-transform duration-200 group-data-[state=open]/sub-collapsible:rotate-180" })] }) }), jsxRuntime.jsx(erpTheme.CollapsibleContent, { children: jsxRuntime.jsx(SidebarMenuSub, { className: "ml-4", children: subItem.subPages.map((subPage, subPageIndex) => {
2433
2433
  const subPageUrl = buildUrlWithCode(code, subPage.url);
2434
2434
  const isSubPageActive = isSubItemActive &&
2435
2435
  activeSubPage === subPageIndex;
@@ -2437,7 +2437,7 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
2437
2437
  return (jsxRuntime.jsx(SidebarMenuSubItem, { children: jsxRuntime.jsx(SidebarMenuSubButton, { asChild: true, className: "font-thai w-full text-sm" +
2438
2438
  (isSubPageActive
2439
2439
  ? " font-medium"
2440
- : ""), isActive: isSubPageActive, children: jsxRuntime.jsxs(Link, { href: subPageUrl, prefetch: false, className: "w-full flex items-center", ...guideRuntime.guideAnchor(`nav.${subPage.id}`), onClick: handleNavLinkClick, "aria-current": isSubPageActive
2440
+ : ""), isActive: isSubPageActive, children: jsxRuntime.jsxs(Link, { href: subPageUrl, prefetch: false, className: "w-full flex items-center", ...format.guideAnchor(`nav.${subPage.id}`), onClick: handleNavLinkClick, "aria-current": isSubPageActive
2441
2441
  ? "page"
2442
2442
  : undefined, children: [SubPageIcon && (jsxRuntime.jsx(SubPageIcon, { className: "sidebar-subpage-icon flex-shrink-0" })), jsxRuntime.jsx("span", { className: "flex-1 truncate", children: subPage.titleTh })] }) }) }, subPage.id));
2443
2443
  }) }) })] }) }, subItem.id));
@@ -2445,7 +2445,7 @@ function NavMain({ items, orgCode, closeOnNavigation = false, approvalCounts, ch
2445
2445
  return (jsxRuntime.jsx(SidebarMenuSubItem, { children: jsxRuntime.jsx(SidebarMenuSubButton, { asChild: true, className: "w-full" +
2446
2446
  (workspace && isSubItemActive
2447
2447
  ? " font-semibold"
2448
- : ""), isActive: isSubItemActive, children: jsxRuntime.jsxs(Link, { href: subItemUrl, prefetch: false, className: "w-full flex items-center", ...guideRuntime.guideAnchor(`nav.${subItem.id}`), onClick: handleNavLinkClick, "aria-current": isSubItemActive ? "page" : undefined, children: [SubIcon && (jsxRuntime.jsx(SubIcon, { className: "sidebar-submodule-icon flex-shrink-0" })), jsxRuntime.jsx("span", { className: "flex-1 truncate", children: subItem.titleTh }), approvalCount > 0 && (jsxRuntime.jsx(erpTheme.Badge, { variant: "destructive", className: "ml-auto h-5 min-w-5 px-1.5 text-xs font-medium tabular-nums", children: approvalCount > 99
2448
+ : ""), isActive: isSubItemActive, children: jsxRuntime.jsxs(Link, { href: subItemUrl, prefetch: false, className: "w-full flex items-center", ...format.guideAnchor(`nav.${subItem.id}`), onClick: handleNavLinkClick, "aria-current": isSubItemActive ? "page" : undefined, children: [SubIcon && (jsxRuntime.jsx(SubIcon, { className: "sidebar-submodule-icon flex-shrink-0" })), jsxRuntime.jsx("span", { className: "flex-1 truncate", children: subItem.titleTh }), approvalCount > 0 && (jsxRuntime.jsx(erpTheme.Badge, { variant: "destructive", className: "ml-auto h-5 min-w-5 px-1.5 text-xs font-medium tabular-nums", children: approvalCount > 99
2449
2449
  ? "99+"
2450
2450
  : approvalCount }))] }) }) }, subItem.id));
2451
2451
  }) }) }))] }) })] }, item.id));
@@ -2572,15 +2572,25 @@ const BUTTON_CLASS = "grid size-[34px] shrink-0 place-items-center rounded-lg te
2572
2572
  * The ช่วยเหลือ button in the top bar. Drawn in the header's ink (the
2573
2573
  * --header-* variables of the workspace chrome, white on the brand chrome).
2574
2574
  *
2575
- * When this user has a guided tour in this app, it opens a small menu:
2576
- * "ไกด์เริ่มต้น n/N" (the quest panel) and "ติดต่อทีมช่วยเหลือ" (the help
2577
- * dialog). Otherwise one press opens the help dialog, as before.
2575
+ * When this user has something to open here, it opens a small menu:
2576
+ * "คู่มือหน้านี้" (this page's guide), "อธิบายส่วนนี้ · <title>" for each
2577
+ * section guide on screen, "ไกด์เริ่มต้น n/N" (the quest
2578
+ * panel), "คู่มือทุกหน้า" (the panel's page guides) and "ติดต่อทีมช่วยเหลือ"
2579
+ * (the help dialog), each only when it has something. Otherwise one press
2580
+ * opens the help dialog, as before.
2578
2581
  */
2579
2582
  function HelpButton({ className }) {
2580
- const guide = guideRuntime.useGuideEntry();
2583
+ const guide = format.useGuideEntry();
2584
+ const pages = format.usePageGuides();
2581
2585
  const toGuide = React.useRef(false);
2582
2586
  const cls = BUTTON_CLASS + (className ? ` ${className}` : "");
2583
- if (!guideRuntime.guideMenuShown(guide)) {
2587
+ const items = format.guideMenuItems(guide, pages);
2588
+ // The tour takes focus (a step, the panel); the button must not take it back.
2589
+ const toTour = (open) => () => {
2590
+ toGuide.current = true;
2591
+ setTimeout(open, 0);
2592
+ };
2593
+ if (!items.length) {
2584
2594
  return (jsxRuntime.jsx("button", { type: "button", onClick: openHelp, "aria-label": "\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D", title: "\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D", className: cls, "data-guide": "shell.help", children: jsxRuntime.jsx(lucideReact.CircleHelp, { className: "size-5", strokeWidth: 1.65 }) }));
2585
2595
  }
2586
2596
  return (jsxRuntime.jsxs(erpTheme.DropdownMenu, { children: [jsxRuntime.jsx(erpTheme.DropdownMenuTrigger, { asChild: true, children: jsxRuntime.jsx("button", { type: "button", "aria-label": "\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D", title: "\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D", className: cls, "data-guide": "shell.help", children: jsxRuntime.jsx(lucideReact.CircleHelp, { className: "size-5", strokeWidth: 1.65 }) }) }), jsxRuntime.jsxs(erpTheme.DropdownMenuContent, { align: "end", sideOffset: 8, className: "w-64", onCloseAutoFocus: e => {
@@ -2589,10 +2599,9 @@ function HelpButton({ className }) {
2589
2599
  e.preventDefault();
2590
2600
  toGuide.current = false;
2591
2601
  }
2592
- }, children: [jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onSelect: () => {
2593
- toGuide.current = true;
2594
- setTimeout(guideRuntime.openGuidePanel, 0);
2595
- }, className: "gap-2.5", children: [jsxRuntime.jsx(lucideReact.Flag, { className: "size-4" }), jsxRuntime.jsx("span", { className: "flex-1", children: "\u0E44\u0E01\u0E14\u0E4C\u0E40\u0E23\u0E34\u0E48\u0E21\u0E15\u0E49\u0E19" }), jsxRuntime.jsxs("span", { className: "text-xs font-semibold text-primary tabular-nums", children: [guide.done, "/", guide.total] })] }), jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onSelect: () => setTimeout(openHelp, 0), className: "gap-2.5", children: [jsxRuntime.jsx(lucideReact.MessageCircle, { className: "size-4" }), jsxRuntime.jsx("span", { className: "flex-1", children: "\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E17\u0E35\u0E21\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D" })] })] })] }));
2602
+ }, children: [items.includes("page") ? (jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onSelect: toTour(format.openPageGuide), className: "gap-2.5", children: [jsxRuntime.jsx(lucideReact.BookOpen, { className: "size-4" }), jsxRuntime.jsx("span", { className: "flex-1", children: "\u0E04\u0E39\u0E48\u0E21\u0E37\u0E2D\u0E2B\u0E19\u0E49\u0E32\u0E19\u0E35\u0E49" })] })) : null, items.includes("sections")
2603
+ ? (pages.sections ?? []).map(sec => (jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onSelect: toTour(() => format.openSectionGuide(sec.key)), className: "gap-2.5", children: [jsxRuntime.jsx(lucideReact.Info, { className: "size-4" }), jsxRuntime.jsxs("span", { className: "flex-1", children: ["\u0E2D\u0E18\u0E34\u0E1A\u0E32\u0E22\u0E2A\u0E48\u0E27\u0E19\u0E19\u0E35\u0E49 \u00B7 ", sec.title] })] }, sec.key)))
2604
+ : null, items.includes("missions") && guide ? (jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onSelect: toTour(format.openGuidePanel), className: "gap-2.5", children: [jsxRuntime.jsx(lucideReact.Flag, { className: "size-4" }), jsxRuntime.jsx("span", { className: "flex-1", children: "\u0E44\u0E01\u0E14\u0E4C\u0E40\u0E23\u0E34\u0E48\u0E21\u0E15\u0E49\u0E19" }), jsxRuntime.jsxs("span", { className: "text-xs font-semibold text-primary tabular-nums", children: [guide.done, "/", guide.total] })] })) : null, items.includes("pages") ? (jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onSelect: toTour(format.openPagesPanel), className: "gap-2.5", children: [jsxRuntime.jsx(lucideReact.Library, { className: "size-4" }), jsxRuntime.jsx("span", { className: "flex-1", children: "\u0E04\u0E39\u0E48\u0E21\u0E37\u0E2D\u0E17\u0E38\u0E01\u0E2B\u0E19\u0E49\u0E32" })] })) : null, jsxRuntime.jsxs(erpTheme.DropdownMenuItem, { onSelect: () => setTimeout(openHelp, 0), className: "gap-2.5", children: [jsxRuntime.jsx(lucideReact.MessageCircle, { className: "size-4" }), jsxRuntime.jsx("span", { className: "flex-1", children: "\u0E15\u0E34\u0E14\u0E15\u0E48\u0E2D\u0E17\u0E35\u0E21\u0E0A\u0E48\u0E27\u0E22\u0E40\u0E2B\u0E25\u0E37\u0E2D" })] })] })] }));
2596
2605
  }
2597
2606
 
2598
2607
  const hasPaginationShape = (payload) => {
@@ -3603,7 +3612,7 @@ function platformGuideProgress(organizationId) {
3603
3612
 
3604
3613
  function GuideHost(props) {
3605
3614
  // The boundary covers the adapters too (useSidebar throws without its provider).
3606
- return (jsxRuntime.jsx(guideRuntime.GuideErrorBoundary, { children: jsxRuntime.jsx(React.Suspense, { fallback: null, children: jsxRuntime.jsx(GuideHostInner, { ...props }) }) }));
3615
+ return (jsxRuntime.jsx(format.GuideErrorBoundary, { children: jsxRuntime.jsx(React.Suspense, { fallback: null, children: jsxRuntime.jsx(GuideHostInner, { ...props }) }) }));
3607
3616
  }
3608
3617
  let fallbackClient = null;
3609
3618
  function useShellQueryClient() {
@@ -3654,7 +3663,7 @@ function GuideHostInner({ config, organizationId, code }) {
3654
3663
  }, [client, organizationId, suite.app, suite.key, suite.version]);
3655
3664
  const routing = React.useMemo(() => ({ pathname, search, navigate: (href) => router.push(href) }), [pathname, search, router]);
3656
3665
  const menu = React.useMemo(() => ({ open: sidebarOpen }), [sidebarOpen]);
3657
- return (jsxRuntime.jsx(guideRuntime.GuideRuntime, { config: config, organizationId: organizationId, code: code, routing: routing, progress: progress, isBusy: isBusy, menu: menu, hidden: employeeBlocked }));
3666
+ return (jsxRuntime.jsx(format.GuideRuntime, { config: config, organizationId: organizationId, code: code, routing: routing, progress: progress, isBusy: isBusy, menu: menu, hidden: employeeBlocked }));
3658
3667
  }
3659
3668
 
3660
3669
  /**
@@ -3863,7 +3872,7 @@ function AppLayoutShell({ children, sidebar, loadingComponent, breadcrumb, permi
3863
3872
  // As in the design: the mark, then a menu button, and the
3864
3873
  // organisation on the right — no trail, no title, so the
3865
3874
  // organisation's name has the room it needs on a phone.
3866
- jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [mobileBrand ? (jsxRuntime.jsx("div", { className: "shrink-0", children: mobileBrand })) : null, jsxRuntime.jsx("button", { type: "button", onClick: () => setOpen(!open), "aria-label": open ? "ปิดเมนู" : "เปิดเมนู", ...guideRuntime.guideAnchor("shell.menu-toggle"), className: "grid size-[34px] shrink-0 place-items-center rounded-lg text-[var(--header-fg-muted)] transition-colors hover:bg-[var(--header-hover)] hover:text-[var(--header-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--header-fg-muted)]", children: jsxRuntime.jsx(lucideReact.LayoutGrid, { className: "size-5", strokeWidth: 1.65 }) })] })) : (jsxRuntime.jsx(SidebarTrigger, { className: "shrink-0 text-[var(--header-fg-muted)] hover:bg-[var(--header-hover)] hover:text-[var(--header-fg)] [&_svg]:!text-current" })), jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-0.5", children: [jsxRuntime.jsx("div", { className: "hidden min-w-0 sm:block", children: trail }), jsxRuntime.jsx("div", { id: "header-title", className: "min-w-0 max-sm:hidden" })] }), headerEnd || showHelp ? (jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3 sm:gap-5", children: [headerEnd, showHelp ? jsxRuntime.jsx(HelpButton, {}) : null] })) : null] })] })) : (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: trail }), headerEnd || showHelp ? (jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [headerEnd, showHelp ? jsxRuntime.jsx(HelpButton, {}) : null] })) : null] }), 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: workspace
3875
+ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [mobileBrand ? (jsxRuntime.jsx("div", { className: "shrink-0", children: mobileBrand })) : null, jsxRuntime.jsx("button", { type: "button", onClick: () => setOpen(!open), "aria-label": open ? "ปิดเมนู" : "เปิดเมนู", ...format.guideAnchor("shell.menu-toggle"), className: "grid size-[34px] shrink-0 place-items-center rounded-lg text-[var(--header-fg-muted)] transition-colors hover:bg-[var(--header-hover)] hover:text-[var(--header-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--header-fg-muted)]", children: jsxRuntime.jsx(lucideReact.LayoutGrid, { className: "size-5", strokeWidth: 1.65 }) })] })) : (jsxRuntime.jsx(SidebarTrigger, { className: "shrink-0 text-[var(--header-fg-muted)] hover:bg-[var(--header-hover)] hover:text-[var(--header-fg)] [&_svg]:!text-current" })), jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-0.5", children: [jsxRuntime.jsx("div", { className: "hidden min-w-0 sm:block", children: trail }), jsxRuntime.jsx("div", { id: "header-title", className: "min-w-0 max-sm:hidden" })] }), headerEnd || showHelp ? (jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3 sm:gap-5", children: [headerEnd, showHelp ? jsxRuntime.jsx(HelpButton, {}) : null] })) : null] })] })) : (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: trail }), headerEnd || showHelp ? (jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [headerEnd, showHelp ? jsxRuntime.jsx(HelpButton, {}) : null] })) : null] }), 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: workspace
3867
3876
  ? "relative z-10 px-[18px] pt-[26px] pb-4 sm:px-6 lg:px-10 lg:pt-[38px]"
3868
3877
  : cx("relative z-10 pr-6", needsLeftGutter && "pl-6"), children: refusePage
3869
3878
  ? (notMemberComponent ?? (jsxRuntime.jsx(AccessDenied, { title: "\u0E44\u0E21\u0E48\u0E21\u0E35\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E4C\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23\u0E19\u0E35\u0E49", message: `บัญชีนี้ไม่ได้เป็นสมาชิกขององค์กร ${code}`, homeHref: selectOrganizationUrl, homeLabel: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E01\u0E23" })))
@@ -4436,18 +4445,31 @@ Object.defineProperty(exports, "isAxiosError", {
4436
4445
  enumerable: true,
4437
4446
  get: function () { return axios.isAxiosError; }
4438
4447
  });
4439
- exports.GUIDE_API_EVENT = guideRuntime.API_EVENT;
4440
- exports.GUIDE_EVENT_PREFIX = guideRuntime.GUIDE_EVENT_PREFIX;
4441
- exports.GUIDE_LAYER_ATTRIBUTE = guideRuntime.GUIDE_LAYER_ATTRIBUTE;
4442
- exports.GuideProgressConflict = guideRuntime.GuideProgressConflict;
4443
- exports.GuideRuntime = guideRuntime.GuideRuntime;
4444
- exports.emitGuideEvent = guideRuntime.emitGuideEvent;
4445
- exports.guideAnchor = guideRuntime.guideAnchor;
4446
- exports.guideMenuShown = guideRuntime.guideMenuShown;
4447
- exports.openGuidePanel = guideRuntime.openGuidePanel;
4448
- exports.reportApiCall = guideRuntime.reportApiCall;
4449
- exports.reportApiEvent = guideRuntime.reportApiEvent;
4450
- exports.useGuideEntry = guideRuntime.useGuideEntry;
4448
+ exports.GUIDE_API_EVENT = format.API_EVENT;
4449
+ exports.GUIDE_EVENT_PREFIX = format.GUIDE_EVENT_PREFIX;
4450
+ exports.GUIDE_LAYER_ATTRIBUTE = format.GUIDE_LAYER_ATTRIBUTE;
4451
+ exports.GuideProgressConflict = format.GuideProgressConflict;
4452
+ exports.GuideRuntime = format.GuideRuntime;
4453
+ exports.SectionGuideButton = format.SectionGuideButton;
4454
+ exports.THAI_MONTHS_SHORT = format.THAI_MONTHS_SHORT;
4455
+ exports.emitGuideEvent = format.emitGuideEvent;
4456
+ exports.formatBaht = format.formatBaht;
4457
+ exports.guideAnchor = format.guideAnchor;
4458
+ exports.guideMenuItems = format.guideMenuItems;
4459
+ exports.guideMenuShown = format.guideMenuShown;
4460
+ exports.openGuidePanel = format.openGuidePanel;
4461
+ exports.openPageGuide = format.openPageGuide;
4462
+ exports.openPagesPanel = format.openPagesPanel;
4463
+ exports.openSectionGuide = format.openSectionGuide;
4464
+ exports.reportApiCall = format.reportApiCall;
4465
+ exports.reportApiEvent = format.reportApiEvent;
4466
+ exports.setSectionGuideValues = format.setSectionGuideValues;
4467
+ exports.thaiMonth = format.thaiMonth;
4468
+ exports.thaiMonthYear = format.thaiMonthYear;
4469
+ exports.useGuideEntry = format.useGuideEntry;
4470
+ exports.usePageGuideEntry = format.usePageGuideEntry;
4471
+ exports.usePageGuides = format.usePageGuides;
4472
+ exports.useSectionGuideValues = format.useSectionGuideValues;
4451
4473
  exports.AccessDenied = AccessDenied;
4452
4474
  exports.ApiError = ApiError;
4453
4475
  exports.AppBreadcrumb = AppBreadcrumb;