@rytass/bpm-core-react 0.14.0 → 0.15.0

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/chunks/FormBuilderView-6bCbIkz6.cjs +3 -0
  3. package/dist/chunks/FormBuilderView-6bCbIkz6.cjs.map +1 -0
  4. package/dist/chunks/{FormBuilderView-BYLN6W-P.js → FormBuilderView-CLg-JYmu.js} +495 -452
  5. package/dist/chunks/FormBuilderView-CLg-JYmu.js.map +1 -0
  6. package/dist/chunks/FormRendererView-DU9LLrOF.js +999 -0
  7. package/dist/chunks/FormRendererView-DU9LLrOF.js.map +1 -0
  8. package/dist/chunks/FormRendererView-gctFIqwC.cjs +4 -0
  9. package/dist/chunks/FormRendererView-gctFIqwC.cjs.map +1 -0
  10. package/dist/chunks/{compose-CSWbRgJJ.js → compose-BtQPBd1Q.js} +5 -5
  11. package/dist/chunks/compose-BtQPBd1Q.js.map +1 -0
  12. package/dist/chunks/compose-DZhTQOt4.cjs +2 -0
  13. package/dist/chunks/compose-DZhTQOt4.cjs.map +1 -0
  14. package/dist/chunks/{designer-BtsIL0BB.js → designer-CrbBRPUU.js} +1285 -1197
  15. package/dist/chunks/designer-CrbBRPUU.js.map +1 -0
  16. package/dist/chunks/designer-D3_lWmfa.cjs +65 -0
  17. package/dist/chunks/designer-D3_lWmfa.cjs.map +1 -0
  18. package/dist/chunks/detail-CVXb5Vy8.cjs +2 -0
  19. package/dist/chunks/detail-CVXb5Vy8.cjs.map +1 -0
  20. package/dist/chunks/{detail-C8NOqY0p.js → detail-_c21WQla.js} +655 -630
  21. package/dist/chunks/detail-_c21WQla.js.map +1 -0
  22. package/dist/chunks/routes-config-C1j_vFKo.cjs.map +1 -1
  23. package/dist/chunks/routes-config-yZbESPlw.js.map +1 -1
  24. package/dist/chunks/task-node-label-BF6UllxP.cjs +2 -0
  25. package/dist/chunks/task-node-label-BF6UllxP.cjs.map +1 -0
  26. package/dist/chunks/task-node-label-CWZgOBj0.js +9 -0
  27. package/dist/chunks/task-node-label-CWZgOBj0.js.map +1 -0
  28. package/dist/lib/member-profile-resolution.d.ts +48 -0
  29. package/dist/lib/routes-config.d.ts +1 -1
  30. package/dist/lib/task-node-label.d.ts +10 -0
  31. package/dist/pages/instances/detail/index.cjs +1 -1
  32. package/dist/pages/instances/detail/index.js +1 -1
  33. package/dist/pages/templates/compose/index.cjs +1 -1
  34. package/dist/pages/templates/compose/index.js +1 -1
  35. package/dist/pages/templates/designer/index.cjs +1 -1
  36. package/dist/pages/templates/designer/index.js +1 -1
  37. package/dist/views/forms/builder/index.cjs +1 -1
  38. package/dist/views/forms/builder/index.js +1 -1
  39. package/dist/views/forms/renderer/form-field-link.d.ts +16 -0
  40. package/dist/views/forms/renderer/index.cjs +1 -1
  41. package/dist/views/forms/renderer/index.js +2 -2
  42. package/dist/views/forms/renderer/member-field-options.d.ts +39 -0
  43. package/dist/views/forms/renderer/member-field.d.ts +21 -0
  44. package/dist/views/inbox/index.cjs +1 -1
  45. package/dist/views/inbox/index.cjs.map +1 -1
  46. package/dist/views/inbox/index.js +100 -97
  47. package/dist/views/inbox/index.js.map +1 -1
  48. package/dist/views/instances/detail/index.cjs +1 -1
  49. package/dist/views/instances/detail/index.js +1 -1
  50. package/dist/views/instances/detail/sections/container-helpers.d.ts +3 -2
  51. package/dist/views/instances/detail/submitted-by.d.ts +23 -0
  52. package/dist/views/instances/new/index.cjs +1 -1
  53. package/dist/views/instances/new/index.js +1 -1
  54. package/dist/views/sent/SentView.d.ts +1 -1
  55. package/dist/views/sent/index.cjs +1 -1
  56. package/dist/views/sent/index.cjs.map +1 -1
  57. package/dist/views/sent/index.js +2 -2
  58. package/dist/views/sent/index.js.map +1 -1
  59. package/dist/views/templates/compose/index.cjs +1 -1
  60. package/dist/views/templates/compose/index.js +1 -1
  61. package/dist/views/templates/designer/designer-version-labels.d.ts +12 -0
  62. package/dist/views/templates/designer/dry-run-assignee-label.d.ts +28 -0
  63. package/dist/views/templates/designer/dry-run-sample-data.d.ts +9 -0
  64. package/dist/views/templates/designer/dynamic-form-resolver-options.d.ts +35 -0
  65. package/dist/views/templates/designer/index.cjs +1 -1
  66. package/dist/views/templates/designer/index.js +1 -1
  67. package/package.json +3 -3
  68. package/dist/chunks/FormBuilderView-BYLN6W-P.js.map +0 -1
  69. package/dist/chunks/FormBuilderView-VmdPVbg7.cjs +0 -3
  70. package/dist/chunks/FormBuilderView-VmdPVbg7.cjs.map +0 -1
  71. package/dist/chunks/FormRendererView-Bs8lTb4k.cjs +0 -2
  72. package/dist/chunks/FormRendererView-Bs8lTb4k.cjs.map +0 -1
  73. package/dist/chunks/FormRendererView-UPowZZgw.js +0 -798
  74. package/dist/chunks/FormRendererView-UPowZZgw.js.map +0 -1
  75. package/dist/chunks/compose-CHZoX-H0.cjs +0 -2
  76. package/dist/chunks/compose-CHZoX-H0.cjs.map +0 -1
  77. package/dist/chunks/compose-CSWbRgJJ.js.map +0 -1
  78. package/dist/chunks/designer-BtsIL0BB.js.map +0 -1
  79. package/dist/chunks/designer-Dm6cX2kZ.cjs +0 -65
  80. package/dist/chunks/designer-Dm6cX2kZ.cjs.map +0 -1
  81. package/dist/chunks/detail-C8NOqY0p.js.map +0 -1
  82. package/dist/chunks/detail-CX-3hZoL.cjs +0 -2
  83. package/dist/chunks/detail-CX-3hZoL.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"routes-config-yZbESPlw.js","names":[],"sources":["../../src/lib/routes-config.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\n/**\n * Framework-agnostic path mapping every BPM view uses for internal\n * cross-navigation. When a host embeds BPM under a non-root prefix\n * (e.g. `/workspace/bpm/*`) it provides its own implementation through\n * `<BPMRoutesProvider value={...}>`; otherwise the default factory\n * preserves the historical `/instances/:id`, `/templates`, etc. paths.\n *\n * Function-shape (instead of string templates) means:\n * - Optional and multi-param routes can branch on argument presence\n * (`caseNew(templateId?)`).\n * - Query-string composition stays inside the host's resolver, not the\n * view.\n * - TypeScript flags missing arguments at compile time.\n *\n * Mirror sibling: {@link RouterAdapter} owns *navigation primitives*\n * (`push`, `replace`, `pathname`); `BPMRoutes` owns *path strings*. The\n * two compose: views call `router.push(routes.caseDetail(id))`.\n */\nexport interface BPMRoutes {\n /** Workflow dashboard landing. Used by sidebar + dashboard tiles. */\n dashboard(): string;\n /** Inbox (待簽) — pending tasks assigned to the current member. */\n inbox(): string;\n /** Sent (我發起的) — instances the current member initiated. */\n sent(): string;\n /** CC (抄送給我) — instances the current member is copied on. */\n cc(): string;\n /** Cross-view search. */\n search(): string;\n /** Personal delegation rules. */\n delegations(): string;\n /**\n * Reserved for hosts that want a dedicated `/notifications` page —\n * BPM's built-in views do **not** navigate here by default (the\n * notification drawer mounted by `<BPMNextProviders>` is the primary\n * unread-notification UX). The `createDefaultBPMRoutes()` factory\n * returns `'/notifications'` so consumers can mount their own page\n * at that path if desired; no BPM-shipped `pages/notifications`\n * shim exists.\n */\n notifications(): string;\n\n /**\n * Detail page for one approval instance.\n *\n * @example default → `/instances/abc123`\n */\n caseDetail(instanceId: string): string;\n /**\n * Launch a new approval instance. When `templateId` is passed, the\n * launch form is pre-populated for that template — by default this is\n * appended as a `?templateId=` query string so the path itself stays\n * stable for routing.\n *\n * @example default `caseNew()` → `/instances/new`\n * @example default `caseNew('tpl-1')` → `/instances/new?templateId=tpl-1`\n */\n caseNew(templateId?: string): string;\n\n /** Template index. */\n templates(): string;\n /**\n * Unified \"form + flow\" creation wizard. Designs a form and an approval\n * flow together, then publishes both atomically. This is the only place\n * forms are designed — there is no separate standalone form builder page.\n */\n templateCompose(): string;\n /** Template designer (xyflow canvas). */\n templateDesigner(templateId: string): string;\n /** Template version history. */\n templateVersions(templateId: string): string;\n /** Template categories admin. */\n templateCategories(): string;\n\n /** Per-member notification preferences. */\n notificationSettings(): string;\n\n /** Admin: organization tree management. */\n adminOrgs(): string;\n /** Admin: member directory mapping. */\n adminUsers(): string;\n /** Admin: delegation rule management. */\n adminDelegations(): string;\n /** Admin: database-managed notify webhook endpoints (ADR 18). */\n adminWebhookEndpoints(): string;\n}\n\n/**\n * Factory for the default `BPMRoutes` value. Reproduces the exact path\n * literals BPM views used before `BPMRoutesContext` existed, so existing\n * hosts (no provider mounted) keep working unchanged.\n */\nexport function createDefaultBPMRoutes(): BPMRoutes {\n return {\n dashboard: () => '/dashboard',\n inbox: () => '/inbox',\n sent: () => '/sent',\n cc: () => '/cc',\n search: () => '/search',\n delegations: () => '/delegations',\n notifications: () => '/notifications',\n\n caseDetail: (instanceId) => `/instances/${instanceId}`,\n caseNew: (templateId) =>\n templateId\n ? `/instances/new?templateId=${encodeURIComponent(templateId)}`\n : '/instances/new',\n\n templates: () => '/templates',\n templateCompose: () => '/templates/compose',\n templateDesigner: (templateId) => `/templates/${templateId}/designer`,\n templateVersions: (templateId) => `/templates/${templateId}/versions`,\n templateCategories: () => '/templates/categories',\n\n notificationSettings: () => '/settings/notifications',\n\n adminOrgs: () => '/admin/orgs',\n adminUsers: () => '/admin/users',\n adminDelegations: () => '/admin/delegations',\n adminWebhookEndpoints: () => '/admin/webhook-endpoints',\n };\n}\n\nconst BPMRoutesContext = createContext<BPMRoutes | null>(null);\n\nexport interface BPMRoutesProviderProps {\n /**\n * Override the path mapping. Provide a full implementation, or spread\n * the default and override individual entries:\n *\n * ```tsx\n * <BPMRoutesProvider value={{\n * ...createDefaultBPMRoutes(),\n * caseDetail: (id) => `/workspace/cases/${id}`,\n * }}>\n * ```\n */\n readonly value?: BPMRoutes;\n readonly children: ReactNode;\n}\n\n/**\n * Wraps the BPM React tree with a {@link BPMRoutes} value. Mounting the\n * provider is **optional** — `useBPMRoutes()` falls back to\n * {@link createDefaultBPMRoutes} when no provider is present. Use it\n * only when the host needs to remap BPM's internal paths.\n */\nexport function BPMRoutesProvider({\n value,\n children,\n}: BPMRoutesProviderProps): ReactElement {\n const resolved = useMemo(() => value ?? createDefaultBPMRoutes(), [value]);\n return (\n <BPMRoutesContext.Provider value={resolved}>\n {children}\n </BPMRoutesContext.Provider>\n );\n}\n\n/**\n * Reads the host-configured {@link BPMRoutes}. Falls back to\n * {@link createDefaultBPMRoutes} when no `<BPMRoutesProvider>` ancestor\n * is mounted, so views remain self-contained.\n */\nexport function useBPMRoutes(): BPMRoutes {\n const value = useContext(BPMRoutesContext);\n return value ?? DEFAULT_ROUTES;\n}\n\nconst DEFAULT_ROUTES: BPMRoutes = createDefaultBPMRoutes();\n"],"mappings":";;;;AAsGA,SAAgB,IAAoC;CAClD,OAAO;EACL,iBAAiB;EACjB,aAAa;EACb,YAAY;EACZ,UAAU;EACV,cAAc;EACd,mBAAmB;EACnB,qBAAqB;EAErB,aAAa,MAAe,cAAc;EAC1C,UAAU,MACR,IACI,6BAA6B,mBAAmB,CAAU,MAC1D;EAEN,iBAAiB;EACjB,uBAAuB;EACvB,mBAAmB,MAAe,cAAc,EAAW;EAC3D,mBAAmB,MAAe,cAAc,EAAW;EAC3D,0BAA0B;EAE1B,4BAA4B;EAE5B,iBAAiB;EACjB,kBAAkB;EAClB,wBAAwB;EACxB,6BAA6B;CAC/B;AACF;AAEA,IAAM,IAAmB,EAAgC,IAAI;AAwB7D,SAAgB,EAAkB,EAChC,UACA,eACuC;CACvC,IAAM,IAAW,QAAc,KAAS,EAAuB,GAAG,CAAC,CAAK,CAAC;CACzE,OACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAC/B;CACwB,CAAA;AAE/B;AAOA,SAAgB,IAA0B;CAExC,OADc,EAAW,CAClB,KAAS;AAClB;AAEA,IAAM,IAA4B,EAAuB"}
1
+ {"version":3,"file":"routes-config-yZbESPlw.js","names":[],"sources":["../../src/lib/routes-config.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\n/**\n * Framework-agnostic path mapping every BPM view uses for internal\n * cross-navigation. When a host embeds BPM under a non-root prefix\n * (e.g. `/workspace/bpm/*`) it provides its own implementation through\n * `<BPMRoutesProvider value={...}>`; otherwise the default factory\n * preserves the historical `/instances/:id`, `/templates`, etc. paths.\n *\n * Function-shape (instead of string templates) means:\n * - Optional and multi-param routes can branch on argument presence\n * (`caseNew(templateId?)`).\n * - Query-string composition stays inside the host's resolver, not the\n * view.\n * - TypeScript flags missing arguments at compile time.\n *\n * Mirror sibling: {@link RouterAdapter} owns *navigation primitives*\n * (`push`, `replace`, `pathname`); `BPMRoutes` owns *path strings*. The\n * two compose: views call `router.push(routes.caseDetail(id))`.\n */\nexport interface BPMRoutes {\n /** Workflow dashboard landing. Used by sidebar + dashboard tiles. */\n dashboard(): string;\n /** Inbox (待簽) — pending tasks assigned to the current member. */\n inbox(): string;\n /** Sent (我發起的) — instances the current member initiated or submitted on someone's behalf. */\n sent(): string;\n /** CC (抄送給我) — instances the current member is copied on. */\n cc(): string;\n /** Cross-view search. */\n search(): string;\n /** Personal delegation rules. */\n delegations(): string;\n /**\n * Reserved for hosts that want a dedicated `/notifications` page —\n * BPM's built-in views do **not** navigate here by default (the\n * notification drawer mounted by `<BPMNextProviders>` is the primary\n * unread-notification UX). The `createDefaultBPMRoutes()` factory\n * returns `'/notifications'` so consumers can mount their own page\n * at that path if desired; no BPM-shipped `pages/notifications`\n * shim exists.\n */\n notifications(): string;\n\n /**\n * Detail page for one approval instance.\n *\n * @example default → `/instances/abc123`\n */\n caseDetail(instanceId: string): string;\n /**\n * Launch a new approval instance. When `templateId` is passed, the\n * launch form is pre-populated for that template — by default this is\n * appended as a `?templateId=` query string so the path itself stays\n * stable for routing.\n *\n * @example default `caseNew()` → `/instances/new`\n * @example default `caseNew('tpl-1')` → `/instances/new?templateId=tpl-1`\n */\n caseNew(templateId?: string): string;\n\n /** Template index. */\n templates(): string;\n /**\n * Unified \"form + flow\" creation wizard. Designs a form and an approval\n * flow together, then publishes both atomically. This is the only place\n * forms are designed — there is no separate standalone form builder page.\n */\n templateCompose(): string;\n /** Template designer (xyflow canvas). */\n templateDesigner(templateId: string): string;\n /** Template version history. */\n templateVersions(templateId: string): string;\n /** Template categories admin. */\n templateCategories(): string;\n\n /** Per-member notification preferences. */\n notificationSettings(): string;\n\n /** Admin: organization tree management. */\n adminOrgs(): string;\n /** Admin: member directory mapping. */\n adminUsers(): string;\n /** Admin: delegation rule management. */\n adminDelegations(): string;\n /** Admin: database-managed notify webhook endpoints (ADR 18). */\n adminWebhookEndpoints(): string;\n}\n\n/**\n * Factory for the default `BPMRoutes` value. Reproduces the exact path\n * literals BPM views used before `BPMRoutesContext` existed, so existing\n * hosts (no provider mounted) keep working unchanged.\n */\nexport function createDefaultBPMRoutes(): BPMRoutes {\n return {\n dashboard: () => '/dashboard',\n inbox: () => '/inbox',\n sent: () => '/sent',\n cc: () => '/cc',\n search: () => '/search',\n delegations: () => '/delegations',\n notifications: () => '/notifications',\n\n caseDetail: (instanceId) => `/instances/${instanceId}`,\n caseNew: (templateId) =>\n templateId\n ? `/instances/new?templateId=${encodeURIComponent(templateId)}`\n : '/instances/new',\n\n templates: () => '/templates',\n templateCompose: () => '/templates/compose',\n templateDesigner: (templateId) => `/templates/${templateId}/designer`,\n templateVersions: (templateId) => `/templates/${templateId}/versions`,\n templateCategories: () => '/templates/categories',\n\n notificationSettings: () => '/settings/notifications',\n\n adminOrgs: () => '/admin/orgs',\n adminUsers: () => '/admin/users',\n adminDelegations: () => '/admin/delegations',\n adminWebhookEndpoints: () => '/admin/webhook-endpoints',\n };\n}\n\nconst BPMRoutesContext = createContext<BPMRoutes | null>(null);\n\nexport interface BPMRoutesProviderProps {\n /**\n * Override the path mapping. Provide a full implementation, or spread\n * the default and override individual entries:\n *\n * ```tsx\n * <BPMRoutesProvider value={{\n * ...createDefaultBPMRoutes(),\n * caseDetail: (id) => `/workspace/cases/${id}`,\n * }}>\n * ```\n */\n readonly value?: BPMRoutes;\n readonly children: ReactNode;\n}\n\n/**\n * Wraps the BPM React tree with a {@link BPMRoutes} value. Mounting the\n * provider is **optional** — `useBPMRoutes()` falls back to\n * {@link createDefaultBPMRoutes} when no provider is present. Use it\n * only when the host needs to remap BPM's internal paths.\n */\nexport function BPMRoutesProvider({\n value,\n children,\n}: BPMRoutesProviderProps): ReactElement {\n const resolved = useMemo(() => value ?? createDefaultBPMRoutes(), [value]);\n return (\n <BPMRoutesContext.Provider value={resolved}>\n {children}\n </BPMRoutesContext.Provider>\n );\n}\n\n/**\n * Reads the host-configured {@link BPMRoutes}. Falls back to\n * {@link createDefaultBPMRoutes} when no `<BPMRoutesProvider>` ancestor\n * is mounted, so views remain self-contained.\n */\nexport function useBPMRoutes(): BPMRoutes {\n const value = useContext(BPMRoutesContext);\n return value ?? DEFAULT_ROUTES;\n}\n\nconst DEFAULT_ROUTES: BPMRoutes = createDefaultBPMRoutes();\n"],"mappings":";;;;AAsGA,SAAgB,IAAoC;CAClD,OAAO;EACL,iBAAiB;EACjB,aAAa;EACb,YAAY;EACZ,UAAU;EACV,cAAc;EACd,mBAAmB;EACnB,qBAAqB;EAErB,aAAa,MAAe,cAAc;EAC1C,UAAU,MACR,IACI,6BAA6B,mBAAmB,CAAU,MAC1D;EAEN,iBAAiB;EACjB,uBAAuB;EACvB,mBAAmB,MAAe,cAAc,EAAW;EAC3D,mBAAmB,MAAe,cAAc,EAAW;EAC3D,0BAA0B;EAE1B,4BAA4B;EAE5B,iBAAiB;EACjB,kBAAkB;EAClB,wBAAwB;EACxB,6BAA6B;CAC/B;AACF;AAEA,IAAM,IAAmB,EAAgC,IAAI;AAwB7D,SAAgB,EAAkB,EAChC,UACA,eACuC;CACvC,IAAM,IAAW,QAAc,KAAS,EAAuB,GAAG,CAAC,CAAK,CAAC;CACzE,OACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAC/B;CACwB,CAAA;AAE/B;AAOA,SAAgB,IAA0B;CAExC,OADc,EAAW,CAClB,KAAS;AAClB;AAEA,IAAM,IAA4B,EAAuB"}
@@ -0,0 +1,2 @@
1
+ function e(e){let t=e.nodeLabel?.trim()?e.nodeLabel:e.nodeId;return e.isAdhoc?`${t}${e.adhocType===`COUNTERSIGN`?`(臨時會簽)`:`(臨時加簽)`}`:t}Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return e}});
2
+ //# sourceMappingURL=task-node-label-BF6UllxP.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-node-label-BF6UllxP.cjs","names":[],"sources":["../../src/lib/task-node-label.ts"],"sourcesContent":["import type { TaskRecord } from '@rytass/bpm-core-client/workflow';\n\n/**\n * What a task list shows in its 「節點」 column: the node's display name\n * (`nodeLabel`, resolved server-side from the instance's workflow snapshot),\n * falling back to the raw `nodeId` when the server sent none — an unknown node\n * or a host running its own queries. An ad-hoc task keeps the label of the\n * node it was spawned on and gets the same 臨時會簽/臨時加簽 marker the\n * instance timeline uses.\n */\nexport function readTaskNodeText(\n task: Pick<TaskRecord, 'adhocType' | 'isAdhoc' | 'nodeId' | 'nodeLabel'>,\n): string {\n const label = task.nodeLabel?.trim() ? task.nodeLabel : task.nodeId;\n\n if (!task.isAdhoc) {\n return label;\n }\n\n return `${label}${\n task.adhocType === 'COUNTERSIGN' ? '(臨時會簽)' : '(臨時加簽)'\n }`;\n}\n"],"mappings":"AAUA,SAAgB,EACd,EACQ,CACR,IAAM,EAAQ,EAAK,WAAW,KAAK,EAAI,EAAK,UAAY,EAAK,OAM7D,OAJK,EAAK,QAIH,GAAG,IACR,EAAK,YAAc,cAAgB,SAAW,WAJvC,CAMX"}
@@ -0,0 +1,9 @@
1
+ //#region src/lib/task-node-label.ts
2
+ function e(e) {
3
+ let t = e.nodeLabel?.trim() ? e.nodeLabel : e.nodeId;
4
+ return e.isAdhoc ? `${t}${e.adhocType === "COUNTERSIGN" ? "(臨時會簽)" : "(臨時加簽)"}` : t;
5
+ }
6
+ //#endregion
7
+ export { e as t };
8
+
9
+ //# sourceMappingURL=task-node-label-CWZgOBj0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-node-label-CWZgOBj0.js","names":[],"sources":["../../src/lib/task-node-label.ts"],"sourcesContent":["import type { TaskRecord } from '@rytass/bpm-core-client/workflow';\n\n/**\n * What a task list shows in its 「節點」 column: the node's display name\n * (`nodeLabel`, resolved server-side from the instance's workflow snapshot),\n * falling back to the raw `nodeId` when the server sent none — an unknown node\n * or a host running its own queries. An ad-hoc task keeps the label of the\n * node it was spawned on and gets the same 臨時會簽/臨時加簽 marker the\n * instance timeline uses.\n */\nexport function readTaskNodeText(\n task: Pick<TaskRecord, 'adhocType' | 'isAdhoc' | 'nodeId' | 'nodeLabel'>,\n): string {\n const label = task.nodeLabel?.trim() ? task.nodeLabel : task.nodeId;\n\n if (!task.isAdhoc) {\n return label;\n }\n\n return `${label}${\n task.adhocType === 'COUNTERSIGN' ? '(臨時會簽)' : '(臨時加簽)'\n }`;\n}\n"],"mappings":";AAUA,SAAgB,EACd,GACQ;CACR,IAAM,IAAQ,EAAK,WAAW,KAAK,IAAI,EAAK,YAAY,EAAK;CAM7D,OAJK,EAAK,UAIH,GAAG,IACR,EAAK,cAAc,gBAAgB,WAAW,aAJvC;AAMX"}
@@ -0,0 +1,48 @@
1
+ import { MemberProfileRecord } from '@rytass/bpm-core-client/template';
2
+ /** Most ids asked for on their own after a failed batch, per round. */
3
+ export declare const MAX_PER_ID_REQUESTS = 20;
4
+ /** Most per-id requests in flight at once. */
5
+ export declare const PER_ID_CONCURRENCY = 4;
6
+ export type MemberProfileResolution = Readonly<{
7
+ /** Ids whose lookup failed even when asked for on their own. */
8
+ failedIds: readonly string[];
9
+ profiles: readonly MemberProfileRecord[];
10
+ /**
11
+ * Ids left unasked because a failed batch held more than the per-id budget.
12
+ * They were not tried, so a later round may ask for them again.
13
+ */
14
+ skippedIds: readonly string[];
15
+ }>;
16
+ export type MemberProfileResolutionLimits = Readonly<{
17
+ concurrency?: number;
18
+ maxPerIdRequests?: number;
19
+ }>;
20
+ /**
21
+ * Looks up member profiles in one batch, and degrades per id when the batch
22
+ * request fails.
23
+ *
24
+ * The host fails a whole lookup when it contains an id it cannot handle (a
25
+ * foreign id stored in a template, say). Treating that as "nobody resolved"
26
+ * labels every approver of the template 「未知會員」 for the sake of one bad
27
+ * id, so a failed batch is retried one id at a time and only the ids that fail
28
+ * on their own are reported back in `failedIds`.
29
+ *
30
+ * The retry is bounded: at most `maxPerIdRequests` ids are asked for on their
31
+ * own (the rest come back in `skippedIds` without a request), and at most
32
+ * `concurrency` of those requests run at once, so one bad id in a large
33
+ * template cannot turn into a burst of requests.
34
+ *
35
+ * A single-id batch is not retried: it already is the per-id request.
36
+ */
37
+ export declare function resolveMemberProfilesPerIdOnFailure(memberIds: readonly string[], resolveMembers: (memberIds: readonly string[]) => Promise<readonly MemberProfileRecord[]>, limits?: MemberProfileResolutionLimits): Promise<MemberProfileResolution>;
38
+ /**
39
+ * The ids still worth asking the directory for: those that already failed on
40
+ * their own in this session are left out, so they stay 「未知會員」 instead of
41
+ * being re-requested every time the option list changes.
42
+ *
43
+ * A failed id stays 「未知會員」 for the rest of the session, even when the
44
+ * failure was transient (a network blip, say), unless a later member search
45
+ * returns it and replaces the placeholder. Ids skipped over the per-id cap
46
+ * were never asked and must not be added to `failedMemberIds`.
47
+ */
48
+ export declare function excludeFailedMemberIds(memberIds: readonly string[], failedMemberIds: ReadonlySet<string>): readonly string[];
@@ -22,7 +22,7 @@ export interface BPMRoutes {
22
22
  dashboard(): string;
23
23
  /** Inbox (待簽) — pending tasks assigned to the current member. */
24
24
  inbox(): string;
25
- /** Sent (我發起的) — instances the current member initiated. */
25
+ /** Sent (我發起的) — instances the current member initiated or submitted on someone's behalf. */
26
26
  sent(): string;
27
27
  /** CC (抄送給我) — instances the current member is copied on. */
28
28
  cc(): string;
@@ -0,0 +1,10 @@
1
+ import { TaskRecord } from '@rytass/bpm-core-client/workflow';
2
+ /**
3
+ * What a task list shows in its 「節點」 column: the node's display name
4
+ * (`nodeLabel`, resolved server-side from the instance's workflow snapshot),
5
+ * falling back to the raw `nodeId` when the server sent none — an unknown node
6
+ * or a host running its own queries. An ad-hoc task keeps the label of the
7
+ * node it was spawned on and gets the same 臨時會簽/臨時加簽 marker the
8
+ * instance timeline uses.
9
+ */
10
+ export declare function readTaskNodeText(task: Pick<TaskRecord, 'adhocType' | 'isAdhoc' | 'nodeId' | 'nodeLabel'>): string;
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/detail-CX-3hZoL.cjs");let t=require("react/jsx-runtime");var n={title:`案件詳情 | BPM Admin`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{instanceId:r})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/detail-CVXb5Vy8.cjs");let t=require("react/jsx-runtime");var n={title:`案件詳情 | BPM Admin`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{instanceId:r})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { t as e } from "../../../chunks/detail-C8NOqY0p.js";
1
+ import { t as e } from "../../../chunks/detail-_c21WQla.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/pages/instances/detail/index.tsx
4
4
  var n = { title: "案件詳情 | BPM Admin" };
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/compose-CHZoX-H0.cjs");let t=require("react/jsx-runtime"),n=require("next/server");var r={title:`建立模板(表單 + 流程) | BPM Admin`,description:`以精靈式流程一次完成 BPM 表單與簽核流程設計並發佈。`};async function i(){return await(0,n.connection)(),(0,t.jsx)(e.t,{aiAssistantAvailable:!!process.env.OPENAI_API_KEY,showAiAssistant:process.env.BPM_AI_ASSISTANT_ENABLED===`true`})}exports.default=i,exports.metadata=r;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/compose-DZhTQOt4.cjs");let t=require("react/jsx-runtime"),n=require("next/server");var r={title:`建立模板(表單 + 流程) | BPM Admin`,description:`以精靈式流程一次完成 BPM 表單與簽核流程設計並發佈。`};async function i(){return await(0,n.connection)(),(0,t.jsx)(e.t,{aiAssistantAvailable:!!process.env.OPENAI_API_KEY,showAiAssistant:process.env.BPM_AI_ASSISTANT_ENABLED===`true`})}exports.default=i,exports.metadata=r;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { t as e } from "../../../chunks/compose-CSWbRgJJ.js";
1
+ import { t as e } from "../../../chunks/compose-BtQPBd1Q.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  import { connection as n } from "next/server";
4
4
  //#region src/pages/templates/compose/index.tsx
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/designer-Dm6cX2kZ.cjs");let t=require("react/jsx-runtime");var n={title:`流程設計器 | BPM Admin`,description:`以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{aiAssistantAvailable:!!process.env.OPENAI_API_KEY,showAiAssistant:process.env.BPM_AI_ASSISTANT_ENABLED===`true`,showDryRun:process.env.BPM_TEMPLATE_DRY_RUN_ENABLED!==`false`,templateId:r})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/designer-D3_lWmfa.cjs");let t=require("react/jsx-runtime");var n={title:`流程設計器 | BPM Admin`,description:`以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{aiAssistantAvailable:!!process.env.OPENAI_API_KEY,showAiAssistant:process.env.BPM_AI_ASSISTANT_ENABLED===`true`,showDryRun:process.env.BPM_TEMPLATE_DRY_RUN_ENABLED!==`false`,templateId:r})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { t as e } from "../../../chunks/designer-BtsIL0BB.js";
1
+ import { t as e } from "../../../chunks/designer-CrbBRPUU.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/pages/templates/designer/index.tsx
4
4
  var n = {
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../chunks/FormBuilderView-VmdPVbg7.cjs");exports.FormBuilderView=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../chunks/FormBuilderView-6bCbIkz6.cjs");exports.FormBuilderView=e.t;
@@ -1,2 +1,2 @@
1
- import { t as e } from "../../../chunks/FormBuilderView-BYLN6W-P.js";
1
+ import { t as e } from "../../../chunks/FormBuilderView-CLg-JYmu.js";
2
2
  export { e as FormBuilderView };
@@ -0,0 +1,16 @@
1
+ export interface FormFieldLink {
2
+ /** Absolute links open in a new tab; site-relative ones stay in this tab. */
3
+ readonly external: boolean;
4
+ readonly href: string;
5
+ }
6
+ /**
7
+ * Decides whether a `displayAs: 'link'` text value may become an anchor.
8
+ * Returns `null` for everything that must stay plain text.
9
+ *
10
+ * Allowed: a site-relative path (`/` first, never `//`) or an absolute
11
+ * `http(s)://` URL with a host and no embedded credentials. Everything else —
12
+ * `javascript:`, `data:`, protocol-relative `//host`, backslash tricks
13
+ * (`/\host` is `//host` to a browser), padded or multi-line values, non-strings
14
+ * — is refused rather than repaired.
15
+ */
16
+ export declare function resolveFormFieldLink(value: unknown): FormFieldLink | null;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../chunks/FormRendererView-Bs8lTb4k.cjs");exports.FormRenderer=e.t,exports.FormRendererView=e.t,exports.isFormDataSourceFieldSubmissionBlocked=e.n,exports.readFormDataSourceFieldStatusMessage=e.r,exports.readFormDataSourceSubmissionBlockMessage=e.i,exports.useFormDataSourceField=e.a;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../chunks/FormRendererView-gctFIqwC.cjs");exports.FormRenderer=e.t,exports.FormRendererView=e.t,exports.isFormDataSourceFieldSubmissionBlocked=e.i,exports.readFormDataSourceFieldStatusMessage=e.a,exports.readFormDataSourceSubmissionBlockMessage=e.o,exports.useFormDataSourceField=e.s;
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "../../../chunks/FormRendererView-UPowZZgw.js";
2
- export { i as FormRenderer, i as FormRendererView, n as isFormDataSourceFieldSubmissionBlocked, r as readFormDataSourceFieldStatusMessage, t as readFormDataSourceSubmissionBlockMessage, e as useFormDataSourceField };
1
+ import { a as e, i as t, o as n, s as r, t as i } from "../../../chunks/FormRendererView-DU9LLrOF.js";
2
+ export { i as FormRenderer, i as FormRendererView, t as isFormDataSourceFieldSubmissionBlocked, e as readFormDataSourceFieldStatusMessage, n as readFormDataSourceSubmissionBlockMessage, r as useFormDataSourceField };
@@ -0,0 +1,39 @@
1
+ import { FormFieldValue } from '@rytass/bpm-core-shared/form';
2
+ /**
3
+ * One pickable member, shaped for Mezzanine `AutoComplete` (`id` + `name`).
4
+ * `unresolved` marks the placeholder written when a stored id has no profile,
5
+ * so a real profile arriving later replaces it instead of the placeholder
6
+ * sticking (the designer does the same for its approver list).
7
+ */
8
+ export type MemberFieldOption = Readonly<{
9
+ id: string;
10
+ name: string;
11
+ unresolved?: boolean;
12
+ }>;
13
+ export declare const UNKNOWN_MEMBER_NAME = "\u672A\u77E5\u6703\u54E1";
14
+ export declare function readMemberFieldOption(member: {
15
+ readonly email: string;
16
+ readonly memberId: string;
17
+ readonly name: string;
18
+ }): MemberFieldOption;
19
+ export declare function readUnresolvedMemberFieldOption(memberId: string): MemberFieldOption;
20
+ /**
21
+ * Adds `nextOptions` to `currentOptions`. A resolved option replaces an
22
+ * unresolved placeholder with the same id; otherwise the first one seen wins.
23
+ * Returns `currentOptions` itself when nothing changed, so state setters can
24
+ * bail out of a re-render.
25
+ */
26
+ export declare function mergeMemberFieldOptions(currentOptions: readonly MemberFieldOption[], nextOptions: readonly MemberFieldOption[]): readonly MemberFieldOption[];
27
+ /** Ids in the stored value that no known option covers yet. */
28
+ export declare function readUnresolvedMemberIds(value: FormFieldValue | undefined, knownOptions: readonly MemberFieldOption[]): readonly string[];
29
+ /**
30
+ * The stored value as options, in stored order. An id with no known profile
31
+ * still shows up (as 「未知會員」) so the filler can see and remove it.
32
+ */
33
+ export declare function readSelectedMemberFieldOptions(value: FormFieldValue | undefined, knownOptions: readonly MemberFieldOption[]): readonly MemberFieldOption[];
34
+ /**
35
+ * What a picker change writes back. Nothing picked is `undefined`, the same as
36
+ * every other optional field, so the renderer's own initial value (`[]` for a
37
+ * multiple member field) is restored by `buildFormRendererValues`.
38
+ */
39
+ export declare function readMemberFieldValue(selected: readonly MemberFieldOption[], multiple: boolean): FormFieldValue | undefined;
@@ -0,0 +1,21 @@
1
+ import { ReactElement } from 'react';
2
+ import { FormFieldValue } from '@rytass/bpm-core-shared/form';
3
+ export interface MemberFieldControlProps {
4
+ readonly multiple: boolean;
5
+ readonly name: string;
6
+ readonly onChange: (value: FormFieldValue | undefined) => void;
7
+ readonly placeholder?: string;
8
+ readonly readonly: boolean;
9
+ readonly size?: 'main' | 'sub';
10
+ readonly value: FormFieldValue | undefined;
11
+ }
12
+ /**
13
+ * 人員選擇 picker. Searching and name lookup reuse the directory queries the
14
+ * admin member picker and the workflow designer already call
15
+ * (`searchMembers` / `resolveMembers`); nothing new is added server side.
16
+ *
17
+ * Stored values are member ids. The names shown here are resolved on mount and
18
+ * whenever a stored id has no profile yet, so a read-only view of a submitted
19
+ * form (instance detail) shows names, not ids.
20
+ */
21
+ export declare function MemberFieldControl({ multiple, name, onChange, placeholder, readonly, size, value, }: MemberFieldControlProps): ReactElement;
@@ -1,2 +1,2 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/rolldown-runtime-BocRIvOZ.cjs"),t=require("../../chunks/router-adapter--gYs13E8.cjs"),n=require("../../chunks/auth-provider-4BeCw7cI.cjs"),r=require("../../chunks/format-date-time-D-cx5M8z.cjs"),i=require("../../chunks/routes-config-C1j_vFKo.cjs");let a=require("react"),o=require("@mezzanine-ui/react"),s=require("react/jsx-runtime"),c=require("@rytass/bpm-core-client/workflow"),l=require("@mezzanine-ui/react/ContentHeader");l=e.t(l,1);let u=require("@mezzanine-ui/icons");function d(){let e=t.r(),d=i.r(),{member:m}=n.n(),h=m?.memberId??null,[g,_]=(0,a.useState)(`pending`),[v,b]=(0,a.useState)(null),[S,w]=(0,a.useState)([]),[T,E]=(0,a.useState)(!0),[D,k]=(0,a.useState)([]);(0,a.useEffect)(()=>{h&&I()},[h]);let A=(0,a.useMemo)(()=>[{dataIndex:`caseTitle`,key:`caseTitle`,title:`案件`,width:280},{dataIndex:`nodeId`,key:`nodeId`,title:`節點`,width:180},{dataIndex:`statusLabel`,key:`statusLabel`,title:`狀態`,width:120},{key:`slaDueAt`,render:e=>(0,s.jsx)(o.Typography,{color:e.slaStatusColor,component:`span`,variant:`body`,children:e.slaStatusText}),title:`SLA`,width:180},{key:`createdAt`,render:e=>(0,s.jsx)(o.Typography,{component:`span`,variant:`body`,children:r.t(e.createdAt)}),title:`建立時間`,width:220}],[]),j=(0,a.useMemo)(()=>({render:t=>[{name:`處理`,onClick:()=>e.push(d.caseDetail(t.instanceId))}],variant:`base-secondary`,width:88}),[e,d]),M=(0,a.useMemo)(()=>[{dataIndex:`caseTitle`,key:`caseTitle`,title:`案件`,width:280},{dataIndex:`nodeId`,key:`nodeId`,title:`節點`,width:180},{key:`decisionLabel`,render:e=>(0,s.jsx)(o.Typography,{color:y(e.decisionAction),component:`span`,variant:`body`,children:e.decisionLabel}),title:`決議`,width:120},{key:`decisionComment`,render:e=>(0,s.jsx)(o.Typography,{component:`span`,variant:`body`,children:e.decisionCommentText}),title:`意見`,width:240},{key:`instanceStateLabel`,render:e=>(0,s.jsx)(o.Typography,{color:x(e.instanceState),component:`span`,variant:`body`,children:e.instanceStateLabel}),title:`流程狀態`,width:140},{key:`decidedAt`,render:e=>(0,s.jsx)(o.Typography,{component:`span`,variant:`body`,children:C(e.decidedAt)}),title:`簽核時間`,width:220}],[]),N=(0,a.useMemo)(()=>({render:t=>[{name:`查看`,onClick:()=>e.push(d.caseDetail(t.instanceId))}],variant:`base-secondary`,width:88}),[e,d]),P=(0,a.useMemo)(()=>S.filter(e=>e.instanceState===`RUNNING`),[S]);function F(e){if(e===`history`||e===`pending`||e===`tracking`){_(e);return}_(`pending`)}async function I(){E(!0),b(null);try{if(!h){k([]),w([]);return}let[e,t]=await Promise.all([(0,c.listInboxTasks)(h),(0,c.listApprovalHistoryTasks)(h)]),[n,r]=await Promise.all([f(e),p(t)]);k(n),w(r)}catch(e){b(O(e))}finally{E(!1)}}return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.PageHeader,{children:(0,s.jsx)(l.default,{description:`目前以 ${m?.name??h??`目前登入會員`} 查詢待處理與歷史簽核任務。`,title:`我的待簽`,children:(0,s.jsx)(o.Button,{icon:u.PlusIcon,iconType:`leading`,onClick:()=>e.push(d.caseNew()),variant:`base-primary`,children:`發起簽核`})})}),(0,s.jsx)(o.SectionGroup,{children:(0,s.jsxs)(o.Section,{children:[v?(0,s.jsx)(o.Typography,{color:`text-error`,variant:`body`,children:v}):null,(0,s.jsxs)(o.Tab,{activeKey:g,onChange:F,size:`sub`,children:[(0,s.jsx)(o.TabItem,{badgeCount:T?void 0:D.length,children:`待簽核`},`pending`),(0,s.jsx)(o.TabItem,{badgeCount:T?void 0:P.length,children:`已處理未結束`},`tracking`),(0,s.jsx)(o.TabItem,{children:`歷史簽核記錄`},`history`)]}),g===`pending`?(0,s.jsx)(o.Table,{actions:j,columns:A,dataSource:D,fullWidth:!0,loading:T}):null,g===`tracking`?(0,s.jsx)(o.Table,{actions:N,columns:M,dataSource:P,fullWidth:!0,loading:T}):null,g===`history`?(0,s.jsx)(o.Table,{actions:N,columns:M,dataSource:S,fullWidth:!0,loading:T}):null]})})]})}async function f(e){let t=await m(e);return e.map((e,n)=>({...e,caseTitle:h(e,t[n]??null),key:e.id,slaStatusColor:T(e.slaDueAt),slaStatusText:w(e.slaDueAt),statusLabel:D(e.status)}))}async function p(e){let[t,n]=await Promise.all([Promise.all(e.map(e=>(0,c.listTaskDecisions)(e.id))),m(e)]);return e.map((e,r)=>{let i=g(t[r]??[]),a=n[r]??null;return{...e,caseTitle:h(e,a),decisionAction:i?.action??null,decisionComment:i?.comment??null,decisionCommentText:S(i?.comment??null),decisionLabel:v(i?.action??null),decidedAt:i?.decidedAt??e.completedAt,instanceState:a?.state??null,instanceStateLabel:b(a?.state??null),key:e.id}})}async function m(e){return Promise.all(e.map(async e=>{try{return(await(0,c.readApprovalInstance)(e.instanceId)).instance}catch{return null}}))}function h(e,t){return t?(0,c.readApprovalInstanceCaseTitle)(t):e.instanceId}function g(e){return[...e].sort(_)[0]??null}function _(e,t){return new Date(t.decidedAt).getTime()-new Date(e.decidedAt).getTime()}function v(e){return e===`APPROVED`?`同意`:e===`REJECTED`?`拒絕`:e===`RETURNED`?`退回`:e===`TRANSFERRED`?`轉派`:`未記錄`}function y(e){return e===`APPROVED`?`text-success`:e===`REJECTED`||e===`RETURNED`?`text-error`:`text-neutral`}function b(e){return e===`RUNNING`?`進行中`:e===`APPROVED`?`已通過`:e===`REJECTED`?`已拒絕`:e===`RETURNED`?`已退回`:e===`CANCELLED`?`已取消`:e===`EXPIRED`?`已逾期`:e===`DRAFT`?`草稿`:`-`}function x(e){return e===`APPROVED`?`text-success`:e===`REJECTED`||e===`CANCELLED`||e===`EXPIRED`?`text-error`:`text-neutral`}function S(e){return(e?.trim()??``)||`-`}function C(e){return r.t(e)}function w(e){if(!e)return`-`;let t=new Date(e).getTime(),n=Date.now(),r=E(Math.abs(t-n));return t<n?`已逾期 ${r}`:`剩餘 ${r}`}function T(e){return e?new Date(e).getTime()<Date.now()?`text-error`:`text-success`:`text-neutral`}function E(e){let t=Math.max(1,Math.ceil(e/6e4)),n=Math.floor(t/1440),r=Math.floor(t%1440/60),i=t%60;return n>0?`${n}天 ${r}小時`:r>0?`${r}小時 ${i}分鐘`:`${i}分鐘`}function D(e){return e===`PENDING`?`待處理`:e===`IN_PROGRESS`?`處理中`:e===`COMPLETED`?`已完成`:e===`TRANSFERRED`?`已轉派`:e===`CANCELLED`?`已取消`:e}function O(e){return e instanceof Error?e.message:`發生未知錯誤`}exports.InboxView=d;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/rolldown-runtime-BocRIvOZ.cjs"),t=require("../../chunks/router-adapter--gYs13E8.cjs"),n=require("../../chunks/auth-provider-4BeCw7cI.cjs"),r=require("../../chunks/format-date-time-D-cx5M8z.cjs"),i=require("../../chunks/routes-config-C1j_vFKo.cjs"),a=require("../../chunks/task-node-label-BF6UllxP.cjs");let o=require("react"),s=require("@mezzanine-ui/react"),c=require("react/jsx-runtime"),l=require("@rytass/bpm-core-client/workflow"),u=require("@mezzanine-ui/react/ContentHeader");u=e.t(u,1);let d=require("@mezzanine-ui/icons");function f(){let e=t.r(),a=i.r(),{member:f}=n.n(),h=f?.memberId??null,[g,_]=(0,o.useState)(`pending`),[v,y]=(0,o.useState)(null),[x,C]=(0,o.useState)([]),[T,E]=(0,o.useState)(!0),[D,O]=(0,o.useState)([]);(0,o.useEffect)(()=>{h&&I()},[h]);let A=(0,o.useMemo)(()=>[{dataIndex:`caseTitle`,key:`caseTitle`,title:`案件`,width:280},{dataIndex:`nodeText`,key:`nodeText`,title:`節點`,width:180},{dataIndex:`statusLabel`,key:`statusLabel`,title:`狀態`,width:120},{key:`slaDueAt`,render:e=>(0,c.jsx)(s.Typography,{color:e.slaStatusColor,component:`span`,variant:`body`,children:e.slaStatusText}),title:`SLA`,width:180},{key:`createdAt`,render:e=>(0,c.jsx)(s.Typography,{component:`span`,variant:`body`,children:r.t(e.createdAt)}),title:`建立時間`,width:220}],[]),j=(0,o.useMemo)(()=>({render:t=>[{name:`處理`,onClick:()=>e.push(a.caseDetail(t.instanceId))}],variant:`base-secondary`,width:88}),[e,a]),M=(0,o.useMemo)(()=>[{dataIndex:`caseTitle`,key:`caseTitle`,title:`案件`,width:280},{dataIndex:`nodeText`,key:`nodeText`,title:`節點`,width:180},{key:`decisionLabel`,render:e=>(0,c.jsx)(s.Typography,{color:b(e.decisionAction),component:`span`,variant:`body`,children:e.decisionLabel}),title:`決議`,width:120},{key:`decisionComment`,render:e=>(0,c.jsx)(s.Typography,{component:`span`,variant:`body`,children:e.decisionCommentText}),title:`意見`,width:240},{key:`instanceStateLabel`,render:e=>(0,c.jsx)(s.Typography,{color:S(e.instanceState),component:`span`,variant:`body`,children:e.instanceStateLabel}),title:`流程狀態`,width:140},{key:`decidedAt`,render:e=>(0,c.jsx)(s.Typography,{component:`span`,variant:`body`,children:w(e.decidedAt)}),title:`簽核時間`,width:220}],[]),N=(0,o.useMemo)(()=>({render:t=>[{name:`查看`,onClick:()=>e.push(a.caseDetail(t.instanceId))}],variant:`base-secondary`,width:88}),[e,a]),P=(0,o.useMemo)(()=>x.filter(e=>e.instanceState===`RUNNING`),[x]);function F(e){if(e===`history`||e===`pending`||e===`tracking`){_(e);return}_(`pending`)}async function I(){E(!0),y(null);try{if(!h){O([]),C([]);return}let[e,t]=await Promise.all([(0,l.listInboxTasks)(h),(0,l.listApprovalHistoryTasks)(h)]),[n,r]=await Promise.all([p(e),m(t)]);O(n),C(r)}catch(e){y(k(e))}finally{E(!1)}}return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(s.PageHeader,{children:(0,c.jsx)(u.default,{description:`目前以 ${f?.name??h??`目前登入會員`} 查詢待處理與歷史簽核任務。`,title:`我的待簽`,children:(0,c.jsx)(s.Button,{icon:d.PlusIcon,iconType:`leading`,onClick:()=>e.push(a.caseNew()),variant:`base-primary`,children:`發起簽核`})})}),(0,c.jsx)(s.SectionGroup,{children:(0,c.jsxs)(s.Section,{children:[v?(0,c.jsx)(s.Typography,{color:`text-error`,variant:`body`,children:v}):null,(0,c.jsxs)(s.Tab,{activeKey:g,onChange:F,size:`sub`,children:[(0,c.jsx)(s.TabItem,{badgeCount:T?void 0:D.length,children:`待簽核`},`pending`),(0,c.jsx)(s.TabItem,{badgeCount:T?void 0:P.length,children:`已處理未結束`},`tracking`),(0,c.jsx)(s.TabItem,{children:`歷史簽核記錄`},`history`)]}),g===`pending`?(0,c.jsx)(s.Table,{actions:j,columns:A,dataSource:D,fullWidth:!0,loading:T}):null,g===`tracking`?(0,c.jsx)(s.Table,{actions:N,columns:M,dataSource:P,fullWidth:!0,loading:T}):null,g===`history`?(0,c.jsx)(s.Table,{actions:N,columns:M,dataSource:x,fullWidth:!0,loading:T}):null]})})]})}async function p(e){let t=await h(e);return e.map((e,n)=>({...e,caseTitle:g(e,t[n]??null),key:e.id,nodeText:a.t(e),slaStatusColor:E(e.slaDueAt),slaStatusText:T(e.slaDueAt),statusLabel:O(e.status)}))}async function m(e){let[t,n]=await Promise.all([Promise.all(e.map(e=>(0,l.listTaskDecisions)(e.id))),h(e)]);return e.map((e,r)=>{let i=_(t[r]??[]),o=n[r]??null;return{...e,caseTitle:g(e,o),decisionAction:i?.action??null,decisionComment:i?.comment??null,decisionCommentText:C(i?.comment??null),decisionLabel:y(i?.action??null),decidedAt:i?.decidedAt??e.completedAt,instanceState:o?.state??null,instanceStateLabel:x(o?.state??null),key:e.id,nodeText:a.t(e)}})}async function h(e){return Promise.all(e.map(async e=>{try{return(await(0,l.readApprovalInstance)(e.instanceId)).instance}catch{return null}}))}function g(e,t){return t?(0,l.readApprovalInstanceCaseTitle)(t):e.instanceId}function _(e){return[...e].sort(v)[0]??null}function v(e,t){return new Date(t.decidedAt).getTime()-new Date(e.decidedAt).getTime()}function y(e){return e===`APPROVED`?`同意`:e===`REJECTED`?`拒絕`:e===`RETURNED`?`退回`:e===`TRANSFERRED`?`轉派`:`未記錄`}function b(e){return e===`APPROVED`?`text-success`:e===`REJECTED`||e===`RETURNED`?`text-error`:`text-neutral`}function x(e){return e===`RUNNING`?`進行中`:e===`APPROVED`?`已通過`:e===`REJECTED`?`已拒絕`:e===`RETURNED`?`已退回`:e===`CANCELLED`?`已取消`:e===`EXPIRED`?`已逾期`:e===`DRAFT`?`草稿`:`-`}function S(e){return e===`APPROVED`?`text-success`:e===`REJECTED`||e===`CANCELLED`||e===`EXPIRED`?`text-error`:`text-neutral`}function C(e){return(e?.trim()??``)||`-`}function w(e){return r.t(e)}function T(e){if(!e)return`-`;let t=new Date(e).getTime(),n=Date.now(),r=D(Math.abs(t-n));return t<n?`已逾期 ${r}`:`剩餘 ${r}`}function E(e){return e?new Date(e).getTime()<Date.now()?`text-error`:`text-success`:`text-neutral`}function D(e){let t=Math.max(1,Math.ceil(e/6e4)),n=Math.floor(t/1440),r=Math.floor(t%1440/60),i=t%60;return n>0?`${n}天 ${r}小時`:r>0?`${r}小時 ${i}分鐘`:`${i}分鐘`}function O(e){return e===`PENDING`?`待處理`:e===`IN_PROGRESS`?`處理中`:e===`COMPLETED`?`已完成`:e===`TRANSFERRED`?`已轉派`:e===`CANCELLED`?`已取消`:e}function k(e){return e instanceof Error?e.message:`發生未知錯誤`}exports.InboxView=f;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/views/inbox/InboxView.tsx"],"sourcesContent":["'use client';\n\nimport { type Key, ReactElement, useEffect, useMemo, useState } from 'react';\nimport {\n Button,\n PageHeader,\n Section,\n SectionGroup,\n Tab,\n TabItem,\n Table,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { PlusIcon } from '@mezzanine-ui/icons';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport {\n ApprovalInstanceRecord,\n listApprovalHistoryTasks,\n listInboxTasks,\n listTaskDecisions,\n readApprovalInstance,\n readApprovalInstanceCaseTitle,\n TaskDecisionRecord,\n TaskRecord,\n} from '@rytass/bpm-core-client/workflow';\nimport { formatDateTime } from '../../lib/format-date-time';\nimport { useAuth } from '../../lib/auth-provider';\nimport { useRouterAdapter } from '../../lib/router-adapter';\nimport { useBPMRoutes } from '../../lib/routes-config';\n\ntype InboxTabKey = 'history' | 'pending' | 'tracking';\n\ntype InboxTaskRow = Readonly<\n Record<string, unknown> &\n TaskRecord & {\n caseTitle: string;\n key: string;\n slaStatusColor: 'text-error' | 'text-neutral' | 'text-success';\n slaStatusText: string;\n statusLabel: string;\n }\n>;\n\ntype ApprovalHistoryRow = Readonly<\n Record<string, unknown> &\n TaskRecord & {\n caseTitle: string;\n decisionAction: TaskDecisionRecord['action'] | null;\n decisionComment: string | null;\n decisionCommentText: string;\n decisionLabel: string;\n decidedAt: string | null;\n instanceState: ApprovalInstanceRecord['state'] | null;\n instanceStateLabel: string;\n key: string;\n }\n>;\n\n\n/**\n * Framework-agnostic view for the BPM \"我的待簽\" inbox. Mechanical port of\n * `apps/client/src/app/inbox/page.tsx` — renders pending tasks, tracking,\n * and history tabs.\n */\nexport function InboxView(): ReactElement {\n const router = useRouterAdapter();\n const routes = useBPMRoutes();\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [activeTab, setActiveTab] = useState<InboxTabKey>('pending');\n const [error, setError] = useState<string | null>(null);\n const [historyRows, setHistoryRows] = useState<ApprovalHistoryRow[]>([]);\n const [loading, setLoading] = useState(true);\n const [pendingRows, setPendingRows] = useState<InboxTaskRow[]>([]);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n void refreshTasks();\n }, [currentMemberId]);\n\n const pendingColumns = useMemo(\n (): TableColumn<InboxTaskRow>[] => [\n { dataIndex: 'caseTitle', key: 'caseTitle', title: '案件', width: 280 },\n { dataIndex: 'nodeId', key: 'nodeId', title: '節點', width: 180 },\n {\n dataIndex: 'statusLabel',\n key: 'statusLabel',\n title: '狀態',\n width: 120,\n },\n {\n key: 'slaDueAt',\n render: (record: InboxTaskRow): ReactElement => (\n <Typography\n color={record.slaStatusColor}\n component=\"span\"\n variant=\"body\"\n >\n {record.slaStatusText}\n </Typography>\n ),\n title: 'SLA',\n width: 180,\n },\n {\n key: 'createdAt',\n render: (record: InboxTaskRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatDateTime(record.createdAt)}\n </Typography>\n ),\n title: '建立時間',\n width: 220,\n },\n ],\n [],\n );\n const pendingTableActions = useMemo(\n (): TableActions<InboxTaskRow> => ({\n render: (record): ReturnType<TableActions<InboxTaskRow>['render']> => [\n {\n name: '處理',\n onClick: (): void => router.push(routes.caseDetail(record.instanceId)),\n },\n ],\n variant: 'base-secondary',\n width: 88,\n }),\n [router, routes],\n );\n const historyColumns = useMemo(\n (): TableColumn<ApprovalHistoryRow>[] => [\n { dataIndex: 'caseTitle', key: 'caseTitle', title: '案件', width: 280 },\n { dataIndex: 'nodeId', key: 'nodeId', title: '節點', width: 180 },\n {\n key: 'decisionLabel',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography\n color={readTaskDecisionColor(record.decisionAction)}\n component=\"span\"\n variant=\"body\"\n >\n {record.decisionLabel}\n </Typography>\n ),\n title: '決議',\n width: 120,\n },\n {\n key: 'decisionComment',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {record.decisionCommentText}\n </Typography>\n ),\n title: '意見',\n width: 240,\n },\n {\n key: 'instanceStateLabel',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography\n color={readInstanceStateColor(record.instanceState)}\n component=\"span\"\n variant=\"body\"\n >\n {record.instanceStateLabel}\n </Typography>\n ),\n title: '流程狀態',\n width: 140,\n },\n {\n key: 'decidedAt',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatHistoryDateTime(record.decidedAt)}\n </Typography>\n ),\n title: '簽核時間',\n width: 220,\n },\n ],\n [],\n );\n const historyTableActions = useMemo(\n (): TableActions<ApprovalHistoryRow> => ({\n render: (\n record,\n ): ReturnType<TableActions<ApprovalHistoryRow>['render']> => [\n {\n name: '查看',\n onClick: (): void => router.push(routes.caseDetail(record.instanceId)),\n },\n ],\n variant: 'base-secondary',\n width: 88,\n }),\n [router, routes],\n );\n const trackingRows = useMemo(\n (): ApprovalHistoryRow[] =>\n historyRows.filter((row) => row.instanceState === 'RUNNING'),\n [historyRows],\n );\n\n function handleTabChange(activeKey: Key): void {\n if (\n activeKey === 'history' ||\n activeKey === 'pending' ||\n activeKey === 'tracking'\n ) {\n setActiveTab(activeKey);\n return;\n }\n\n setActiveTab('pending');\n }\n\n async function refreshTasks(): Promise<void> {\n setLoading(true);\n setError(null);\n\n try {\n if (!currentMemberId) {\n setPendingRows([]);\n setHistoryRows([]);\n return;\n }\n\n const [nextPendingTasks, nextHistoryTasks] = await Promise.all([\n listInboxTasks(currentMemberId),\n listApprovalHistoryTasks(currentMemberId),\n ]);\n const [nextPendingRows, nextHistoryRows] = await Promise.all([\n readInboxTaskRows(nextPendingTasks),\n readApprovalHistoryRows(nextHistoryTasks),\n ]);\n\n setPendingRows(nextPendingRows);\n setHistoryRows(nextHistoryRows);\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description={`目前以 ${member?.name ?? currentMemberId ?? '目前登入會員'} 查詢待處理與歷史簽核任務。`}\n title=\"我的待簽\"\n >\n <Button\n icon={PlusIcon}\n iconType=\"leading\"\n onClick={(): void => router.push(routes.caseNew())}\n variant=\"base-primary\"\n >\n 發起簽核\n </Button>\n </ContentHeader>\n </PageHeader>\n\n <SectionGroup>\n <Section>\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n <Tab activeKey={activeTab} onChange={handleTabChange} size=\"sub\">\n <TabItem\n badgeCount={loading ? undefined : pendingRows.length}\n key=\"pending\"\n >\n 待簽核\n </TabItem>\n <TabItem\n badgeCount={loading ? undefined : trackingRows.length}\n key=\"tracking\"\n >\n 已處理未結束\n </TabItem>\n <TabItem key=\"history\">歷史簽核記錄</TabItem>\n </Tab>\n\n {activeTab === 'pending' ? (\n <Table\n actions={pendingTableActions}\n columns={pendingColumns}\n dataSource={pendingRows}\n fullWidth\n loading={loading}\n />\n ) : null}\n {activeTab === 'tracking' ? (\n <Table\n actions={historyTableActions}\n columns={historyColumns}\n dataSource={trackingRows}\n fullWidth\n loading={loading}\n />\n ) : null}\n {activeTab === 'history' ? (\n <Table\n actions={historyTableActions}\n columns={historyColumns}\n dataSource={historyRows}\n fullWidth\n loading={loading}\n />\n ) : null}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nasync function readInboxTaskRows(\n tasks: readonly TaskRecord[],\n): Promise<InboxTaskRow[]> {\n const instances = await readTaskInstances(tasks);\n\n return tasks.map(\n (task, index): InboxTaskRow => ({\n ...task,\n caseTitle: readTaskCaseTitle(task, instances[index] ?? null),\n key: task.id,\n slaStatusColor: readSlaStatusColor(task.slaDueAt),\n slaStatusText: readSlaStatusText(task.slaDueAt),\n statusLabel: readTaskStatusLabel(task.status),\n }),\n );\n}\n\nasync function readApprovalHistoryRows(\n tasks: readonly TaskRecord[],\n): Promise<ApprovalHistoryRow[]> {\n const [decisionLists, instances] = await Promise.all([\n Promise.all(tasks.map((task) => listTaskDecisions(task.id))),\n readTaskInstances(tasks),\n ]);\n\n return tasks.map((task, index): ApprovalHistoryRow => {\n const decision = readLatestTaskDecision(decisionLists[index] ?? []);\n const instance = instances[index] ?? null;\n\n return {\n ...task,\n caseTitle: readTaskCaseTitle(task, instance),\n decisionAction: decision?.action ?? null,\n decisionComment: decision?.comment ?? null,\n decisionCommentText: readDecisionCommentText(decision?.comment ?? null),\n decisionLabel: readTaskDecisionLabel(decision?.action ?? null),\n decidedAt: decision?.decidedAt ?? task.completedAt,\n instanceState: instance?.state ?? null,\n instanceStateLabel: readInstanceStateLabel(instance?.state ?? null),\n key: task.id,\n };\n });\n}\n\nasync function readTaskInstances(\n tasks: readonly TaskRecord[],\n): Promise<readonly (ApprovalInstanceRecord | null)[]> {\n return Promise.all(\n tasks.map(async (task): Promise<ApprovalInstanceRecord | null> => {\n try {\n return (await readApprovalInstance(task.instanceId)).instance;\n } catch {\n return null;\n }\n }),\n );\n}\n\nfunction readTaskCaseTitle(\n task: TaskRecord,\n instance: ApprovalInstanceRecord | null,\n): string {\n return instance ? readApprovalInstanceCaseTitle(instance) : task.instanceId;\n}\n\nfunction readLatestTaskDecision(\n decisions: readonly TaskDecisionRecord[],\n): TaskDecisionRecord | null {\n return [...decisions].sort(compareTaskDecisionDesc)[0] ?? null;\n}\n\nfunction compareTaskDecisionDesc(\n left: TaskDecisionRecord,\n right: TaskDecisionRecord,\n): number {\n return (\n new Date(right.decidedAt).getTime() - new Date(left.decidedAt).getTime()\n );\n}\n\nfunction readTaskDecisionLabel(\n action: TaskDecisionRecord['action'] | null,\n): string {\n if (action === 'APPROVED') {\n return '同意';\n }\n\n if (action === 'REJECTED') {\n return '拒絕';\n }\n\n if (action === 'RETURNED') {\n return '退回';\n }\n\n if (action === 'TRANSFERRED') {\n return '轉派';\n }\n\n return '未記錄';\n}\n\nfunction readTaskDecisionColor(\n action: TaskDecisionRecord['action'] | null,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (action === 'APPROVED') {\n return 'text-success';\n }\n\n if (action === 'REJECTED' || action === 'RETURNED') {\n return 'text-error';\n }\n\n return 'text-neutral';\n}\n\nfunction readInstanceStateLabel(\n state: ApprovalInstanceRecord['state'] | null,\n): string {\n if (state === 'RUNNING') {\n return '進行中';\n }\n\n if (state === 'APPROVED') {\n return '已通過';\n }\n\n if (state === 'REJECTED') {\n return '已拒絕';\n }\n\n if (state === 'RETURNED') {\n return '已退回';\n }\n\n if (state === 'CANCELLED') {\n return '已取消';\n }\n\n if (state === 'EXPIRED') {\n return '已逾期';\n }\n\n if (state === 'DRAFT') {\n return '草稿';\n }\n\n return '-';\n}\n\nfunction readInstanceStateColor(\n state: ApprovalInstanceRecord['state'] | null,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (state === 'APPROVED') {\n return 'text-success';\n }\n\n if (state === 'REJECTED' || state === 'CANCELLED' || state === 'EXPIRED') {\n return 'text-error';\n }\n\n return 'text-neutral';\n}\n\nfunction readDecisionCommentText(comment: string | null): string {\n const trimmedComment = comment?.trim() ?? '';\n\n return trimmedComment || '-';\n}\n\nfunction formatHistoryDateTime(value: string | null): string {\n return formatDateTime(value);\n}\n\nfunction readSlaStatusText(value: string | null): string {\n if (!value) {\n return '-';\n }\n\n const dueAt = new Date(value).getTime();\n const now = Date.now();\n const diffMs = Math.abs(dueAt - now);\n const label = formatDuration(diffMs);\n\n return dueAt < now ? `已逾期 ${label}` : `剩餘 ${label}`;\n}\n\nfunction readSlaStatusColor(\n value: string | null,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (!value) {\n return 'text-neutral';\n }\n\n return new Date(value).getTime() < Date.now() ? 'text-error' : 'text-success';\n}\n\nfunction formatDuration(value: number): string {\n const totalMinutes = Math.max(1, Math.ceil(value / 60_000));\n const days = Math.floor(totalMinutes / 1440);\n const hours = Math.floor((totalMinutes % 1440) / 60);\n const minutes = totalMinutes % 60;\n\n if (days > 0) {\n return `${days}天 ${hours}小時`;\n }\n\n if (hours > 0) {\n return `${hours}小時 ${minutes}分鐘`;\n }\n\n return `${minutes}分鐘`;\n}\n\nfunction readTaskStatusLabel(status: TaskRecord['status']): string {\n if (status === 'PENDING') {\n return '待處理';\n }\n\n if (status === 'IN_PROGRESS') {\n return '處理中';\n }\n\n if (status === 'COMPLETED') {\n return '已完成';\n }\n\n if (status === 'TRANSFERRED') {\n return '已轉派';\n }\n\n if (status === 'CANCELLED') {\n return '已取消';\n }\n\n return status;\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/views/inbox/InboxView.tsx"],"sourcesContent":["'use client';\n\nimport { type Key, ReactElement, useEffect, useMemo, useState } from 'react';\nimport {\n Button,\n PageHeader,\n Section,\n SectionGroup,\n Tab,\n TabItem,\n Table,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { PlusIcon } from '@mezzanine-ui/icons';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport {\n ApprovalInstanceRecord,\n listApprovalHistoryTasks,\n listInboxTasks,\n listTaskDecisions,\n readApprovalInstance,\n readApprovalInstanceCaseTitle,\n TaskDecisionRecord,\n TaskRecord,\n} from '@rytass/bpm-core-client/workflow';\nimport { formatDateTime } from '../../lib/format-date-time';\nimport { useAuth } from '../../lib/auth-provider';\nimport { readTaskNodeText } from '../../lib/task-node-label';\nimport { useRouterAdapter } from '../../lib/router-adapter';\nimport { useBPMRoutes } from '../../lib/routes-config';\n\ntype InboxTabKey = 'history' | 'pending' | 'tracking';\n\ntype InboxTaskRow = Readonly<\n Record<string, unknown> &\n TaskRecord & {\n caseTitle: string;\n key: string;\n nodeText: string;\n slaStatusColor: 'text-error' | 'text-neutral' | 'text-success';\n slaStatusText: string;\n statusLabel: string;\n }\n>;\n\ntype ApprovalHistoryRow = Readonly<\n Record<string, unknown> &\n TaskRecord & {\n caseTitle: string;\n decisionAction: TaskDecisionRecord['action'] | null;\n decisionComment: string | null;\n decisionCommentText: string;\n decisionLabel: string;\n decidedAt: string | null;\n instanceState: ApprovalInstanceRecord['state'] | null;\n instanceStateLabel: string;\n key: string;\n nodeText: string;\n }\n>;\n\n\n/**\n * Framework-agnostic view for the BPM \"我的待簽\" inbox. Mechanical port of\n * `apps/client/src/app/inbox/page.tsx` — renders pending tasks, tracking,\n * and history tabs.\n */\nexport function InboxView(): ReactElement {\n const router = useRouterAdapter();\n const routes = useBPMRoutes();\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [activeTab, setActiveTab] = useState<InboxTabKey>('pending');\n const [error, setError] = useState<string | null>(null);\n const [historyRows, setHistoryRows] = useState<ApprovalHistoryRow[]>([]);\n const [loading, setLoading] = useState(true);\n const [pendingRows, setPendingRows] = useState<InboxTaskRow[]>([]);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n void refreshTasks();\n }, [currentMemberId]);\n\n const pendingColumns = useMemo(\n (): TableColumn<InboxTaskRow>[] => [\n { dataIndex: 'caseTitle', key: 'caseTitle', title: '案件', width: 280 },\n { dataIndex: 'nodeText', key: 'nodeText', title: '節點', width: 180 },\n {\n dataIndex: 'statusLabel',\n key: 'statusLabel',\n title: '狀態',\n width: 120,\n },\n {\n key: 'slaDueAt',\n render: (record: InboxTaskRow): ReactElement => (\n <Typography\n color={record.slaStatusColor}\n component=\"span\"\n variant=\"body\"\n >\n {record.slaStatusText}\n </Typography>\n ),\n title: 'SLA',\n width: 180,\n },\n {\n key: 'createdAt',\n render: (record: InboxTaskRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatDateTime(record.createdAt)}\n </Typography>\n ),\n title: '建立時間',\n width: 220,\n },\n ],\n [],\n );\n const pendingTableActions = useMemo(\n (): TableActions<InboxTaskRow> => ({\n render: (record): ReturnType<TableActions<InboxTaskRow>['render']> => [\n {\n name: '處理',\n onClick: (): void => router.push(routes.caseDetail(record.instanceId)),\n },\n ],\n variant: 'base-secondary',\n width: 88,\n }),\n [router, routes],\n );\n const historyColumns = useMemo(\n (): TableColumn<ApprovalHistoryRow>[] => [\n { dataIndex: 'caseTitle', key: 'caseTitle', title: '案件', width: 280 },\n { dataIndex: 'nodeText', key: 'nodeText', title: '節點', width: 180 },\n {\n key: 'decisionLabel',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography\n color={readTaskDecisionColor(record.decisionAction)}\n component=\"span\"\n variant=\"body\"\n >\n {record.decisionLabel}\n </Typography>\n ),\n title: '決議',\n width: 120,\n },\n {\n key: 'decisionComment',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {record.decisionCommentText}\n </Typography>\n ),\n title: '意見',\n width: 240,\n },\n {\n key: 'instanceStateLabel',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography\n color={readInstanceStateColor(record.instanceState)}\n component=\"span\"\n variant=\"body\"\n >\n {record.instanceStateLabel}\n </Typography>\n ),\n title: '流程狀態',\n width: 140,\n },\n {\n key: 'decidedAt',\n render: (record: ApprovalHistoryRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatHistoryDateTime(record.decidedAt)}\n </Typography>\n ),\n title: '簽核時間',\n width: 220,\n },\n ],\n [],\n );\n const historyTableActions = useMemo(\n (): TableActions<ApprovalHistoryRow> => ({\n render: (\n record,\n ): ReturnType<TableActions<ApprovalHistoryRow>['render']> => [\n {\n name: '查看',\n onClick: (): void => router.push(routes.caseDetail(record.instanceId)),\n },\n ],\n variant: 'base-secondary',\n width: 88,\n }),\n [router, routes],\n );\n const trackingRows = useMemo(\n (): ApprovalHistoryRow[] =>\n historyRows.filter((row) => row.instanceState === 'RUNNING'),\n [historyRows],\n );\n\n function handleTabChange(activeKey: Key): void {\n if (\n activeKey === 'history' ||\n activeKey === 'pending' ||\n activeKey === 'tracking'\n ) {\n setActiveTab(activeKey);\n return;\n }\n\n setActiveTab('pending');\n }\n\n async function refreshTasks(): Promise<void> {\n setLoading(true);\n setError(null);\n\n try {\n if (!currentMemberId) {\n setPendingRows([]);\n setHistoryRows([]);\n return;\n }\n\n const [nextPendingTasks, nextHistoryTasks] = await Promise.all([\n listInboxTasks(currentMemberId),\n listApprovalHistoryTasks(currentMemberId),\n ]);\n const [nextPendingRows, nextHistoryRows] = await Promise.all([\n readInboxTaskRows(nextPendingTasks),\n readApprovalHistoryRows(nextHistoryTasks),\n ]);\n\n setPendingRows(nextPendingRows);\n setHistoryRows(nextHistoryRows);\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description={`目前以 ${member?.name ?? currentMemberId ?? '目前登入會員'} 查詢待處理與歷史簽核任務。`}\n title=\"我的待簽\"\n >\n <Button\n icon={PlusIcon}\n iconType=\"leading\"\n onClick={(): void => router.push(routes.caseNew())}\n variant=\"base-primary\"\n >\n 發起簽核\n </Button>\n </ContentHeader>\n </PageHeader>\n\n <SectionGroup>\n <Section>\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n <Tab activeKey={activeTab} onChange={handleTabChange} size=\"sub\">\n <TabItem\n badgeCount={loading ? undefined : pendingRows.length}\n key=\"pending\"\n >\n 待簽核\n </TabItem>\n <TabItem\n badgeCount={loading ? undefined : trackingRows.length}\n key=\"tracking\"\n >\n 已處理未結束\n </TabItem>\n <TabItem key=\"history\">歷史簽核記錄</TabItem>\n </Tab>\n\n {activeTab === 'pending' ? (\n <Table\n actions={pendingTableActions}\n columns={pendingColumns}\n dataSource={pendingRows}\n fullWidth\n loading={loading}\n />\n ) : null}\n {activeTab === 'tracking' ? (\n <Table\n actions={historyTableActions}\n columns={historyColumns}\n dataSource={trackingRows}\n fullWidth\n loading={loading}\n />\n ) : null}\n {activeTab === 'history' ? (\n <Table\n actions={historyTableActions}\n columns={historyColumns}\n dataSource={historyRows}\n fullWidth\n loading={loading}\n />\n ) : null}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nasync function readInboxTaskRows(\n tasks: readonly TaskRecord[],\n): Promise<InboxTaskRow[]> {\n const instances = await readTaskInstances(tasks);\n\n return tasks.map(\n (task, index): InboxTaskRow => ({\n ...task,\n caseTitle: readTaskCaseTitle(task, instances[index] ?? null),\n key: task.id,\n nodeText: readTaskNodeText(task),\n slaStatusColor: readSlaStatusColor(task.slaDueAt),\n slaStatusText: readSlaStatusText(task.slaDueAt),\n statusLabel: readTaskStatusLabel(task.status),\n }),\n );\n}\n\nasync function readApprovalHistoryRows(\n tasks: readonly TaskRecord[],\n): Promise<ApprovalHistoryRow[]> {\n const [decisionLists, instances] = await Promise.all([\n Promise.all(tasks.map((task) => listTaskDecisions(task.id))),\n readTaskInstances(tasks),\n ]);\n\n return tasks.map((task, index): ApprovalHistoryRow => {\n const decision = readLatestTaskDecision(decisionLists[index] ?? []);\n const instance = instances[index] ?? null;\n\n return {\n ...task,\n caseTitle: readTaskCaseTitle(task, instance),\n decisionAction: decision?.action ?? null,\n decisionComment: decision?.comment ?? null,\n decisionCommentText: readDecisionCommentText(decision?.comment ?? null),\n decisionLabel: readTaskDecisionLabel(decision?.action ?? null),\n decidedAt: decision?.decidedAt ?? task.completedAt,\n instanceState: instance?.state ?? null,\n instanceStateLabel: readInstanceStateLabel(instance?.state ?? null),\n key: task.id,\n nodeText: readTaskNodeText(task),\n };\n });\n}\n\nasync function readTaskInstances(\n tasks: readonly TaskRecord[],\n): Promise<readonly (ApprovalInstanceRecord | null)[]> {\n return Promise.all(\n tasks.map(async (task): Promise<ApprovalInstanceRecord | null> => {\n try {\n return (await readApprovalInstance(task.instanceId)).instance;\n } catch {\n return null;\n }\n }),\n );\n}\n\nfunction readTaskCaseTitle(\n task: TaskRecord,\n instance: ApprovalInstanceRecord | null,\n): string {\n return instance ? readApprovalInstanceCaseTitle(instance) : task.instanceId;\n}\n\nfunction readLatestTaskDecision(\n decisions: readonly TaskDecisionRecord[],\n): TaskDecisionRecord | null {\n return [...decisions].sort(compareTaskDecisionDesc)[0] ?? null;\n}\n\nfunction compareTaskDecisionDesc(\n left: TaskDecisionRecord,\n right: TaskDecisionRecord,\n): number {\n return (\n new Date(right.decidedAt).getTime() - new Date(left.decidedAt).getTime()\n );\n}\n\nfunction readTaskDecisionLabel(\n action: TaskDecisionRecord['action'] | null,\n): string {\n if (action === 'APPROVED') {\n return '同意';\n }\n\n if (action === 'REJECTED') {\n return '拒絕';\n }\n\n if (action === 'RETURNED') {\n return '退回';\n }\n\n if (action === 'TRANSFERRED') {\n return '轉派';\n }\n\n return '未記錄';\n}\n\nfunction readTaskDecisionColor(\n action: TaskDecisionRecord['action'] | null,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (action === 'APPROVED') {\n return 'text-success';\n }\n\n if (action === 'REJECTED' || action === 'RETURNED') {\n return 'text-error';\n }\n\n return 'text-neutral';\n}\n\nfunction readInstanceStateLabel(\n state: ApprovalInstanceRecord['state'] | null,\n): string {\n if (state === 'RUNNING') {\n return '進行中';\n }\n\n if (state === 'APPROVED') {\n return '已通過';\n }\n\n if (state === 'REJECTED') {\n return '已拒絕';\n }\n\n if (state === 'RETURNED') {\n return '已退回';\n }\n\n if (state === 'CANCELLED') {\n return '已取消';\n }\n\n if (state === 'EXPIRED') {\n return '已逾期';\n }\n\n if (state === 'DRAFT') {\n return '草稿';\n }\n\n return '-';\n}\n\nfunction readInstanceStateColor(\n state: ApprovalInstanceRecord['state'] | null,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (state === 'APPROVED') {\n return 'text-success';\n }\n\n if (state === 'REJECTED' || state === 'CANCELLED' || state === 'EXPIRED') {\n return 'text-error';\n }\n\n return 'text-neutral';\n}\n\nfunction readDecisionCommentText(comment: string | null): string {\n const trimmedComment = comment?.trim() ?? '';\n\n return trimmedComment || '-';\n}\n\nfunction formatHistoryDateTime(value: string | null): string {\n return formatDateTime(value);\n}\n\nfunction readSlaStatusText(value: string | null): string {\n if (!value) {\n return '-';\n }\n\n const dueAt = new Date(value).getTime();\n const now = Date.now();\n const diffMs = Math.abs(dueAt - now);\n const label = formatDuration(diffMs);\n\n return dueAt < now ? `已逾期 ${label}` : `剩餘 ${label}`;\n}\n\nfunction readSlaStatusColor(\n value: string | null,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (!value) {\n return 'text-neutral';\n }\n\n return new Date(value).getTime() < Date.now() ? 'text-error' : 'text-success';\n}\n\nfunction formatDuration(value: number): string {\n const totalMinutes = Math.max(1, Math.ceil(value / 60_000));\n const days = Math.floor(totalMinutes / 1440);\n const hours = Math.floor((totalMinutes % 1440) / 60);\n const minutes = totalMinutes % 60;\n\n if (days > 0) {\n return `${days}天 ${hours}小時`;\n }\n\n if (hours > 0) {\n return `${hours}小時 ${minutes}分鐘`;\n }\n\n return `${minutes}分鐘`;\n}\n\nfunction readTaskStatusLabel(status: TaskRecord['status']): string {\n if (status === 'PENDING') {\n return '待處理';\n }\n\n if (status === 'IN_PROGRESS') {\n return '處理中';\n }\n\n if (status === 'COMPLETED') {\n return '已完成';\n }\n\n if (status === 'TRANSFERRED') {\n return '已轉派';\n }\n\n if (status === 'CANCELLED') {\n return '已取消';\n }\n\n return status;\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}