@eintrek/erp-shell 0.1.71 → 0.1.73

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.
@@ -357,6 +357,16 @@ function publishGuideEntry(next, openPanel) {
357
357
  emit();
358
358
  }
359
359
  }
360
+ /**
361
+ * Whether the "?" menu offers "ไกด์เริ่มต้น": a tour whose missions are all
362
+ * hidden for this user (`visible`, e.g. Plannex without an employee record)
363
+ * has nothing to open, and "0/0" led to an empty panel. The entry itself
364
+ * stays published: apps wait on it before they load the facts that make
365
+ * missions visible.
366
+ */
367
+ function guideMenuShown(e) {
368
+ return !!e && e.total > 0;
369
+ }
360
370
  /** Opens the tour's quest panel, when this user has a tour here. */
361
371
  function openGuidePanel() {
362
372
  openPanelImpl?.();
@@ -747,11 +757,34 @@ function announceStep(d, suite, env) {
747
757
  const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
748
758
  const title = s.bridge ? COPY.bridgeTitle : plainTitle(resolveText(step.title, ctx));
749
759
  const why = s.bridge ? COPY.bridgeWhy(suite.appName) : plainTitle(resolveText(step.why, ctx));
760
+ const pos = stepPosition(mission, s.index, env, d.state.progress.context);
750
761
  d.effects.push({
751
762
  type: "announce",
752
- text: `ขั้นที่ ${s.index + 1} จาก ${mission.steps.length}. ${title}${why ? `. ${why}` : ""}`,
763
+ text: `ขั้นที่ ${pos.no} จาก ${pos.total}. ${title}${why ? `. ${why}` : ""}`,
753
764
  });
754
765
  }
766
+ /**
767
+ * "ขั้นที่ no จาก total" counting only the steps this person will see: a step
768
+ * whose `requires` they lack or whose `skipIf` holds is left out (the step on
769
+ * screen always counts).
770
+ */
771
+ function stepPosition(mission, index, env, context) {
772
+ let no = 0;
773
+ let total = 0;
774
+ mission.steps.forEach((step, i) => {
775
+ if (i !== index) {
776
+ if (step.requires && !safe(() => env.can(step.requires), true))
777
+ return;
778
+ const ctx = guideContext(env, context, routeParamsFor(step, env));
779
+ if (step.skipIf && safe(() => step.skipIf(ctx), false))
780
+ return;
781
+ }
782
+ total++;
783
+ if (i <= index)
784
+ no = total;
785
+ });
786
+ return { no: Math.max(no, 1), total: Math.max(total, 1) };
787
+ }
755
788
  /** "กด [บันทึก]" → "กด บันทึก" (for screen readers). */
756
789
  function plainTitle(t) {
757
790
  return t.replace(/\[([^\]]+)\]/g, "$1");
@@ -800,6 +833,7 @@ function enterStep(d, suite, env, mission, index, opts) {
800
833
  const where = placeRoute(steps, i);
801
834
  const away = !!where && !matchRoute(where, env.pathname, env.search, env.code);
802
835
  const bridge = !away && needsBridge(step, env, ctx);
836
+ const nts = !away && !bridge && nothingToSaveHolds(step, env, ctx);
803
837
  setScreen(d, {
804
838
  name: "step",
805
839
  mission: mission.key,
@@ -811,7 +845,9 @@ function enterStep(d, suite, env, mission, index, opts) {
811
845
  confirmExit: false,
812
846
  misses: 0,
813
847
  armed: hiddenAnchorShown(step, env),
814
- nothingToSave: !away && !bridge && nothingToSaveHolds(step, env, ctx),
848
+ nothingToSave: nts,
849
+ status: away || bridge ? null : stepStatus(shownStep(step, nts), ctx),
850
+ replayNext: !away && !bridge && opts.replay && replayCanGoOn(mission, i, env, d.state.progress.context, nts),
815
851
  });
816
852
  if (!opts.replay) {
817
853
  const cp = checkpointAtOrBefore(steps, i);
@@ -854,6 +890,54 @@ function setNothingToSave(d, suite, env, on) {
854
890
  if (stepTargetVisible(step, env, ctx, s.bridge))
855
891
  announceStep(d, suite, env);
856
892
  }
893
+ /** The step's status line (GuideStep.status): trimmed text, or null. */
894
+ function stepStatus(step, ctx) {
895
+ if (!step.status)
896
+ return null;
897
+ const text = safe(() => step.status(ctx), undefined);
898
+ return typeof text === "string" && text.trim() ? text.trim() : null;
899
+ }
900
+ /**
901
+ * In a replay, whether a "select" step may go on with ถัดไป: the next step
902
+ * this person sees has its target on the page already (or there is none).
903
+ * A pick that makes the next step's target (a save shown only once the
904
+ * right value is chosen) is still needed.
905
+ */
906
+ function replayCanGoOn(mission, index, env, context, nothingToSave = false) {
907
+ if (stepKind(shownStep(mission.steps[index], nothingToSave)) !== "select")
908
+ return false;
909
+ for (let i = index + 1; i < mission.steps.length; i++) {
910
+ const next = mission.steps[i];
911
+ const ctx = guideContext(env, context, routeParamsFor(next, env));
912
+ if (next.requires && !safe(() => env.can(next.requires), true))
913
+ continue;
914
+ if (next.skipIf && safe(() => next.skipIf(ctx), false))
915
+ continue;
916
+ if (!next.target)
917
+ return true;
918
+ if (stepTargetVisible(next, env, ctx, false))
919
+ return true;
920
+ // A commit step with nothing to save is read on its saved state.
921
+ return nothingToSaveHolds(next, env, ctx);
922
+ }
923
+ return true;
924
+ }
925
+ /** Keep the status line and a replay's ถัดไป current (the page changed). */
926
+ function syncStatus(d, suite, env) {
927
+ const s = d.state.screen;
928
+ if (s.name !== "step")
929
+ return;
930
+ const mission = findMission(suite, s.mission);
931
+ if (!mission)
932
+ return;
933
+ const step = shownStep(mission.steps[s.index], s.nothingToSave);
934
+ const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
935
+ const off = s.phase === "away" || s.bridge;
936
+ const status = off ? null : stepStatus(step, ctx);
937
+ const replayNext = !off && s.replay && replayCanGoOn(mission, s.index, env, d.state.progress.context, s.nothingToSave);
938
+ if (status !== s.status || replayNext !== s.replayNext)
939
+ setScreen(d, { ...s, status, replayNext });
940
+ }
857
941
  function markReadyIfVisible(d, suite, env) {
858
942
  const s = d.state.screen;
859
943
  if (s.name !== "step" || s.phase !== "waiting")
@@ -1239,6 +1323,7 @@ function reduceGuide(state, action, suite, env) {
1239
1323
  else if (!cur.bridge && needsBridge(step, env, ctx)) {
1240
1324
  setScreen(d, { ...cur, bridge: true, phase: "waiting" });
1241
1325
  }
1326
+ syncStatus(d, suite, env);
1242
1327
  markReadyIfVisible(d, suite, env);
1243
1328
  break;
1244
1329
  }
@@ -1301,6 +1386,12 @@ function reduceGuide(state, action, suite, env) {
1301
1386
  advance(d, suite, env);
1302
1387
  break;
1303
1388
  }
1389
+ if (kind === "select") {
1390
+ // A replay goes on without a pick when the next step is on the page.
1391
+ if (s.replay && replayCanGoOn(mission, s.index, env, d.state.progress.context, s.nothingToSave))
1392
+ advance(d, suite, env);
1393
+ break;
1394
+ }
1304
1395
  if (kind !== "input")
1305
1396
  break;
1306
1397
  const ctx = guideContext(env, d.state.progress.context, routeParamsFor(step, env));
@@ -2307,6 +2398,7 @@ const GUIDE_CSS = `
2307
2398
  .eg-b-t{margin:0;font-size:16px;font-weight:600;line-height:1.45}
2308
2399
  .eg-b-w{margin:2px 0 0;font-size:13.5px;color:var(--eg-muted)}
2309
2400
  .eg-bubble.eg-err .eg-b-t{color:var(--eg-danger)}
2401
+ .eg-b-s{margin:6px 0 0;font-size:13px;font-weight:600;color:var(--eg-danger)}
2310
2402
  .eg-k{display:inline-block;background:var(--eg-tint);color:var(--eg-strong);border-radius:7px;padding:0 7px;font-weight:600}
2311
2403
  .eg-b-f{display:flex;align-items:center;justify-content:space-between;margin-top:12px;gap:8px}
2312
2404
  .eg-b-f .eg-btn{height:34px;padding:0 14px;font-size:13.5px}
@@ -2540,7 +2632,7 @@ function StepLayer(p) {
2540
2632
  const showBubble = p.phase === "ready" && (!!spot || !p.hasTarget);
2541
2633
  const gap = p.showHand ? 34 : 14;
2542
2634
  const spotKey = spot ? `${spot.x}|${spot.y}|${spot.width}|${spot.height}` : "";
2543
- const contentKey = `${p.bubble.title}|${p.bubble.why}|${p.bubble.error?.title ?? ""}|${p.bubble.showNext}`;
2635
+ const contentKey = `${p.bubble.title}|${p.bubble.why}|${p.bubble.error?.title ?? ""}|${p.bubble.status ?? ""}|${p.bubble.showNext}`;
2544
2636
  // What the bubble sits beside: the target, with an open list over it (the
2545
2637
  // list is what the person is choosing from now). What it must not cover:
2546
2638
  // open lists, and the other anchors the step allows (a map step's
@@ -2663,7 +2755,7 @@ function BubbleBody(p) {
2663
2755
  const b = p.bubble;
2664
2756
  const title = b.error ? b.error.title : b.title;
2665
2757
  const why = b.error ? (b.error.detail ?? "") : b.why;
2666
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "eg-b-top", children: [jsxRuntime.jsxs("span", { className: "eg-mis", children: ["\u0E20\u0E32\u0E23\u0E01\u0E34\u0E08 ", b.missionNo, " \u00B7 ", b.missionTitle, b.replay ? " (ทบทวน)" : ""] }), jsxRuntime.jsxs("span", { className: "eg-cnt", children: ["\u0E02\u0E31\u0E49\u0E19\u0E17\u0E35\u0E48 ", b.index + 1, "/", b.total] })] }), jsxRuntime.jsx("div", { className: "eg-pips", "aria-hidden": "true", children: Array.from({ length: b.total }, (_, i) => (jsxRuntime.jsx("i", { className: i < b.index ? "eg-d" : i === b.index ? "eg-c" : undefined }, i))) }), jsxRuntime.jsx("p", { className: "eg-b-t", id: BUBBLE_TITLE_ID, children: jsxRuntime.jsx(GuideTitle, { text: title }) }), jsxRuntime.jsx("p", { className: "eg-b-w", id: BUBBLE_WHY_ID, hidden: !why, children: jsxRuntime.jsx(GuideTitle, { text: why }) }), jsxRuntime.jsxs("div", { className: "eg-b-f", children: [jsxRuntime.jsx("button", { type: "button", className: "eg-exit", onClick: p.onExit, children: b.replay ? "จบการทบทวน" : "ออกจากไกด์" }), jsxRuntime.jsxs("span", { style: { display: "flex", gap: 6 }, children: [b.showEndWithoutSave ? (jsxRuntime.jsx("button", { type: "button", className: "eg-btn eg-ghost", onClick: p.onEndWithoutSave, children: "\u0E08\u0E1A\u0E42\u0E14\u0E22\u0E44\u0E21\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01" })) : null, b.showNext ? (jsxRuntime.jsx("button", { type: "button", className: "eg-btn eg-primary", onClick: p.onNext, children: "\u0E16\u0E31\u0E14\u0E44\u0E1B" })) : null] })] })] }));
2758
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "eg-b-top", children: [jsxRuntime.jsxs("span", { className: "eg-mis", children: ["\u0E20\u0E32\u0E23\u0E01\u0E34\u0E08 ", b.missionNo, " \u00B7 ", b.missionTitle, b.replay ? " (ทบทวน)" : ""] }), jsxRuntime.jsxs("span", { className: "eg-cnt", children: ["\u0E02\u0E31\u0E49\u0E19\u0E17\u0E35\u0E48 ", b.index + 1, "/", b.total] })] }), jsxRuntime.jsx("div", { className: "eg-pips", "aria-hidden": "true", children: Array.from({ length: b.total }, (_, i) => (jsxRuntime.jsx("i", { className: i < b.index ? "eg-d" : i === b.index ? "eg-c" : undefined }, i))) }), jsxRuntime.jsx("p", { className: "eg-b-t", id: BUBBLE_TITLE_ID, children: jsxRuntime.jsx(GuideTitle, { text: title }) }), jsxRuntime.jsx("p", { className: "eg-b-w", id: BUBBLE_WHY_ID, hidden: !why, children: jsxRuntime.jsx(GuideTitle, { text: why }) }), b.status && !b.error ? (jsxRuntime.jsx("p", { className: "eg-b-s", role: "status", "data-eg-status": "", children: b.status })) : null, jsxRuntime.jsxs("div", { className: "eg-b-f", children: [jsxRuntime.jsx("button", { type: "button", className: "eg-exit", onClick: p.onExit, children: b.replay ? "จบการทบทวน" : "ออกจากไกด์" }), jsxRuntime.jsxs("span", { style: { display: "flex", gap: 6 }, children: [b.showEndWithoutSave ? (jsxRuntime.jsx("button", { type: "button", className: "eg-btn eg-ghost", onClick: p.onEndWithoutSave, children: "\u0E08\u0E1A\u0E42\u0E14\u0E22\u0E44\u0E21\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01" })) : null, b.showNext ? (jsxRuntime.jsx("button", { type: "button", className: "eg-btn eg-primary", onClick: p.onNext, children: "\u0E16\u0E31\u0E14\u0E44\u0E1B" })) : null] })] })] }));
2667
2759
  }
2668
2760
  /** The fingertip at ≈62% across and down the target; upside down near the bottom. */
2669
2761
  function handPosition(spot, viewportHeight) {
@@ -3809,12 +3901,14 @@ function GuideRuntimeInner({ config, organizationId, code, routing, progress: pr
3809
3901
  missionNo,
3810
3902
  missionTitle: mission.title,
3811
3903
  replay: screen.replay,
3812
- index: screen.index,
3813
- total: mission.steps.length,
3904
+ // Steps this person skips (requires / skipIf) are not counted.
3905
+ index: stepPosition(mission, screen.index, env(), state.progress.context).no - 1,
3906
+ total: stepPosition(mission, screen.index, env(), state.progress.context).total,
3814
3907
  title: bridge ? "กดปุ่มเมนู" : resolveText(step.title, stepCtx),
3815
3908
  why: bridge ? `เมนูทั้งหมดของ ${suite.appName} อยู่ในนี้` : resolveText(step.why, stepCtx),
3816
3909
  error: screen.error,
3817
- showNext: !bridge && (kind === "input" || kind === "view"),
3910
+ status: bridge ? null : screen.status,
3911
+ showNext: !bridge && (kind === "input" || kind === "view" || (kind === "select" && screen.replay && screen.replayNext)),
3818
3912
  showEndWithoutSave: !bridge && screen.replay && kind === "commit",
3819
3913
  }, hasTarget: bridge || !!step.target, targetPressable: targetPressable(step, bridge), phase: screen.phase, confirmExit: screen.confirmExit, reopenHint: suite.reopenHint, nudge: state.nudge, moving: moving, onExit: () => dispatch({ type: "exit" }), onNext: () => dispatch({ type: "next" }), onEndWithoutSave: () => dispatch({ type: "endWithoutSave" }), onRetry: () => dispatch({ type: "retry" }), onGoResume: () => dispatch({ type: "goResume" }), onExitConfirm: () => dispatch({ type: "exitConfirm" }), onExitCancel: () => dispatch({ type: "exitCancel" }) }));
3820
3914
  }
@@ -3876,6 +3970,7 @@ exports.apiCallDetail = apiCallDetail;
3876
3970
  exports.emitGuideEvent = emitGuideEvent;
3877
3971
  exports.fillRoute = fillRoute;
3878
3972
  exports.guideAnchor = guideAnchor;
3973
+ exports.guideMenuShown = guideMenuShown;
3879
3974
  exports.loadGuides = loadGuides;
3880
3975
  exports.matchApiPath = matchApiPath;
3881
3976
  exports.matchRoute = matchRoute;
@@ -3884,4 +3979,4 @@ exports.reportApiCall = reportApiCall;
3884
3979
  exports.reportApiEvent = reportApiEvent;
3885
3980
  exports.storeGuides = storeGuides;
3886
3981
  exports.useGuideEntry = useGuideEntry;
3887
- //# sourceMappingURL=guide-runtime-B_fYkQv7.cjs.map
3982
+ //# sourceMappingURL=guide-runtime-CLVSNkTN.cjs.map