@rytass/bpm-core-react 0.20.1 → 0.21.1

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 (176) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +1 -1
  3. package/dist/approval-instance-list-page.css +1 -1
  4. package/dist/chunks/{FormBuilderView-CFRiw04B.js → FormBuilderView-6KdApi-D.js} +667 -584
  5. package/dist/chunks/FormBuilderView-6KdApi-D.js.map +1 -0
  6. package/dist/chunks/FormBuilderView-CGKkuVFi.cjs +4 -0
  7. package/dist/chunks/FormBuilderView-CGKkuVFi.cjs.map +1 -0
  8. package/dist/chunks/{FormRendererView-lYSaR8CQ.cjs → FormRendererView-B-xycC9S.cjs} +3 -3
  9. package/dist/chunks/{FormRendererView-lYSaR8CQ.cjs.map → FormRendererView-B-xycC9S.cjs.map} +1 -1
  10. package/dist/chunks/{FormRendererView-GfbVWEGa.js → FormRendererView-BcQKUi1A.js} +238 -238
  11. package/dist/chunks/{FormRendererView-GfbVWEGa.js.map → FormRendererView-BcQKUi1A.js.map} +1 -1
  12. package/dist/chunks/approval-instance-list-page-BI5fIXc-.js +724 -0
  13. package/dist/chunks/approval-instance-list-page-BI5fIXc-.js.map +1 -0
  14. package/dist/chunks/approval-instance-list-page-Cv5I1YPX.cjs +2 -0
  15. package/dist/chunks/approval-instance-list-page-Cv5I1YPX.cjs.map +1 -0
  16. package/dist/chunks/{categories-DQUtVEiV.cjs → categories-CyyqG6wG.cjs} +2 -2
  17. package/dist/chunks/{categories-DQUtVEiV.cjs.map → categories-CyyqG6wG.cjs.map} +1 -1
  18. package/dist/chunks/{categories-BAdxjNSA.js → categories-f7E-RJEc.js} +9 -9
  19. package/dist/chunks/{categories-BAdxjNSA.js.map → categories-f7E-RJEc.js.map} +1 -1
  20. package/dist/chunks/{compose-egQlRjuZ.js → compose-DFuLPmOA.js} +5 -5
  21. package/dist/chunks/{compose-egQlRjuZ.js.map → compose-DFuLPmOA.js.map} +1 -1
  22. package/dist/chunks/compose-DK3ocWwG.cjs +2 -0
  23. package/dist/chunks/compose-DK3ocWwG.cjs.map +1 -0
  24. package/dist/chunks/{delegations-CVmtpkRn.cjs → delegations-ClRCwD1I.cjs} +2 -2
  25. package/dist/chunks/{delegations-CVmtpkRn.cjs.map → delegations-ClRCwD1I.cjs.map} +1 -1
  26. package/dist/chunks/{delegations-9-yvWWws.js → delegations-CqND-Qtz.js} +5 -5
  27. package/dist/chunks/{delegations-9-yvWWws.js.map → delegations-CqND-Qtz.js.map} +1 -1
  28. package/dist/chunks/{delegations-CrfjsH94.cjs → delegations-D43VDSz1.cjs} +2 -2
  29. package/dist/chunks/{delegations-CrfjsH94.cjs.map → delegations-D43VDSz1.cjs.map} +1 -1
  30. package/dist/chunks/{delegations-CxnNvTCc.js → delegations-zisffLwk.js} +5 -5
  31. package/dist/chunks/{delegations-CxnNvTCc.js.map → delegations-zisffLwk.js.map} +1 -1
  32. package/dist/chunks/designer-CAUWEKJ4.cjs +69 -0
  33. package/dist/chunks/designer-CAUWEKJ4.cjs.map +1 -0
  34. package/dist/chunks/{designer-CZDlQ9Nf.js → designer-CCy5EbOe.js} +2380 -1786
  35. package/dist/chunks/designer-CCy5EbOe.js.map +1 -0
  36. package/dist/chunks/detail-C5KrUC7s.cjs +5 -0
  37. package/dist/chunks/detail-C5KrUC7s.cjs.map +1 -0
  38. package/dist/chunks/{detail-ByZV1XaI.js → detail-Dnc4PPWO.js} +804 -777
  39. package/dist/chunks/detail-Dnc4PPWO.js.map +1 -0
  40. package/dist/chunks/instance-error-message-BEM4O_Kj.js +208 -0
  41. package/dist/chunks/instance-error-message-BEM4O_Kj.js.map +1 -0
  42. package/dist/chunks/instance-error-message-BpCLCOjp.cjs +2 -0
  43. package/dist/chunks/instance-error-message-BpCLCOjp.cjs.map +1 -0
  44. package/dist/chunks/organization-time-zone-provider-B7CXQril.cjs +2 -0
  45. package/dist/chunks/organization-time-zone-provider-B7CXQril.cjs.map +1 -0
  46. package/dist/chunks/organization-time-zone-provider-DNL8yo4-.js +42 -0
  47. package/dist/chunks/organization-time-zone-provider-DNL8yo4-.js.map +1 -0
  48. package/dist/chunks/{providers-BbCs23sk.js → providers-CQWekD7_.js} +197 -196
  49. package/dist/chunks/providers-CQWekD7_.js.map +1 -0
  50. package/dist/chunks/providers-VUOh6i0i.cjs +2 -0
  51. package/dist/chunks/providers-VUOh6i0i.cjs.map +1 -0
  52. package/dist/components/approval-instance-field-filter-controls.d.cts +12 -0
  53. package/dist/components/approval-instance-field-filter-controls.d.ts +12 -0
  54. package/dist/components/approval-instance-field-filters.d.cts +86 -0
  55. package/dist/components/approval-instance-field-filters.d.ts +86 -0
  56. package/dist/components/approval-instance-list-page.d.cts +4 -0
  57. package/dist/components/approval-instance-list-page.d.ts +4 -0
  58. package/dist/components/notification-drawer-options.d.cts +18 -0
  59. package/dist/components/notification-drawer-options.d.ts +18 -0
  60. package/dist/index.cjs +1 -1
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts +1 -0
  63. package/dist/index.d.ts +1 -0
  64. package/dist/index.js +23 -23
  65. package/dist/index.js.map +1 -1
  66. package/dist/lib/organization-time-zone-provider.d.cts +20 -0
  67. package/dist/lib/organization-time-zone-provider.d.ts +20 -0
  68. package/dist/lib/organization-time-zone.d.cts +8 -0
  69. package/dist/lib/organization-time-zone.d.ts +8 -0
  70. package/dist/lib/providers.d.cts +10 -1
  71. package/dist/lib/providers.d.ts +10 -1
  72. package/dist/next/BPMNextProviders.d.cts +8 -1
  73. package/dist/next/BPMNextProviders.d.ts +8 -1
  74. package/dist/next/index.cjs +1 -1
  75. package/dist/next/index.cjs.map +1 -1
  76. package/dist/next/index.js +12 -11
  77. package/dist/next/index.js.map +1 -1
  78. package/dist/next/workflow-chat-route.cjs +2 -2
  79. package/dist/next/workflow-chat-route.cjs.map +1 -1
  80. package/dist/next/workflow-chat-route.js +13 -13
  81. package/dist/pages/admin/delegations/index.cjs +1 -1
  82. package/dist/pages/admin/delegations/index.js +1 -1
  83. package/dist/pages/delegations/index.cjs +1 -1
  84. package/dist/pages/delegations/index.js +1 -1
  85. package/dist/pages/instances/detail/index.cjs +1 -1
  86. package/dist/pages/instances/detail/index.js +1 -1
  87. package/dist/pages/templates/categories/index.cjs +1 -1
  88. package/dist/pages/templates/categories/index.js +1 -1
  89. package/dist/pages/templates/compose/index.cjs +1 -1
  90. package/dist/pages/templates/compose/index.js +1 -1
  91. package/dist/pages/templates/designer/index.cjs +1 -1
  92. package/dist/pages/templates/designer/index.js +1 -1
  93. package/dist/views/admin/delegations/index.cjs +1 -1
  94. package/dist/views/admin/delegations/index.js +1 -1
  95. package/dist/views/admin/index.cjs +1 -1
  96. package/dist/views/admin/index.js +1 -1
  97. package/dist/views/admin/webhook-endpoints/index.cjs +1 -1
  98. package/dist/views/admin/webhook-endpoints/index.cjs.map +1 -1
  99. package/dist/views/admin/webhook-endpoints/index.js +60 -60
  100. package/dist/views/admin/webhook-endpoints/index.js.map +1 -1
  101. package/dist/views/cc/index.cjs +1 -1
  102. package/dist/views/cc/index.js +1 -1
  103. package/dist/views/delegations/index.cjs +1 -1
  104. package/dist/views/delegations/index.js +1 -1
  105. package/dist/views/forms/builder/FormBuilderView.d.cts +8 -1
  106. package/dist/views/forms/builder/FormBuilderView.d.ts +8 -1
  107. package/dist/views/forms/builder/index.cjs +1 -1
  108. package/dist/views/forms/builder/index.js +1 -1
  109. package/dist/views/forms/renderer/index.cjs +1 -1
  110. package/dist/views/forms/renderer/index.js +1 -1
  111. package/dist/views/instances/approver-filled-form.d.cts +8 -5
  112. package/dist/views/instances/approver-filled-form.d.ts +8 -5
  113. package/dist/views/instances/detail/index.cjs +1 -1
  114. package/dist/views/instances/detail/index.js +1 -1
  115. package/dist/views/instances/detail/sections/InstanceTasksSection.d.cts +2 -1
  116. package/dist/views/instances/detail/sections/InstanceTasksSection.d.ts +2 -1
  117. package/dist/views/instances/detail/sections/shared.d.cts +0 -1
  118. package/dist/views/instances/detail/sections/shared.d.ts +0 -1
  119. package/dist/views/instances/new/index.cjs +1 -1
  120. package/dist/views/instances/new/index.cjs.map +1 -1
  121. package/dist/views/instances/new/index.js +8 -8
  122. package/dist/views/instances/new/index.js.map +1 -1
  123. package/dist/views/search/index.cjs +1 -1
  124. package/dist/views/search/index.js +1 -1
  125. package/dist/views/sent/index.cjs +1 -1
  126. package/dist/views/sent/index.js +1 -1
  127. package/dist/views/templates/categories/index.cjs +1 -1
  128. package/dist/views/templates/categories/index.js +1 -1
  129. package/dist/views/templates/compose/index.cjs +1 -1
  130. package/dist/views/templates/compose/index.js +1 -1
  131. package/dist/views/templates/designer/NotifyWebhookTargetsEditor.d.cts +2 -0
  132. package/dist/views/templates/designer/NotifyWebhookTargetsEditor.d.ts +2 -0
  133. package/dist/views/templates/designer/approver-resolver-model.d.cts +105 -1
  134. package/dist/views/templates/designer/approver-resolver-model.d.ts +105 -1
  135. package/dist/views/templates/designer/dynamic-form-resolver-options.d.cts +5 -0
  136. package/dist/views/templates/designer/dynamic-form-resolver-options.d.ts +5 -0
  137. package/dist/views/templates/designer/index.cjs +1 -1
  138. package/dist/views/templates/designer/index.js +1 -1
  139. package/dist/views/templates/designer/node-actor-resolver-options.d.cts +44 -1
  140. package/dist/views/templates/designer/node-actor-resolver-options.d.ts +44 -1
  141. package/dist/views/templates/designer/notify-recipient-options.d.cts +81 -0
  142. package/dist/views/templates/designer/notify-recipient-options.d.ts +81 -0
  143. package/dist/views/templates/designer/position-level-options.d.cts +3 -1
  144. package/dist/views/templates/designer/position-level-options.d.ts +3 -1
  145. package/dist/views/templates/designer/position-scope.d.cts +51 -0
  146. package/dist/views/templates/designer/position-scope.d.ts +51 -0
  147. package/dist/views/templates/index.cjs +1 -1
  148. package/dist/views/templates/index.cjs.map +1 -1
  149. package/dist/views/templates/index.js +7 -7
  150. package/package.json +3 -3
  151. package/dist/chunks/FormBuilderView-CFRiw04B.js.map +0 -1
  152. package/dist/chunks/FormBuilderView-CpUgbSbt.cjs +0 -3
  153. package/dist/chunks/FormBuilderView-CpUgbSbt.cjs.map +0 -1
  154. package/dist/chunks/approval-instance-list-page-BnI4t5mD.js +0 -279
  155. package/dist/chunks/approval-instance-list-page-BnI4t5mD.js.map +0 -1
  156. package/dist/chunks/approval-instance-list-page-fqvmkBlg.cjs +0 -2
  157. package/dist/chunks/approval-instance-list-page-fqvmkBlg.cjs.map +0 -1
  158. package/dist/chunks/compose-Bb5bUVFd.cjs +0 -2
  159. package/dist/chunks/compose-Bb5bUVFd.cjs.map +0 -1
  160. package/dist/chunks/designer-CX9bhYJS.cjs +0 -69
  161. package/dist/chunks/designer-CX9bhYJS.cjs.map +0 -1
  162. package/dist/chunks/designer-CZDlQ9Nf.js.map +0 -1
  163. package/dist/chunks/detail-ByZV1XaI.js.map +0 -1
  164. package/dist/chunks/detail-ChseSF55.cjs +0 -5
  165. package/dist/chunks/detail-ChseSF55.cjs.map +0 -1
  166. package/dist/chunks/instance-error-message-DTARGbGo.cjs +0 -2
  167. package/dist/chunks/instance-error-message-DTARGbGo.cjs.map +0 -1
  168. package/dist/chunks/instance-error-message-q6gy5evy.js +0 -120
  169. package/dist/chunks/instance-error-message-q6gy5evy.js.map +0 -1
  170. package/dist/chunks/language-provider-Bdnx3Gxl.cjs +0 -2
  171. package/dist/chunks/language-provider-Bdnx3Gxl.cjs.map +0 -1
  172. package/dist/chunks/language-provider-phkWOapf.js +0 -17
  173. package/dist/chunks/language-provider-phkWOapf.js.map +0 -1
  174. package/dist/chunks/providers-BbCs23sk.js.map +0 -1
  175. package/dist/chunks/providers-D_KAgOOu.cjs +0 -2
  176. package/dist/chunks/providers-D_KAgOOu.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers-CQWekD7_.js","names":["_Fragment","Modal","Textarea","useAuth","DEFAULT_BPM_LANGUAGE","BPMOrganizationTimeZoneProvider","useRouterAdapter","useBPMRoutes","BPMFormField","Fragment$1","Drawer","Modal$1","NotificationCenter","Textarea$1","NotificationDrawerContext","createContext","NotificationDrawerProvider","isOpen","setIsOpen","useState","open","useCallback","close","toggle","current","value","useMemo","useNotificationDrawer","useContext","NotificationUnreadContext","NotificationUnreadProvider","currentMemberId","member","unreadCount","setUnreadCount","refreshUnreadCount","next","readUnreadNotificationCount","active","useNotificationUnread","buildNotificationOptions","payload","readNotificationPayload","record","canReject","readNotificationAllowReject","opensToFill","readPayloadFlag","isRecord","key","TIME_GROUP_ORDER","TIME_GROUP_LABEL","PAGE_SIZE","TOGGLE_ARCHIVED_OPTION_ID","RESOLVED_TITLE","resolveDisplayTitle","resolveSeverity","toSeverity","NotificationDrawer","router","routes","rows","setRows","totalCount","setTotalCount","page","setPage","loading","setLoading","bulkLoading","setBulkLoading","error","setError","notice","setNotice","filter","setFilter","includeArchived","setIncludeArchived","approveTarget","setApproveTarget","approveComment","setApproveComment","rejectTarget","setRejectTarget","rejectReason","setRejectReason","deciding","setDeciding","trimmedApproveComment","trimmedRejectReason","loadPage","result","listNotifications","nextPage","append","readErrorMessage","handleFilterChange","event","handleMarkAllRead","markAllNotificationsRead","handleLoadMore","handleMarkRead","markNotificationRead","handleArchive","label","unarchiveNotifications","archiveNotifications","handleFilterAreaSelect","option","handleOpenInstance","openApproveModal","closeApproveModal","handleApproveConfirm","target","decideTask","openRejectModal","closeRejectModal","handleRejectConfirm","handleBadgeSelect","action","isCardActivation","card","handleCardActivate","handleCardKeyDown","filteredRows","row","groupedRows","now","buckets","group","accumulator","resolveTimeGroup","items","hasMore","Fragment","openable","itemIndex","Typography","type","notificationDate","nowStartOfDay","notificationStartOfDay","yesterdayStartOfDay","Providers","CalendarLocale","CalendarConfigProviderMoment","BPMLanguageProvider","organizationTimeZone","AuthProvider","children"],"sources":["../../src/lib/notification-drawer-provider.tsx","../../src/lib/notification-unread-provider.tsx","../../src/components/notification-drawer-options.ts","../../src/components/notification-drawer.tsx","../../src/lib/providers.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\ninterface NotificationDrawerContextValue {\n readonly close: () => void;\n readonly isOpen: boolean;\n readonly open: () => void;\n readonly toggle: () => void;\n}\n\nconst NotificationDrawerContext =\n createContext<NotificationDrawerContextValue | null>(null);\n\ninterface NotificationDrawerProviderProps {\n readonly children: ReactNode;\n}\n\n/**\n * Controls the open/closed state of the BPM notification drawer. Wraps\n * children with a context that `<NotificationDrawer />` reads to mount /\n * hide itself, and that the host navigation reads (via\n * `<BPMNotificationBellButton />` or `useNotificationDrawer().open`) to\n * open the drawer when the bell icon is clicked.\n *\n * When used outside this provider, the returned hook is a safe no-op so\n * components don't crash in test or storybook environments.\n */\nexport function NotificationDrawerProvider({\n children,\n}: NotificationDrawerProviderProps): ReactElement {\n const [isOpen, setIsOpen] = useState(false);\n\n const open = useCallback((): void => {\n setIsOpen(true);\n }, []);\n const close = useCallback((): void => {\n setIsOpen(false);\n }, []);\n const toggle = useCallback((): void => {\n setIsOpen((current) => !current);\n }, []);\n\n const value = useMemo<NotificationDrawerContextValue>(\n () => ({ close, isOpen, open, toggle }),\n [close, isOpen, open, toggle],\n );\n\n return (\n <NotificationDrawerContext.Provider value={value}>\n {children}\n </NotificationDrawerContext.Provider>\n );\n}\n\n/**\n * Read the BPM notification drawer's open state and control helpers.\n * Returns a no-op stub when used outside `<NotificationDrawerProvider>`.\n */\nexport function useNotificationDrawer(): NotificationDrawerContextValue {\n const context = useContext(NotificationDrawerContext);\n if (!context) {\n return {\n close: (): void => undefined,\n isOpen: false,\n open: (): void => undefined,\n toggle: (): void => undefined,\n };\n }\n return context;\n}\n","'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { readUnreadNotificationCount } from '@rytass/bpm-core-client/workflow';\nimport { useAuth } from './auth-provider';\n\ninterface NotificationUnreadContextValue {\n readonly refreshUnreadCount: () => Promise<number>;\n readonly unreadCount: number;\n}\n\nconst NotificationUnreadContext =\n createContext<NotificationUnreadContextValue | null>(null);\n\ninterface NotificationUnreadProviderProps {\n readonly children: ReactNode;\n}\n\n/**\n * Polls BPM for the current member's unread notification count via\n * `readUnreadNotificationCount` and exposes it through context for the\n * host navigation (`<BPMNotificationBellButton />` badge or any custom\n * trigger using `useNotificationUnread().unreadCount`) and the BPM\n * `<NotificationDrawer />` (header count). Refresh is triggered on\n * mount and whenever the auth member id changes; consumers can call\n * `refreshUnreadCount()` after acknowledging a notification.\n */\nexport function NotificationUnreadProvider({\n children,\n}: NotificationUnreadProviderProps): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [unreadCount, setUnreadCount] = useState(0);\n\n const refreshUnreadCount = useCallback(async (): Promise<number> => {\n if (!currentMemberId) {\n setUnreadCount(0);\n return 0;\n }\n const next = await readUnreadNotificationCount(currentMemberId);\n setUnreadCount(next);\n return next;\n }, [currentMemberId]);\n\n useEffect((): (() => void) => {\n let active = true;\n void (async () => {\n try {\n const next = await refreshUnreadCount();\n if (active) setUnreadCount(next);\n } catch {\n if (active) setUnreadCount(0);\n }\n })();\n return (): void => {\n active = false;\n };\n }, [refreshUnreadCount]);\n\n const value = useMemo<NotificationUnreadContextValue>(\n () => ({ refreshUnreadCount, unreadCount }),\n [refreshUnreadCount, unreadCount],\n );\n\n return (\n <NotificationUnreadContext.Provider value={value}>\n {children}\n </NotificationUnreadContext.Provider>\n );\n}\n\n/**\n * Read the current unread-notification count and a manual refresh helper.\n * Returns a zero/no-op stub when used outside\n * `<NotificationUnreadProvider>`.\n */\nexport function useNotificationUnread(): NotificationUnreadContextValue {\n const context = useContext(NotificationUnreadContext);\n if (!context) {\n return {\n refreshUnreadCount: async (): Promise<number> => 0,\n unreadCount: 0,\n };\n }\n return context;\n}\n","import type { DropdownOption } from '@mezzanine-ui/core/dropdown/dropdown';\nimport type { NotificationRecord } from '@rytass/bpm-core-client/workflow';\n\nexport type NotificationAction =\n 'approve' | 'archive' | 'reject' | 'unarchive' | 'view' | 'read';\n\n/**\n * Build the `...` dropdown options for one notification. Approve / reject are\n * offered only while the server still reports the notification as `actionable`\n * (an unresolved task assignment) — once the task is decided / cancelled the\n * backend flips `actionable` to false, so a stale \"同意\" can never appear.\n * A task that fills in form fields (`hasFillableFields`) cannot be approved\n * from here — the fill has to be entered on the case page — so its \"同意\"\n * becomes \"開啟案件\". So does a task at a node that requires an approval\n * comment (`requireApprovalComment`): the drawer has no comment dialog of its\n * own for that, the case page does.\n * \"查看案件\" needs an `instanceId`; \"標為已讀\" only shows while unread.\n * Archiving is independent of read state, so the entry flips between\n * \"封存\" / \"取消封存\" for every notification.\n */\nexport function buildNotificationOptions(\n record: NotificationRecord,\n): readonly DropdownOption[] {\n const payload = readNotificationPayload(record);\n const canReject = readNotificationAllowReject(record, payload) !== false;\n const opensToFill =\n record.actionable &&\n Boolean(record.instanceId) &&\n (readPayloadFlag(payload, 'hasFillableFields') ||\n readPayloadFlag(payload, 'requireApprovalComment'));\n\n return [\n ...(record.actionable\n ? ([\n opensToFill\n ? { id: 'view', name: '開啟案件' }\n : { id: 'approve', name: '同意' },\n ...(canReject ? [{ id: 'reject', name: '拒絕' }] : []),\n ] satisfies DropdownOption[])\n : []),\n ...(record.instanceId && !opensToFill\n ? ([{ id: 'view', name: '查看案件' }] satisfies DropdownOption[])\n : []),\n ...(record.status !== 'READ'\n ? ([{ id: 'read', name: '標為已讀' }] satisfies DropdownOption[])\n : []),\n record.archivedAt\n ? { id: 'unarchive', name: '取消封存' }\n : { id: 'archive', name: '封存' },\n ];\n}\n\n/**\n * The notification's `payloadJson`, parsed once. `null` when it is not a JSON\n * object; every reader below takes this one result.\n */\nfunction readNotificationPayload(\n record: NotificationRecord,\n): Readonly<Record<string, unknown>> | null {\n try {\n const payload: unknown = JSON.parse(record.payloadJson);\n\n return isRecord(payload) ? payload : null;\n } catch {\n return null;\n }\n}\n\nfunction readNotificationAllowReject(\n record: NotificationRecord,\n payload: Readonly<Record<string, unknown>> | null,\n): boolean | null {\n if (payload && typeof payload.allowReject === 'boolean') {\n return payload.allowReject;\n }\n\n return record.allowReject;\n}\n\n/**\n * `true` only when the payload sets the flag to `true`. A missing key, a\n * `false`, or an unparsable payload all read as `false`.\n *\n * `TASK_ASSIGNED` / `TASK_TRANSFERRED` payloads carry `hasFillableFields`, and\n * `requireApprovalComment: true` only when the node asks for a comment on\n * approval. Notifications created before those keys existed read as `false`.\n */\nfunction readPayloadFlag(\n payload: Readonly<Record<string, unknown>> | null,\n key: string,\n): boolean {\n return payload !== null && payload[key] === true;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","'use client';\n\nimport {\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n type ChangeEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactElement,\n} from 'react';\nimport Drawer from '@mezzanine-ui/react/Drawer';\nimport Modal from '@mezzanine-ui/react/Modal';\nimport NotificationCenter from '@mezzanine-ui/react/NotificationCenter';\nimport Textarea from '@mezzanine-ui/react/Textarea';\nimport { Typography } from '@mezzanine-ui/react';\nimport type { NotificationSeverity } from '@mezzanine-ui/core/notification-center';\nimport type { DropdownOption } from '@mezzanine-ui/core/dropdown/dropdown';\nimport {\n archiveNotifications,\n decideTask,\n listNotifications,\n markAllNotificationsRead,\n markNotificationRead,\n unarchiveNotifications,\n type NotificationRecord,\n type NotificationResolution,\n type NotificationType,\n} from '@rytass/bpm-core-client/workflow';\nimport { useAuth } from '../lib/auth-provider';\nimport { useNotificationDrawer } from '../lib/notification-drawer-provider';\nimport { useNotificationUnread } from '../lib/notification-unread-provider';\nimport { useRouterAdapter } from '../lib/router-adapter';\nimport { useBPMRoutes } from '../lib/routes-config';\nimport { BPMFormField } from './bpm-form-field';\nimport {\n buildNotificationOptions,\n type NotificationAction,\n} from './notification-drawer-options';\n\ntype FilterValue = 'all' | 'read' | 'unread';\n\ntype TimeGroup = 'today' | 'yesterday' | 'past7Days' | 'earlier';\n\nconst TIME_GROUP_ORDER: readonly TimeGroup[] = [\n 'today',\n 'yesterday',\n 'past7Days',\n 'earlier',\n];\n\nconst TIME_GROUP_LABEL: Readonly<Record<TimeGroup, string>> = {\n earlier: '更早',\n past7Days: '過去七天',\n today: '今天',\n yesterday: '昨天',\n};\n\nconst PAGE_SIZE = 50;\n\n/** Identifier for the filter-bar dropdown entry toggling the archived view. */\nconst TOGGLE_ARCHIVED_OPTION_ID = 'toggleArchived';\n\n/**\n * Title shown for a resolved task-assignment notification, replacing the\n * stored \"新的待簽任務\" wording so a decided card no longer reads as pending.\n * The stored `body` is kept as historical context (which case / node).\n */\nconst RESOLVED_TITLE: Readonly<Record<NotificationResolution, string>> = {\n APPROVED: '簽核任務已同意',\n REJECTED: '簽核任務已拒絕',\n RETURNED: '簽核任務已退回',\n SUPERSEDED: '簽核任務已結束',\n TRANSFERRED: '簽核任務已轉派',\n};\n\nfunction resolveDisplayTitle(record: NotificationRecord): string {\n return record.resolution ? RESOLVED_TITLE[record.resolution] : record.title;\n}\n\n/**\n * Severity (icon colour) for a notification. Once resolved, it reflects the\n * outcome — green for approved, red for rejected, neutral otherwise — instead\n * of the original by-type colour.\n */\nfunction resolveSeverity(record: NotificationRecord): NotificationSeverity {\n if (record.resolution === 'APPROVED') return 'success';\n if (record.resolution === 'REJECTED') return 'error';\n if (record.resolution) return 'info';\n return toSeverity(record.type);\n}\n\n/**\n * Right-side notification drawer mounted at the root by `<Providers>`.\n * Opens / closes via `useNotificationDrawer()`, polls\n * `listNotifications()` for the current member, supports filter\n * (`all` / `read` / `unread`), per-row mark-read, per-row archive / unarchive,\n * bulk mark-all-read, and load-more pagination. Archived notifications are\n * hidden until the filter-bar menu turns \"顯示已封存\" on. Clicking a row with an\n * `instanceId` navigates to `/instances/<id>` via the host's router adapter.\n */\nexport function NotificationDrawer(): ReactElement | null {\n const router = useRouterAdapter();\n const routes = useBPMRoutes();\n const { member } = useAuth();\n const { close, isOpen } = useNotificationDrawer();\n const { refreshUnreadCount } = useNotificationUnread();\n const currentMemberId = member?.memberId ?? null;\n const [rows, setRows] = useState<readonly NotificationRecord[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [page, setPage] = useState(1);\n const [loading, setLoading] = useState(false);\n const [bulkLoading, setBulkLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [filter, setFilter] = useState<FilterValue>('all');\n const [includeArchived, setIncludeArchived] = useState(false);\n const [approveTarget, setApproveTarget] = useState<NotificationRecord | null>(\n null,\n );\n const [approveComment, setApproveComment] = useState('');\n const [rejectTarget, setRejectTarget] = useState<NotificationRecord | null>(\n null,\n );\n const [rejectReason, setRejectReason] = useState('');\n const [deciding, setDeciding] = useState(false);\n\n const trimmedApproveComment = approveComment.trim();\n const trimmedRejectReason = rejectReason.trim();\n\n const loadPage = useCallback(\n async (nextPage: number, append: boolean): Promise<boolean> => {\n if (!currentMemberId) return false;\n setLoading(true);\n setError(null);\n try {\n const result = await listNotifications({\n includeArchived,\n includeRead: true,\n page: nextPage,\n pageSize: PAGE_SIZE,\n recipientMemberId: currentMemberId,\n });\n setRows((current): readonly NotificationRecord[] =>\n append ? [...current, ...result.notifications] : result.notifications,\n );\n setTotalCount(result.totalCount);\n setPage(nextPage);\n await refreshUnreadCount();\n return true;\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n return false;\n } finally {\n setLoading(false);\n }\n },\n [currentMemberId, includeArchived, refreshUnreadCount],\n );\n\n useEffect((): void => {\n if (!isOpen || !currentMemberId) return;\n setError(null);\n setNotice(null);\n void loadPage(1, false);\n }, [isOpen, currentMemberId, loadPage]);\n\n const handleFilterChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>): void => {\n const next = event.target.value;\n if (next === 'all' || next === 'read' || next === 'unread') setFilter(next);\n },\n [],\n );\n\n const handleMarkAllRead = useCallback(async (): Promise<void> => {\n if (!currentMemberId || bulkLoading) return;\n setBulkLoading(true);\n setError(null);\n try {\n await markAllNotificationsRead({ recipientMemberId: currentMemberId });\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setBulkLoading(false);\n }\n }, [bulkLoading, currentMemberId, loadPage]);\n\n const handleLoadMore = useCallback((): void => {\n if (loading) return;\n void loadPage(page + 1, true);\n }, [loading, loadPage, page]);\n\n const handleMarkRead = useCallback(\n async (id: string): Promise<void> => {\n if (!currentMemberId) return;\n try {\n await markNotificationRead({ id, readerMemberId: currentMemberId });\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n }\n },\n [currentMemberId, loadPage],\n );\n\n const handleArchive = useCallback(\n async (record: NotificationRecord): Promise<void> => {\n if (!currentMemberId) return;\n setError(null);\n setNotice(null);\n try {\n // Name the notification the way the row does — `title` is the raw\n // stored title, which can differ from the rendered one.\n const label = resolveDisplayTitle(record);\n\n if (record.archivedAt) {\n await unarchiveNotifications({ ids: [record.id] });\n setNotice(`已將「${label}」移出封存。`);\n } else {\n await archiveNotifications({ ids: [record.id] });\n setNotice(`已封存「${label}」。`);\n }\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n }\n },\n [currentMemberId, loadPage],\n );\n\n const handleFilterAreaSelect = useCallback((option: DropdownOption): void => {\n if (option.id !== TOGGLE_ARCHIVED_OPTION_ID) return;\n setNotice(null);\n setIncludeArchived((current): boolean => !current);\n }, []);\n\n const handleOpenInstance = useCallback(\n async (record: NotificationRecord): Promise<void> => {\n if (!record.instanceId || !currentMemberId) return;\n try {\n if (record.status !== 'READ') {\n await markNotificationRead({\n id: record.id,\n readerMemberId: currentMemberId,\n });\n await refreshUnreadCount();\n }\n close();\n router.push(routes.caseDetail(record.instanceId));\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n }\n },\n [close, currentMemberId, refreshUnreadCount, router, routes],\n );\n\n const openApproveModal = useCallback((record: NotificationRecord): void => {\n setError(null);\n setApproveTarget(record);\n setApproveComment('');\n }, []);\n\n const closeApproveModal = useCallback((): void => {\n if (deciding) return;\n setApproveTarget(null);\n setApproveComment('');\n }, [deciding]);\n\n const handleApproveConfirm = useCallback(async (): Promise<void> => {\n const target = approveTarget;\n if (!target?.taskId || !currentMemberId || deciding) return;\n setDeciding(true);\n setError(null);\n setNotice(null);\n try {\n await decideTask({\n action: 'APPROVED',\n comment: trimmedApproveComment || null,\n decidedByMemberId: currentMemberId,\n taskId: target.taskId,\n });\n const refreshed = await loadPage(1, false);\n if (!refreshed) return;\n setApproveTarget(null);\n setApproveComment('');\n setNotice(`已同意「${target.title}」。`);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setDeciding(false);\n }\n }, [\n approveTarget,\n currentMemberId,\n deciding,\n loadPage,\n trimmedApproveComment,\n ]);\n\n const openRejectModal = useCallback((record: NotificationRecord): void => {\n setRejectTarget(record);\n setRejectReason('');\n }, []);\n\n const closeRejectModal = useCallback((): void => {\n setRejectTarget(null);\n setRejectReason('');\n }, []);\n\n const handleRejectConfirm = useCallback(async (): Promise<void> => {\n const target = rejectTarget;\n if (!target?.taskId || !currentMemberId || !trimmedRejectReason || deciding)\n return;\n setDeciding(true);\n setError(null);\n setNotice(null);\n try {\n await decideTask({\n action: 'REJECTED',\n comment: trimmedRejectReason,\n decidedByMemberId: currentMemberId,\n taskId: target.taskId,\n });\n setRejectTarget(null);\n setRejectReason('');\n setNotice(`已拒絕「${target.title}」。`);\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setDeciding(false);\n }\n }, [currentMemberId, deciding, loadPage, rejectTarget, trimmedRejectReason]);\n\n const handleBadgeSelect = useCallback(\n (record: NotificationRecord, option: DropdownOption): void => {\n const action = option.id as NotificationAction;\n if (action === 'approve') openApproveModal(record);\n else if (action === 'reject') openRejectModal(record);\n else if (action === 'view') void handleOpenInstance(record);\n else if (action === 'read') void handleMarkRead(record.id);\n else if (action === 'archive' || action === 'unarchive')\n void handleArchive(record);\n },\n [\n handleArchive,\n handleMarkRead,\n handleOpenInstance,\n openApproveModal,\n openRejectModal,\n ],\n );\n\n /**\n * Whether an event that reached the card wrapper actually happened inside the\n * card, and not on one of its action controls.\n *\n * Two escape hatches are needed. The `...` menu button (and its `<svg>` icon —\n * hence the `Element` check, SVG nodes are not HTMLElements) sits inside the\n * card and must open the dropdown rather than navigate. The dropdown itself\n * renders into a portal, and React replays events along the component tree\n * rather than the DOM tree, so picking \"封存\" also reaches this handler even\n * though the `<li role=\"option\">` lives outside the card's DOM — and it is not\n * a `<button>`, so the first guard alone misses it.\n */\n const isCardActivation = useCallback(\n (target: EventTarget | null, card: HTMLDivElement): boolean => {\n if (!(target instanceof Node) || !card.contains(target)) return false;\n\n return !(target instanceof Element && target.closest('button'));\n },\n [],\n );\n\n const handleCardActivate = useCallback(\n (record: NotificationRecord, event: ReactMouseEvent<HTMLDivElement>): void => {\n if (!isCardActivation(event.target, event.currentTarget)) return;\n void handleOpenInstance(record);\n },\n [handleOpenInstance, isCardActivation],\n );\n\n const handleCardKeyDown = useCallback(\n (\n record: NotificationRecord,\n event: ReactKeyboardEvent<HTMLDivElement>,\n ): void => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n if (!isCardActivation(event.target, event.currentTarget)) return;\n event.preventDefault();\n void handleOpenInstance(record);\n },\n [handleOpenInstance, isCardActivation],\n );\n\n const filteredRows = useMemo(\n (): readonly NotificationRecord[] =>\n rows.filter((row): boolean => {\n if (filter === 'all') return true;\n if (filter === 'read') return row.status === 'READ';\n return row.status !== 'READ';\n }),\n [filter, rows],\n );\n\n const groupedRows = useMemo(\n (): ReadonlyArray<readonly [TimeGroup, readonly NotificationRecord[]]> => {\n const now = new Date();\n const buckets = TIME_GROUP_ORDER.reduce<\n Record<TimeGroup, NotificationRecord[]>\n >(\n (accumulator, group) => {\n accumulator[group] = [];\n return accumulator;\n },\n { earlier: [], past7Days: [], today: [], yesterday: [] },\n );\n filteredRows.forEach((row): void => {\n buckets[resolveTimeGroup(row.createdAt, now)].push(row);\n });\n return TIME_GROUP_ORDER.map(\n (group) => [group, buckets[group]] as const,\n ).filter(([, items]) => items.length > 0);\n },\n [filteredRows],\n );\n\n const hasMore = rows.length < totalCount;\n\n if (!currentMemberId) return null;\n\n return (\n <>\n <Drawer\n bottomGhostActionDisabled={bulkLoading || loading}\n bottomGhostActionLoading={bulkLoading}\n bottomGhostActionText=\"全部標為已讀\"\n bottomOnGhostActionClick={(): void => {\n void handleMarkAllRead();\n }}\n bottomOnPrimaryActionClick={(): void => {\n handleLoadMore();\n }}\n bottomPrimaryActionDisabled={!hasMore || loading}\n bottomPrimaryActionLoading={loading && hasMore}\n bottomPrimaryActionText={hasMore ? '載入更多' : '已顯示全部'}\n contentKey={`${filter}:${includeArchived}:${rows.length}`}\n filterAreaAllRadioLabel=\"全部\"\n filterAreaOnRadioChange={handleFilterChange}\n filterAreaOnSelect={handleFilterAreaSelect}\n filterAreaOptions={[\n {\n id: TOGGLE_ARCHIVED_OPTION_ID,\n name: includeArchived ? '隱藏已封存' : '顯示已封存',\n },\n ]}\n filterAreaReadRadioLabel=\"已讀\"\n filterAreaShow\n filterAreaUnreadRadioLabel=\"未讀\"\n filterAreaValue={filter}\n headerTitle=\"通知中心\"\n isBottomDisplay\n isHeaderDisplay\n onClose={close}\n open={isOpen}\n size=\"medium\"\n >\n <div role=\"list\">\n {error ? (\n <p\n role=\"alert\"\n style={{\n color: 'var(--mzn-color-text-error, #d92d20)',\n padding: '12px 16px',\n }}\n >\n {error}\n </p>\n ) : null}\n {notice ? (\n <p\n role=\"status\"\n style={{\n color: 'var(--mzn-color-text-success, #079455)',\n padding: '12px 16px',\n }}\n >\n {notice}\n </p>\n ) : null}\n {groupedRows.length === 0 ? (\n <p\n style={{\n color: 'var(--mzn-color-text-secondary, #6b7280)',\n padding: '24px 16px',\n textAlign: 'center',\n }}\n >\n {loading ? '載入中…' : '目前沒有通知'}\n </p>\n ) : null}\n {groupedRows.map(([group, items]) => (\n <Fragment key={group}>\n {items.map((record, itemIndex) => {\n const openable = record.instanceId !== null;\n\n return (\n <div\n key={record.id}\n onClick={\n openable\n ? (event: ReactMouseEvent<HTMLDivElement>): void => {\n handleCardActivate(record, event);\n }\n : undefined\n }\n onKeyDown={\n openable\n ? (event: ReactKeyboardEvent<HTMLDivElement>): void => {\n handleCardKeyDown(record, event);\n }\n : undefined\n }\n role={openable ? 'button' : undefined}\n style={openable ? { cursor: 'pointer' } : undefined}\n tabIndex={openable ? 0 : undefined}\n >\n <NotificationCenter\n appendTips={record.archivedAt ? '已封存' : undefined}\n description={record.body}\n onBadgeSelect={(option: DropdownOption): void => {\n handleBadgeSelect(record, option);\n }}\n options={[...buildNotificationOptions(record)]}\n prependTips={\n itemIndex === 0 ? TIME_GROUP_LABEL[group] : undefined\n }\n reference={record.id}\n severity={resolveSeverity(record)}\n showBadge={record.status !== 'READ'}\n timeStamp={record.createdAt}\n title={resolveDisplayTitle(record)}\n type=\"drawer\"\n />\n </div>\n );\n })}\n </Fragment>\n ))}\n </div>\n </Drawer>\n <Modal\n cancelText=\"取消\"\n confirmText=\"送出同意\"\n loading={deciding}\n modalType=\"standard\"\n onCancel={closeApproveModal}\n onClose={closeApproveModal}\n onConfirm={(): void => {\n void handleApproveConfirm();\n }}\n open={approveTarget !== null}\n showModalFooter\n showModalHeader\n size=\"regular\"\n supportingText=\"可以留下同意說明,內容會記錄在簽核歷程;不填也可以直接送出。\"\n title=\"簽核意見\"\n >\n {error ? (\n <Typography color=\"text-error\" role=\"alert\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n <BPMFormField label=\"簽核意見\" name=\"notificationApproveComment\">\n <Textarea\n autoFocus\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void => {\n setApproveComment(event.target.value);\n }}\n placeholder=\"選填,例如補充核准條件或提醒事項\"\n rows={4}\n value={approveComment}\n />\n </BPMFormField>\n </Modal>\n <Modal\n cancelText=\"取消\"\n confirmButtonProps={{\n disabled: !trimmedRejectReason,\n variant: 'destructive-primary',\n }}\n confirmText=\"送出拒絕\"\n loading={deciding}\n modalStatusType=\"error\"\n modalType=\"standard\"\n onCancel={closeRejectModal}\n onClose={closeRejectModal}\n onConfirm={(): void => {\n void handleRejectConfirm();\n }}\n open={rejectTarget !== null}\n showModalFooter\n showModalHeader\n size=\"regular\"\n supportingText=\"拒絕案件時必須留下原因,供發起人與後續追蹤查看。\"\n title=\"拒絕原因\"\n >\n <Textarea\n autoFocus\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void => {\n setRejectReason(event.target.value);\n }}\n placeholder=\"請輸入拒絕原因\"\n rows={4}\n value={rejectReason}\n />\n </Modal>\n </>\n );\n}\n\nfunction toSeverity(type: NotificationType): NotificationSeverity {\n if (type === 'SLA_OVERDUE') return 'error';\n if (type === 'SLA_WARNING') return 'warning';\n if (type === 'INSTANCE_COMPLETED') return 'success';\n return 'info';\n}\n\nfunction resolveTimeGroup(value: string, now: Date): TimeGroup {\n const notificationDate = new Date(value);\n if (Number.isNaN(notificationDate.getTime())) return 'earlier';\n const nowStartOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const notificationStartOfDay = new Date(\n notificationDate.getFullYear(),\n notificationDate.getMonth(),\n notificationDate.getDate(),\n );\n if (notificationStartOfDay.getTime() === nowStartOfDay.getTime()) return 'today';\n const yesterdayStartOfDay = new Date(nowStartOfDay);\n yesterdayStartOfDay.setDate(yesterdayStartOfDay.getDate() - 1);\n if (notificationStartOfDay.getTime() === yesterdayStartOfDay.getTime())\n return 'yesterday';\n const diffInDays =\n (now.getTime() - notificationDate.getTime()) / (1000 * 60 * 60 * 24);\n if (diffInDays <= 7) return 'past7Days';\n return 'earlier';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n","'use client';\n\nimport type { ReactElement, ReactNode } from 'react';\nimport {\n CalendarConfigProviderMoment,\n CalendarLocale,\n} from '@mezzanine-ui/react/moment';\nimport { AuthProvider } from './auth-provider';\nimport {\n BPMLanguageProvider,\n DEFAULT_BPM_LANGUAGE,\n type BPMLanguage,\n} from './language-provider';\nimport { BPMOrganizationTimeZoneProvider } from './organization-time-zone-provider';\nimport { NotificationDrawer } from '../components/notification-drawer';\nimport { NotificationDrawerProvider } from './notification-drawer-provider';\nimport { NotificationUnreadProvider } from './notification-unread-provider';\n\ninterface ProvidersProps {\n readonly children: ReactNode;\n /** Override Mezzanine calendar locale. Defaults to `CalendarLocale.ZH_TW`. */\n readonly locale?: CalendarLocale;\n /**\n * Language BPM views write their messages in, such as publish-lint errors.\n * Defaults to `'zh-TW'`. Pass the user's choice as state to let them switch.\n * Independent of `locale`, which only formats calendar dates.\n */\n readonly language?: BPMLanguage;\n /**\n * IANA zone in which BPM views read \"today\" against organization effective\n * dates. Pass the same value given to `BPMRootModule` as\n * `organizationTimeZone`; leave it unset when the API does not set one.\n * Defaults to `DEFAULT_BPM_ORGANIZATION_TIME_ZONE` (`'Asia/Taipei'`); an\n * unknown zone falls back to it with a console warning.\n */\n readonly organizationTimeZone?: string;\n /**\n * Public paths that should not trigger redirect to `/login` when there\n * is no session. Forwarded to `<AuthProvider>`. Defaults to `['/login']`.\n */\n readonly publicPaths?: readonly string[];\n /** Where to send unauthenticated users. Defaults to `'/login'`. */\n readonly loginPath?: string;\n}\n\n/**\n * One-stop BPM admin provider stack. Wires:\n *\n * - Mezzanine UI calendar locale (moment-based, `ZH_TW` by default)\n * - `<BPMLanguageProvider>` (message language, `'zh-TW'` by default)\n * - `<BPMOrganizationTimeZoneProvider>` (organization \"today\" zone)\n * - `<AuthProvider>` (BPM session via REST `/auth/*`)\n * - `<NotificationUnreadProvider>` (polls unread count)\n * - `<NotificationDrawerProvider>` (controls drawer open/close state)\n * - `<NotificationDrawer />` mounted at the root so the host navigation\n * bell (`<BPMNotificationBellButton />` or any custom trigger calling\n * `useNotificationDrawer().open`) can open it.\n *\n * Consumer hosts wrap this **inside** a `<RouterAdapterProvider>` (provided\n * by the `pages/*` subpath shims when consuming via Next.js, or wired by\n * hand for other frameworks).\n */\nexport function Providers({\n children,\n locale = CalendarLocale.ZH_TW,\n language = DEFAULT_BPM_LANGUAGE,\n organizationTimeZone,\n publicPaths,\n loginPath,\n}: ProvidersProps): ReactElement {\n return (\n <CalendarConfigProviderMoment locale={locale}>\n <BPMLanguageProvider language={language}>\n <BPMOrganizationTimeZoneProvider timeZone={organizationTimeZone}>\n <AuthProvider publicPaths={publicPaths} loginPath={loginPath}>\n <NotificationUnreadProvider>\n <NotificationDrawerProvider>\n {children}\n <NotificationDrawer />\n </NotificationDrawerProvider>\n </NotificationUnreadProvider>\n </AuthProvider>\n </BPMOrganizationTimeZoneProvider>\n </BPMLanguageProvider>\n </CalendarConfigProviderMoment>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use client";const e=require("./rolldown-runtime-BocRIvOZ.cjs"),t=require("./auth-provider-4BeCw7cI.cjs"),n=require("./organization-time-zone-provider-B7CXQril.cjs"),r=require("./router-adapter--gYs13E8.cjs"),i=require("./routes-config-C1j_vFKo.cjs"),a=require("./bpm-form-field-Bc6k4ZEO.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/moment"),d=require("@mezzanine-ui/react/Drawer");d=e.t(d,1);let f=require("@mezzanine-ui/react/Modal");f=e.t(f,1);let p=require("@mezzanine-ui/react/NotificationCenter");p=e.t(p,1);let m=require("@mezzanine-ui/react/Textarea");m=e.t(m,1);var h=(0,o.createContext)(null);function g({children:e}){let[t,n]=(0,o.useState)(!1),r=(0,o.useCallback)(()=>{n(!0)},[]),i=(0,o.useCallback)(()=>{n(!1)},[]),a=(0,o.useCallback)(()=>{n(e=>!e)},[]),s=(0,o.useMemo)(()=>({close:i,isOpen:t,open:r,toggle:a}),[i,t,r,a]);return(0,c.jsx)(h.Provider,{value:s,children:e})}function _(){return(0,o.useContext)(h)||{close:()=>void 0,isOpen:!1,open:()=>void 0,toggle:()=>void 0}}var v=(0,o.createContext)(null);function y({children:e}){let{member:n}=t.n(),r=n?.memberId??null,[i,a]=(0,o.useState)(0),s=(0,o.useCallback)(async()=>{if(!r)return a(0),0;let e=await(0,l.readUnreadNotificationCount)(r);return a(e),e},[r]);(0,o.useEffect)(()=>{let e=!0;return(async()=>{try{let t=await s();e&&a(t)}catch{e&&a(0)}})(),()=>{e=!1}},[s]);let u=(0,o.useMemo)(()=>({refreshUnreadCount:s,unreadCount:i}),[s,i]);return(0,c.jsx)(v.Provider,{value:u,children:e})}function ee(){return(0,o.useContext)(v)||{refreshUnreadCount:async()=>0,unreadCount:0}}function te(e){let t=b(e),n=ne(e,t)!==!1,r=e.actionable&&!!e.instanceId&&(x(t,`hasFillableFields`)||x(t,`requireApprovalComment`));return[...e.actionable?[r?{id:`view`,name:`開啟案件`}:{id:`approve`,name:`同意`},...n?[{id:`reject`,name:`拒絕`}]:[]]:[],...e.instanceId&&!r?[{id:`view`,name:`查看案件`}]:[],...e.status===`READ`?[]:[{id:`read`,name:`標為已讀`}],e.archivedAt?{id:`unarchive`,name:`取消封存`}:{id:`archive`,name:`封存`}]}function b(e){try{let t=JSON.parse(e.payloadJson);return S(t)?t:null}catch{return null}}function ne(e,t){return t&&typeof t.allowReject==`boolean`?t.allowReject:e.allowReject}function x(e,t){return e!==null&&e[t]===!0}function S(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var re=[`today`,`yesterday`,`past7Days`,`earlier`],ie={earlier:`更早`,past7Days:`過去七天`,today:`今天`,yesterday:`昨天`},ae=50,oe=`toggleArchived`,C={APPROVED:`簽核任務已同意`,REJECTED:`簽核任務已拒絕`,RETURNED:`簽核任務已退回`,SUPERSEDED:`簽核任務已結束`,TRANSFERRED:`簽核任務已轉派`};function w(e){return e.resolution?C[e.resolution]:e.title}function se(e){return e.resolution===`APPROVED`?`success`:e.resolution===`REJECTED`?`error`:e.resolution?`info`:E(e.type)}function T(){let e=r.r(),n=i.r(),{member:u}=t.n(),{close:h,isOpen:g}=_(),{refreshUnreadCount:v}=ee(),y=u?.memberId??null,[b,ne]=(0,o.useState)([]),[x,S]=(0,o.useState)(0),[C,T]=(0,o.useState)(1),[E,O]=(0,o.useState)(!1),[k,A]=(0,o.useState)(!1),[j,M]=(0,o.useState)(null),[N,P]=(0,o.useState)(null),[F,le]=(0,o.useState)(`all`),[I,ue]=(0,o.useState)(!1),[L,R]=(0,o.useState)(null),[z,B]=(0,o.useState)(``),[V,H]=(0,o.useState)(null),[U,W]=(0,o.useState)(``),[G,K]=(0,o.useState)(!1),q=z.trim(),J=U.trim(),Y=(0,o.useCallback)(async(e,t)=>{if(!y)return!1;O(!0),M(null);try{let n=await(0,l.listNotifications)({includeArchived:I,includeRead:!0,page:e,pageSize:ae,recipientMemberId:y});return ne(e=>t?[...e,...n.notifications]:n.notifications),S(n.totalCount),T(e),await v(),!0}catch(e){return M(D(e)),!1}finally{O(!1)}},[y,I,v]);(0,o.useEffect)(()=>{g&&y&&(M(null),P(null),Y(1,!1))},[g,y,Y]);let de=(0,o.useCallback)(e=>{let t=e.target.value;(t===`all`||t===`read`||t===`unread`)&&le(t)},[]),fe=(0,o.useCallback)(async()=>{if(y&&!k){A(!0),M(null);try{await(0,l.markAllNotificationsRead)({recipientMemberId:y}),await Y(1,!1)}catch(e){M(D(e))}finally{A(!1)}}},[k,y,Y]),pe=(0,o.useCallback)(()=>{E||Y(C+1,!0)},[E,Y,C]),me=(0,o.useCallback)(async e=>{if(y)try{await(0,l.markNotificationRead)({id:e,readerMemberId:y}),await Y(1,!1)}catch(e){M(D(e))}},[y,Y]),he=(0,o.useCallback)(async e=>{if(y){M(null),P(null);try{let t=w(e);e.archivedAt?(await(0,l.unarchiveNotifications)({ids:[e.id]}),P(`已將「${t}」移出封存。`)):(await(0,l.archiveNotifications)({ids:[e.id]}),P(`已封存「${t}」。`)),await Y(1,!1)}catch(e){M(D(e))}}},[y,Y]),ge=(0,o.useCallback)(e=>{e.id===oe&&(P(null),ue(e=>!e))},[]),X=(0,o.useCallback)(async t=>{if(t.instanceId&&y)try{t.status!==`READ`&&(await(0,l.markNotificationRead)({id:t.id,readerMemberId:y}),await v()),h(),e.push(n.caseDetail(t.instanceId))}catch(e){M(D(e))}},[h,y,v,e,n]),_e=(0,o.useCallback)(e=>{M(null),R(e),B(``)},[]),ve=(0,o.useCallback)(()=>{G||(R(null),B(``))},[G]),ye=(0,o.useCallback)(async()=>{let e=L;if(e?.taskId&&y&&!G){K(!0),M(null),P(null);try{if(await(0,l.decideTask)({action:`APPROVED`,comment:q||null,decidedByMemberId:y,taskId:e.taskId}),!await Y(1,!1))return;R(null),B(``),P(`已同意「${e.title}」。`)}catch(e){M(D(e))}finally{K(!1)}}},[L,y,G,Y,q]),be=(0,o.useCallback)(e=>{H(e),W(``)},[]),xe=(0,o.useCallback)(()=>{H(null),W(``)},[]),Se=(0,o.useCallback)(async()=>{let e=V;if(e?.taskId&&y&&J&&!G){K(!0),M(null),P(null);try{await(0,l.decideTask)({action:`REJECTED`,comment:J,decidedByMemberId:y,taskId:e.taskId}),H(null),W(``),P(`已拒絕「${e.title}」。`),await Y(1,!1)}catch(e){M(D(e))}finally{K(!1)}}},[y,G,Y,V,J]),Ce=(0,o.useCallback)((e,t)=>{let n=t.id;n===`approve`?_e(e):n===`reject`?be(e):n===`view`?X(e):n===`read`?me(e.id):(n===`archive`||n===`unarchive`)&&he(e)},[he,me,X,_e,be]),Z=(0,o.useCallback)((e,t)=>!(e instanceof Node)||!t.contains(e)?!1:!(e instanceof Element&&e.closest(`button`)),[]),we=(0,o.useCallback)((e,t)=>{Z(t.target,t.currentTarget)&&X(e)},[X,Z]),Te=(0,o.useCallback)((e,t)=>{(t.key===`Enter`||t.key===` `)&&Z(t.target,t.currentTarget)&&(t.preventDefault(),X(e))},[X,Z]),Ee=(0,o.useMemo)(()=>b.filter(e=>F===`all`?!0:F===`read`?e.status===`READ`:e.status!==`READ`),[F,b]),Q=(0,o.useMemo)(()=>{let e=new Date,t=re.reduce((e,t)=>(e[t]=[],e),{earlier:[],past7Days:[],today:[],yesterday:[]});return Ee.forEach(n=>{t[ce(n.createdAt,e)].push(n)}),re.map(e=>[e,t[e]]).filter(([,e])=>e.length>0)},[Ee]),$=b.length<x;return y?(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(d.default,{bottomGhostActionDisabled:k||E,bottomGhostActionLoading:k,bottomGhostActionText:`全部標為已讀`,bottomOnGhostActionClick:()=>{fe()},bottomOnPrimaryActionClick:()=>{pe()},bottomPrimaryActionDisabled:!$||E,bottomPrimaryActionLoading:E&&$,bottomPrimaryActionText:$?`載入更多`:`已顯示全部`,contentKey:`${F}:${I}:${b.length}`,filterAreaAllRadioLabel:`全部`,filterAreaOnRadioChange:de,filterAreaOnSelect:ge,filterAreaOptions:[{id:oe,name:I?`隱藏已封存`:`顯示已封存`}],filterAreaReadRadioLabel:`已讀`,filterAreaShow:!0,filterAreaUnreadRadioLabel:`未讀`,filterAreaValue:F,headerTitle:`通知中心`,isBottomDisplay:!0,isHeaderDisplay:!0,onClose:h,open:g,size:`medium`,children:(0,c.jsxs)(`div`,{role:`list`,children:[j?(0,c.jsx)(`p`,{role:`alert`,style:{color:`var(--mzn-color-text-error, #d92d20)`,padding:`12px 16px`},children:j}):null,N?(0,c.jsx)(`p`,{role:`status`,style:{color:`var(--mzn-color-text-success, #079455)`,padding:`12px 16px`},children:N}):null,Q.length===0?(0,c.jsx)(`p`,{style:{color:`var(--mzn-color-text-secondary, #6b7280)`,padding:`24px 16px`,textAlign:`center`},children:E?`載入中…`:`目前沒有通知`}):null,Q.map(([e,t])=>(0,c.jsx)(o.Fragment,{children:t.map((t,n)=>{let r=t.instanceId!==null;return(0,c.jsx)(`div`,{onClick:r?e=>{we(t,e)}:void 0,onKeyDown:r?e=>{Te(t,e)}:void 0,role:r?`button`:void 0,style:r?{cursor:`pointer`}:void 0,tabIndex:r?0:void 0,children:(0,c.jsx)(p.default,{appendTips:t.archivedAt?`已封存`:void 0,description:t.body,onBadgeSelect:e=>{Ce(t,e)},options:[...te(t)],prependTips:n===0?ie[e]:void 0,reference:t.id,severity:se(t),showBadge:t.status!==`READ`,timeStamp:t.createdAt,title:w(t),type:`drawer`})},t.id)})},e))]})}),(0,c.jsxs)(f.default,{cancelText:`取消`,confirmText:`送出同意`,loading:G,modalType:`standard`,onCancel:ve,onClose:ve,onConfirm:()=>{ye()},open:L!==null,showModalFooter:!0,showModalHeader:!0,size:`regular`,supportingText:`可以留下同意說明,內容會記錄在簽核歷程;不填也可以直接送出。`,title:`簽核意見`,children:[j?(0,c.jsx)(s.Typography,{color:`text-error`,role:`alert`,variant:`body`,children:j}):null,(0,c.jsx)(a.t,{label:`簽核意見`,name:`notificationApproveComment`,children:(0,c.jsx)(m.default,{autoFocus:!0,onChange:e=>{B(e.target.value)},placeholder:`選填,例如補充核准條件或提醒事項`,rows:4,value:z})})]}),(0,c.jsx)(f.default,{cancelText:`取消`,confirmButtonProps:{disabled:!J,variant:`destructive-primary`},confirmText:`送出拒絕`,loading:G,modalStatusType:`error`,modalType:`standard`,onCancel:xe,onClose:xe,onConfirm:()=>{Se()},open:V!==null,showModalFooter:!0,showModalHeader:!0,size:`regular`,supportingText:`拒絕案件時必須留下原因,供發起人與後續追蹤查看。`,title:`拒絕原因`,children:(0,c.jsx)(m.default,{autoFocus:!0,onChange:e=>{W(e.target.value)},placeholder:`請輸入拒絕原因`,rows:4,value:U})})]}):null}function E(e){return e===`SLA_OVERDUE`?`error`:e===`SLA_WARNING`?`warning`:e===`INSTANCE_COMPLETED`?`success`:`info`}function ce(e,t){let n=new Date(e);if(Number.isNaN(n.getTime()))return`earlier`;let r=new Date(t.getFullYear(),t.getMonth(),t.getDate()),i=new Date(n.getFullYear(),n.getMonth(),n.getDate());if(i.getTime()===r.getTime())return`today`;let a=new Date(r);return a.setDate(a.getDate()-1),i.getTime()===a.getTime()?`yesterday`:(t.getTime()-n.getTime())/864e5<=7?`past7Days`:`earlier`}function D(e){return e instanceof Error?e.message:`發生未知錯誤`}function O({children:e,locale:r=u.CalendarLocale.ZH_TW,language:i=n.a,organizationTimeZone:a,publicPaths:o,loginPath:s}){return(0,c.jsx)(u.CalendarConfigProviderMoment,{locale:r,children:(0,c.jsx)(n.r,{language:i,children:(0,c.jsx)(n.t,{timeZone:a,children:(0,c.jsx)(t.t,{publicPaths:o,loginPath:s,children:(0,c.jsx)(y,{children:(0,c.jsxs)(g,{children:[e,(0,c.jsx)(T,{})]})})})})})})}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return ee}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return T}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return _}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return O}});
2
+ //# sourceMappingURL=providers-VUOh6i0i.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers-VUOh6i0i.cjs","names":["createContext","useState","useCallback","useMemo","useContext","createContext","useAuth","useState","useCallback","readUnreadNotificationCount","useMemo","useContext","useRouterAdapter","useBPMRoutes","useAuth","useState","useCallback","listNotifications","markAllNotificationsRead","markNotificationRead","unarchiveNotifications","archiveNotifications","decideTask","useMemo","_Fragment","Drawer","Fragment","NotificationCenter","Modal","Typography","BPMFormField","Textarea","CalendarLocale","DEFAULT_BPM_LANGUAGE","CalendarConfigProviderMoment","BPMLanguageProvider","BPMOrganizationTimeZoneProvider","AuthProvider","require_rolldown_runtime","require_auth_provider","require_organization_time_zone_provider","require_router_adapter","require_routes_config","require_bpm_form_field","react","_mezzanine_ui_react","react_jsx_runtime","_rytass_bpm_core_client_workflow","_mezzanine_ui_react_moment","_mezzanine_ui_react_Drawer","_mezzanine_ui_react_Modal","_mezzanine_ui_react_NotificationCenter","_mezzanine_ui_react_Textarea","NotificationDrawerContext","NotificationDrawerProvider","isOpen","setIsOpen","open","close","toggle","current","value","useNotificationDrawer","NotificationUnreadContext","NotificationUnreadProvider","currentMemberId","member","unreadCount","setUnreadCount","refreshUnreadCount","next","active","useNotificationUnread","buildNotificationOptions","payload","readNotificationPayload","record","canReject","readNotificationAllowReject","opensToFill","readPayloadFlag","isRecord","key","TIME_GROUP_ORDER","TIME_GROUP_LABEL","PAGE_SIZE","TOGGLE_ARCHIVED_OPTION_ID","RESOLVED_TITLE","resolveDisplayTitle","resolveSeverity","toSeverity","NotificationDrawer","router","routes","rows","setRows","totalCount","setTotalCount","page","setPage","loading","setLoading","bulkLoading","setBulkLoading","error","setError","notice","setNotice","filter","setFilter","includeArchived","setIncludeArchived","approveTarget","setApproveTarget","approveComment","setApproveComment","rejectTarget","setRejectTarget","rejectReason","setRejectReason","deciding","setDeciding","trimmedApproveComment","trimmedRejectReason","loadPage","result","nextPage","append","readErrorMessage","handleFilterChange","event","handleMarkAllRead","handleLoadMore","handleMarkRead","handleArchive","label","handleFilterAreaSelect","option","handleOpenInstance","openApproveModal","closeApproveModal","handleApproveConfirm","target","openRejectModal","closeRejectModal","handleRejectConfirm","handleBadgeSelect","action","isCardActivation","card","handleCardActivate","handleCardKeyDown","filteredRows","row","groupedRows","now","buckets","group","accumulator","resolveTimeGroup","items","hasMore","openable","itemIndex","type","notificationDate","nowStartOfDay","notificationStartOfDay","yesterdayStartOfDay","Providers","organizationTimeZone","children"],"sources":["../../src/lib/notification-drawer-provider.tsx","../../src/lib/notification-unread-provider.tsx","../../src/components/notification-drawer-options.ts","../../src/components/notification-drawer.tsx","../../src/lib/providers.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useState,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\ninterface NotificationDrawerContextValue {\n readonly close: () => void;\n readonly isOpen: boolean;\n readonly open: () => void;\n readonly toggle: () => void;\n}\n\nconst NotificationDrawerContext =\n createContext<NotificationDrawerContextValue | null>(null);\n\ninterface NotificationDrawerProviderProps {\n readonly children: ReactNode;\n}\n\n/**\n * Controls the open/closed state of the BPM notification drawer. Wraps\n * children with a context that `<NotificationDrawer />` reads to mount /\n * hide itself, and that the host navigation reads (via\n * `<BPMNotificationBellButton />` or `useNotificationDrawer().open`) to\n * open the drawer when the bell icon is clicked.\n *\n * When used outside this provider, the returned hook is a safe no-op so\n * components don't crash in test or storybook environments.\n */\nexport function NotificationDrawerProvider({\n children,\n}: NotificationDrawerProviderProps): ReactElement {\n const [isOpen, setIsOpen] = useState(false);\n\n const open = useCallback((): void => {\n setIsOpen(true);\n }, []);\n const close = useCallback((): void => {\n setIsOpen(false);\n }, []);\n const toggle = useCallback((): void => {\n setIsOpen((current) => !current);\n }, []);\n\n const value = useMemo<NotificationDrawerContextValue>(\n () => ({ close, isOpen, open, toggle }),\n [close, isOpen, open, toggle],\n );\n\n return (\n <NotificationDrawerContext.Provider value={value}>\n {children}\n </NotificationDrawerContext.Provider>\n );\n}\n\n/**\n * Read the BPM notification drawer's open state and control helpers.\n * Returns a no-op stub when used outside `<NotificationDrawerProvider>`.\n */\nexport function useNotificationDrawer(): NotificationDrawerContextValue {\n const context = useContext(NotificationDrawerContext);\n if (!context) {\n return {\n close: (): void => undefined,\n isOpen: false,\n open: (): void => undefined,\n toggle: (): void => undefined,\n };\n }\n return context;\n}\n","'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { readUnreadNotificationCount } from '@rytass/bpm-core-client/workflow';\nimport { useAuth } from './auth-provider';\n\ninterface NotificationUnreadContextValue {\n readonly refreshUnreadCount: () => Promise<number>;\n readonly unreadCount: number;\n}\n\nconst NotificationUnreadContext =\n createContext<NotificationUnreadContextValue | null>(null);\n\ninterface NotificationUnreadProviderProps {\n readonly children: ReactNode;\n}\n\n/**\n * Polls BPM for the current member's unread notification count via\n * `readUnreadNotificationCount` and exposes it through context for the\n * host navigation (`<BPMNotificationBellButton />` badge or any custom\n * trigger using `useNotificationUnread().unreadCount`) and the BPM\n * `<NotificationDrawer />` (header count). Refresh is triggered on\n * mount and whenever the auth member id changes; consumers can call\n * `refreshUnreadCount()` after acknowledging a notification.\n */\nexport function NotificationUnreadProvider({\n children,\n}: NotificationUnreadProviderProps): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [unreadCount, setUnreadCount] = useState(0);\n\n const refreshUnreadCount = useCallback(async (): Promise<number> => {\n if (!currentMemberId) {\n setUnreadCount(0);\n return 0;\n }\n const next = await readUnreadNotificationCount(currentMemberId);\n setUnreadCount(next);\n return next;\n }, [currentMemberId]);\n\n useEffect((): (() => void) => {\n let active = true;\n void (async () => {\n try {\n const next = await refreshUnreadCount();\n if (active) setUnreadCount(next);\n } catch {\n if (active) setUnreadCount(0);\n }\n })();\n return (): void => {\n active = false;\n };\n }, [refreshUnreadCount]);\n\n const value = useMemo<NotificationUnreadContextValue>(\n () => ({ refreshUnreadCount, unreadCount }),\n [refreshUnreadCount, unreadCount],\n );\n\n return (\n <NotificationUnreadContext.Provider value={value}>\n {children}\n </NotificationUnreadContext.Provider>\n );\n}\n\n/**\n * Read the current unread-notification count and a manual refresh helper.\n * Returns a zero/no-op stub when used outside\n * `<NotificationUnreadProvider>`.\n */\nexport function useNotificationUnread(): NotificationUnreadContextValue {\n const context = useContext(NotificationUnreadContext);\n if (!context) {\n return {\n refreshUnreadCount: async (): Promise<number> => 0,\n unreadCount: 0,\n };\n }\n return context;\n}\n","import type { DropdownOption } from '@mezzanine-ui/core/dropdown/dropdown';\nimport type { NotificationRecord } from '@rytass/bpm-core-client/workflow';\n\nexport type NotificationAction =\n 'approve' | 'archive' | 'reject' | 'unarchive' | 'view' | 'read';\n\n/**\n * Build the `...` dropdown options for one notification. Approve / reject are\n * offered only while the server still reports the notification as `actionable`\n * (an unresolved task assignment) — once the task is decided / cancelled the\n * backend flips `actionable` to false, so a stale \"同意\" can never appear.\n * A task that fills in form fields (`hasFillableFields`) cannot be approved\n * from here — the fill has to be entered on the case page — so its \"同意\"\n * becomes \"開啟案件\". So does a task at a node that requires an approval\n * comment (`requireApprovalComment`): the drawer has no comment dialog of its\n * own for that, the case page does.\n * \"查看案件\" needs an `instanceId`; \"標為已讀\" only shows while unread.\n * Archiving is independent of read state, so the entry flips between\n * \"封存\" / \"取消封存\" for every notification.\n */\nexport function buildNotificationOptions(\n record: NotificationRecord,\n): readonly DropdownOption[] {\n const payload = readNotificationPayload(record);\n const canReject = readNotificationAllowReject(record, payload) !== false;\n const opensToFill =\n record.actionable &&\n Boolean(record.instanceId) &&\n (readPayloadFlag(payload, 'hasFillableFields') ||\n readPayloadFlag(payload, 'requireApprovalComment'));\n\n return [\n ...(record.actionable\n ? ([\n opensToFill\n ? { id: 'view', name: '開啟案件' }\n : { id: 'approve', name: '同意' },\n ...(canReject ? [{ id: 'reject', name: '拒絕' }] : []),\n ] satisfies DropdownOption[])\n : []),\n ...(record.instanceId && !opensToFill\n ? ([{ id: 'view', name: '查看案件' }] satisfies DropdownOption[])\n : []),\n ...(record.status !== 'READ'\n ? ([{ id: 'read', name: '標為已讀' }] satisfies DropdownOption[])\n : []),\n record.archivedAt\n ? { id: 'unarchive', name: '取消封存' }\n : { id: 'archive', name: '封存' },\n ];\n}\n\n/**\n * The notification's `payloadJson`, parsed once. `null` when it is not a JSON\n * object; every reader below takes this one result.\n */\nfunction readNotificationPayload(\n record: NotificationRecord,\n): Readonly<Record<string, unknown>> | null {\n try {\n const payload: unknown = JSON.parse(record.payloadJson);\n\n return isRecord(payload) ? payload : null;\n } catch {\n return null;\n }\n}\n\nfunction readNotificationAllowReject(\n record: NotificationRecord,\n payload: Readonly<Record<string, unknown>> | null,\n): boolean | null {\n if (payload && typeof payload.allowReject === 'boolean') {\n return payload.allowReject;\n }\n\n return record.allowReject;\n}\n\n/**\n * `true` only when the payload sets the flag to `true`. A missing key, a\n * `false`, or an unparsable payload all read as `false`.\n *\n * `TASK_ASSIGNED` / `TASK_TRANSFERRED` payloads carry `hasFillableFields`, and\n * `requireApprovalComment: true` only when the node asks for a comment on\n * approval. Notifications created before those keys existed read as `false`.\n */\nfunction readPayloadFlag(\n payload: Readonly<Record<string, unknown>> | null,\n key: string,\n): boolean {\n return payload !== null && payload[key] === true;\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","'use client';\n\nimport {\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n type ChangeEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactElement,\n} from 'react';\nimport Drawer from '@mezzanine-ui/react/Drawer';\nimport Modal from '@mezzanine-ui/react/Modal';\nimport NotificationCenter from '@mezzanine-ui/react/NotificationCenter';\nimport Textarea from '@mezzanine-ui/react/Textarea';\nimport { Typography } from '@mezzanine-ui/react';\nimport type { NotificationSeverity } from '@mezzanine-ui/core/notification-center';\nimport type { DropdownOption } from '@mezzanine-ui/core/dropdown/dropdown';\nimport {\n archiveNotifications,\n decideTask,\n listNotifications,\n markAllNotificationsRead,\n markNotificationRead,\n unarchiveNotifications,\n type NotificationRecord,\n type NotificationResolution,\n type NotificationType,\n} from '@rytass/bpm-core-client/workflow';\nimport { useAuth } from '../lib/auth-provider';\nimport { useNotificationDrawer } from '../lib/notification-drawer-provider';\nimport { useNotificationUnread } from '../lib/notification-unread-provider';\nimport { useRouterAdapter } from '../lib/router-adapter';\nimport { useBPMRoutes } from '../lib/routes-config';\nimport { BPMFormField } from './bpm-form-field';\nimport {\n buildNotificationOptions,\n type NotificationAction,\n} from './notification-drawer-options';\n\ntype FilterValue = 'all' | 'read' | 'unread';\n\ntype TimeGroup = 'today' | 'yesterday' | 'past7Days' | 'earlier';\n\nconst TIME_GROUP_ORDER: readonly TimeGroup[] = [\n 'today',\n 'yesterday',\n 'past7Days',\n 'earlier',\n];\n\nconst TIME_GROUP_LABEL: Readonly<Record<TimeGroup, string>> = {\n earlier: '更早',\n past7Days: '過去七天',\n today: '今天',\n yesterday: '昨天',\n};\n\nconst PAGE_SIZE = 50;\n\n/** Identifier for the filter-bar dropdown entry toggling the archived view. */\nconst TOGGLE_ARCHIVED_OPTION_ID = 'toggleArchived';\n\n/**\n * Title shown for a resolved task-assignment notification, replacing the\n * stored \"新的待簽任務\" wording so a decided card no longer reads as pending.\n * The stored `body` is kept as historical context (which case / node).\n */\nconst RESOLVED_TITLE: Readonly<Record<NotificationResolution, string>> = {\n APPROVED: '簽核任務已同意',\n REJECTED: '簽核任務已拒絕',\n RETURNED: '簽核任務已退回',\n SUPERSEDED: '簽核任務已結束',\n TRANSFERRED: '簽核任務已轉派',\n};\n\nfunction resolveDisplayTitle(record: NotificationRecord): string {\n return record.resolution ? RESOLVED_TITLE[record.resolution] : record.title;\n}\n\n/**\n * Severity (icon colour) for a notification. Once resolved, it reflects the\n * outcome — green for approved, red for rejected, neutral otherwise — instead\n * of the original by-type colour.\n */\nfunction resolveSeverity(record: NotificationRecord): NotificationSeverity {\n if (record.resolution === 'APPROVED') return 'success';\n if (record.resolution === 'REJECTED') return 'error';\n if (record.resolution) return 'info';\n return toSeverity(record.type);\n}\n\n/**\n * Right-side notification drawer mounted at the root by `<Providers>`.\n * Opens / closes via `useNotificationDrawer()`, polls\n * `listNotifications()` for the current member, supports filter\n * (`all` / `read` / `unread`), per-row mark-read, per-row archive / unarchive,\n * bulk mark-all-read, and load-more pagination. Archived notifications are\n * hidden until the filter-bar menu turns \"顯示已封存\" on. Clicking a row with an\n * `instanceId` navigates to `/instances/<id>` via the host's router adapter.\n */\nexport function NotificationDrawer(): ReactElement | null {\n const router = useRouterAdapter();\n const routes = useBPMRoutes();\n const { member } = useAuth();\n const { close, isOpen } = useNotificationDrawer();\n const { refreshUnreadCount } = useNotificationUnread();\n const currentMemberId = member?.memberId ?? null;\n const [rows, setRows] = useState<readonly NotificationRecord[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [page, setPage] = useState(1);\n const [loading, setLoading] = useState(false);\n const [bulkLoading, setBulkLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [filter, setFilter] = useState<FilterValue>('all');\n const [includeArchived, setIncludeArchived] = useState(false);\n const [approveTarget, setApproveTarget] = useState<NotificationRecord | null>(\n null,\n );\n const [approveComment, setApproveComment] = useState('');\n const [rejectTarget, setRejectTarget] = useState<NotificationRecord | null>(\n null,\n );\n const [rejectReason, setRejectReason] = useState('');\n const [deciding, setDeciding] = useState(false);\n\n const trimmedApproveComment = approveComment.trim();\n const trimmedRejectReason = rejectReason.trim();\n\n const loadPage = useCallback(\n async (nextPage: number, append: boolean): Promise<boolean> => {\n if (!currentMemberId) return false;\n setLoading(true);\n setError(null);\n try {\n const result = await listNotifications({\n includeArchived,\n includeRead: true,\n page: nextPage,\n pageSize: PAGE_SIZE,\n recipientMemberId: currentMemberId,\n });\n setRows((current): readonly NotificationRecord[] =>\n append ? [...current, ...result.notifications] : result.notifications,\n );\n setTotalCount(result.totalCount);\n setPage(nextPage);\n await refreshUnreadCount();\n return true;\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n return false;\n } finally {\n setLoading(false);\n }\n },\n [currentMemberId, includeArchived, refreshUnreadCount],\n );\n\n useEffect((): void => {\n if (!isOpen || !currentMemberId) return;\n setError(null);\n setNotice(null);\n void loadPage(1, false);\n }, [isOpen, currentMemberId, loadPage]);\n\n const handleFilterChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>): void => {\n const next = event.target.value;\n if (next === 'all' || next === 'read' || next === 'unread') setFilter(next);\n },\n [],\n );\n\n const handleMarkAllRead = useCallback(async (): Promise<void> => {\n if (!currentMemberId || bulkLoading) return;\n setBulkLoading(true);\n setError(null);\n try {\n await markAllNotificationsRead({ recipientMemberId: currentMemberId });\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setBulkLoading(false);\n }\n }, [bulkLoading, currentMemberId, loadPage]);\n\n const handleLoadMore = useCallback((): void => {\n if (loading) return;\n void loadPage(page + 1, true);\n }, [loading, loadPage, page]);\n\n const handleMarkRead = useCallback(\n async (id: string): Promise<void> => {\n if (!currentMemberId) return;\n try {\n await markNotificationRead({ id, readerMemberId: currentMemberId });\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n }\n },\n [currentMemberId, loadPage],\n );\n\n const handleArchive = useCallback(\n async (record: NotificationRecord): Promise<void> => {\n if (!currentMemberId) return;\n setError(null);\n setNotice(null);\n try {\n // Name the notification the way the row does — `title` is the raw\n // stored title, which can differ from the rendered one.\n const label = resolveDisplayTitle(record);\n\n if (record.archivedAt) {\n await unarchiveNotifications({ ids: [record.id] });\n setNotice(`已將「${label}」移出封存。`);\n } else {\n await archiveNotifications({ ids: [record.id] });\n setNotice(`已封存「${label}」。`);\n }\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n }\n },\n [currentMemberId, loadPage],\n );\n\n const handleFilterAreaSelect = useCallback((option: DropdownOption): void => {\n if (option.id !== TOGGLE_ARCHIVED_OPTION_ID) return;\n setNotice(null);\n setIncludeArchived((current): boolean => !current);\n }, []);\n\n const handleOpenInstance = useCallback(\n async (record: NotificationRecord): Promise<void> => {\n if (!record.instanceId || !currentMemberId) return;\n try {\n if (record.status !== 'READ') {\n await markNotificationRead({\n id: record.id,\n readerMemberId: currentMemberId,\n });\n await refreshUnreadCount();\n }\n close();\n router.push(routes.caseDetail(record.instanceId));\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n }\n },\n [close, currentMemberId, refreshUnreadCount, router, routes],\n );\n\n const openApproveModal = useCallback((record: NotificationRecord): void => {\n setError(null);\n setApproveTarget(record);\n setApproveComment('');\n }, []);\n\n const closeApproveModal = useCallback((): void => {\n if (deciding) return;\n setApproveTarget(null);\n setApproveComment('');\n }, [deciding]);\n\n const handleApproveConfirm = useCallback(async (): Promise<void> => {\n const target = approveTarget;\n if (!target?.taskId || !currentMemberId || deciding) return;\n setDeciding(true);\n setError(null);\n setNotice(null);\n try {\n await decideTask({\n action: 'APPROVED',\n comment: trimmedApproveComment || null,\n decidedByMemberId: currentMemberId,\n taskId: target.taskId,\n });\n const refreshed = await loadPage(1, false);\n if (!refreshed) return;\n setApproveTarget(null);\n setApproveComment('');\n setNotice(`已同意「${target.title}」。`);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setDeciding(false);\n }\n }, [\n approveTarget,\n currentMemberId,\n deciding,\n loadPage,\n trimmedApproveComment,\n ]);\n\n const openRejectModal = useCallback((record: NotificationRecord): void => {\n setRejectTarget(record);\n setRejectReason('');\n }, []);\n\n const closeRejectModal = useCallback((): void => {\n setRejectTarget(null);\n setRejectReason('');\n }, []);\n\n const handleRejectConfirm = useCallback(async (): Promise<void> => {\n const target = rejectTarget;\n if (!target?.taskId || !currentMemberId || !trimmedRejectReason || deciding)\n return;\n setDeciding(true);\n setError(null);\n setNotice(null);\n try {\n await decideTask({\n action: 'REJECTED',\n comment: trimmedRejectReason,\n decidedByMemberId: currentMemberId,\n taskId: target.taskId,\n });\n setRejectTarget(null);\n setRejectReason('');\n setNotice(`已拒絕「${target.title}」。`);\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setDeciding(false);\n }\n }, [currentMemberId, deciding, loadPage, rejectTarget, trimmedRejectReason]);\n\n const handleBadgeSelect = useCallback(\n (record: NotificationRecord, option: DropdownOption): void => {\n const action = option.id as NotificationAction;\n if (action === 'approve') openApproveModal(record);\n else if (action === 'reject') openRejectModal(record);\n else if (action === 'view') void handleOpenInstance(record);\n else if (action === 'read') void handleMarkRead(record.id);\n else if (action === 'archive' || action === 'unarchive')\n void handleArchive(record);\n },\n [\n handleArchive,\n handleMarkRead,\n handleOpenInstance,\n openApproveModal,\n openRejectModal,\n ],\n );\n\n /**\n * Whether an event that reached the card wrapper actually happened inside the\n * card, and not on one of its action controls.\n *\n * Two escape hatches are needed. The `...` menu button (and its `<svg>` icon —\n * hence the `Element` check, SVG nodes are not HTMLElements) sits inside the\n * card and must open the dropdown rather than navigate. The dropdown itself\n * renders into a portal, and React replays events along the component tree\n * rather than the DOM tree, so picking \"封存\" also reaches this handler even\n * though the `<li role=\"option\">` lives outside the card's DOM — and it is not\n * a `<button>`, so the first guard alone misses it.\n */\n const isCardActivation = useCallback(\n (target: EventTarget | null, card: HTMLDivElement): boolean => {\n if (!(target instanceof Node) || !card.contains(target)) return false;\n\n return !(target instanceof Element && target.closest('button'));\n },\n [],\n );\n\n const handleCardActivate = useCallback(\n (record: NotificationRecord, event: ReactMouseEvent<HTMLDivElement>): void => {\n if (!isCardActivation(event.target, event.currentTarget)) return;\n void handleOpenInstance(record);\n },\n [handleOpenInstance, isCardActivation],\n );\n\n const handleCardKeyDown = useCallback(\n (\n record: NotificationRecord,\n event: ReactKeyboardEvent<HTMLDivElement>,\n ): void => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n if (!isCardActivation(event.target, event.currentTarget)) return;\n event.preventDefault();\n void handleOpenInstance(record);\n },\n [handleOpenInstance, isCardActivation],\n );\n\n const filteredRows = useMemo(\n (): readonly NotificationRecord[] =>\n rows.filter((row): boolean => {\n if (filter === 'all') return true;\n if (filter === 'read') return row.status === 'READ';\n return row.status !== 'READ';\n }),\n [filter, rows],\n );\n\n const groupedRows = useMemo(\n (): ReadonlyArray<readonly [TimeGroup, readonly NotificationRecord[]]> => {\n const now = new Date();\n const buckets = TIME_GROUP_ORDER.reduce<\n Record<TimeGroup, NotificationRecord[]>\n >(\n (accumulator, group) => {\n accumulator[group] = [];\n return accumulator;\n },\n { earlier: [], past7Days: [], today: [], yesterday: [] },\n );\n filteredRows.forEach((row): void => {\n buckets[resolveTimeGroup(row.createdAt, now)].push(row);\n });\n return TIME_GROUP_ORDER.map(\n (group) => [group, buckets[group]] as const,\n ).filter(([, items]) => items.length > 0);\n },\n [filteredRows],\n );\n\n const hasMore = rows.length < totalCount;\n\n if (!currentMemberId) return null;\n\n return (\n <>\n <Drawer\n bottomGhostActionDisabled={bulkLoading || loading}\n bottomGhostActionLoading={bulkLoading}\n bottomGhostActionText=\"全部標為已讀\"\n bottomOnGhostActionClick={(): void => {\n void handleMarkAllRead();\n }}\n bottomOnPrimaryActionClick={(): void => {\n handleLoadMore();\n }}\n bottomPrimaryActionDisabled={!hasMore || loading}\n bottomPrimaryActionLoading={loading && hasMore}\n bottomPrimaryActionText={hasMore ? '載入更多' : '已顯示全部'}\n contentKey={`${filter}:${includeArchived}:${rows.length}`}\n filterAreaAllRadioLabel=\"全部\"\n filterAreaOnRadioChange={handleFilterChange}\n filterAreaOnSelect={handleFilterAreaSelect}\n filterAreaOptions={[\n {\n id: TOGGLE_ARCHIVED_OPTION_ID,\n name: includeArchived ? '隱藏已封存' : '顯示已封存',\n },\n ]}\n filterAreaReadRadioLabel=\"已讀\"\n filterAreaShow\n filterAreaUnreadRadioLabel=\"未讀\"\n filterAreaValue={filter}\n headerTitle=\"通知中心\"\n isBottomDisplay\n isHeaderDisplay\n onClose={close}\n open={isOpen}\n size=\"medium\"\n >\n <div role=\"list\">\n {error ? (\n <p\n role=\"alert\"\n style={{\n color: 'var(--mzn-color-text-error, #d92d20)',\n padding: '12px 16px',\n }}\n >\n {error}\n </p>\n ) : null}\n {notice ? (\n <p\n role=\"status\"\n style={{\n color: 'var(--mzn-color-text-success, #079455)',\n padding: '12px 16px',\n }}\n >\n {notice}\n </p>\n ) : null}\n {groupedRows.length === 0 ? (\n <p\n style={{\n color: 'var(--mzn-color-text-secondary, #6b7280)',\n padding: '24px 16px',\n textAlign: 'center',\n }}\n >\n {loading ? '載入中…' : '目前沒有通知'}\n </p>\n ) : null}\n {groupedRows.map(([group, items]) => (\n <Fragment key={group}>\n {items.map((record, itemIndex) => {\n const openable = record.instanceId !== null;\n\n return (\n <div\n key={record.id}\n onClick={\n openable\n ? (event: ReactMouseEvent<HTMLDivElement>): void => {\n handleCardActivate(record, event);\n }\n : undefined\n }\n onKeyDown={\n openable\n ? (event: ReactKeyboardEvent<HTMLDivElement>): void => {\n handleCardKeyDown(record, event);\n }\n : undefined\n }\n role={openable ? 'button' : undefined}\n style={openable ? { cursor: 'pointer' } : undefined}\n tabIndex={openable ? 0 : undefined}\n >\n <NotificationCenter\n appendTips={record.archivedAt ? '已封存' : undefined}\n description={record.body}\n onBadgeSelect={(option: DropdownOption): void => {\n handleBadgeSelect(record, option);\n }}\n options={[...buildNotificationOptions(record)]}\n prependTips={\n itemIndex === 0 ? TIME_GROUP_LABEL[group] : undefined\n }\n reference={record.id}\n severity={resolveSeverity(record)}\n showBadge={record.status !== 'READ'}\n timeStamp={record.createdAt}\n title={resolveDisplayTitle(record)}\n type=\"drawer\"\n />\n </div>\n );\n })}\n </Fragment>\n ))}\n </div>\n </Drawer>\n <Modal\n cancelText=\"取消\"\n confirmText=\"送出同意\"\n loading={deciding}\n modalType=\"standard\"\n onCancel={closeApproveModal}\n onClose={closeApproveModal}\n onConfirm={(): void => {\n void handleApproveConfirm();\n }}\n open={approveTarget !== null}\n showModalFooter\n showModalHeader\n size=\"regular\"\n supportingText=\"可以留下同意說明,內容會記錄在簽核歷程;不填也可以直接送出。\"\n title=\"簽核意見\"\n >\n {error ? (\n <Typography color=\"text-error\" role=\"alert\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n <BPMFormField label=\"簽核意見\" name=\"notificationApproveComment\">\n <Textarea\n autoFocus\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void => {\n setApproveComment(event.target.value);\n }}\n placeholder=\"選填,例如補充核准條件或提醒事項\"\n rows={4}\n value={approveComment}\n />\n </BPMFormField>\n </Modal>\n <Modal\n cancelText=\"取消\"\n confirmButtonProps={{\n disabled: !trimmedRejectReason,\n variant: 'destructive-primary',\n }}\n confirmText=\"送出拒絕\"\n loading={deciding}\n modalStatusType=\"error\"\n modalType=\"standard\"\n onCancel={closeRejectModal}\n onClose={closeRejectModal}\n onConfirm={(): void => {\n void handleRejectConfirm();\n }}\n open={rejectTarget !== null}\n showModalFooter\n showModalHeader\n size=\"regular\"\n supportingText=\"拒絕案件時必須留下原因,供發起人與後續追蹤查看。\"\n title=\"拒絕原因\"\n >\n <Textarea\n autoFocus\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void => {\n setRejectReason(event.target.value);\n }}\n placeholder=\"請輸入拒絕原因\"\n rows={4}\n value={rejectReason}\n />\n </Modal>\n </>\n );\n}\n\nfunction toSeverity(type: NotificationType): NotificationSeverity {\n if (type === 'SLA_OVERDUE') return 'error';\n if (type === 'SLA_WARNING') return 'warning';\n if (type === 'INSTANCE_COMPLETED') return 'success';\n return 'info';\n}\n\nfunction resolveTimeGroup(value: string, now: Date): TimeGroup {\n const notificationDate = new Date(value);\n if (Number.isNaN(notificationDate.getTime())) return 'earlier';\n const nowStartOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const notificationStartOfDay = new Date(\n notificationDate.getFullYear(),\n notificationDate.getMonth(),\n notificationDate.getDate(),\n );\n if (notificationStartOfDay.getTime() === nowStartOfDay.getTime()) return 'today';\n const yesterdayStartOfDay = new Date(nowStartOfDay);\n yesterdayStartOfDay.setDate(yesterdayStartOfDay.getDate() - 1);\n if (notificationStartOfDay.getTime() === yesterdayStartOfDay.getTime())\n return 'yesterday';\n const diffInDays =\n (now.getTime() - notificationDate.getTime()) / (1000 * 60 * 60 * 24);\n if (diffInDays <= 7) return 'past7Days';\n return 'earlier';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n","'use client';\n\nimport type { ReactElement, ReactNode } from 'react';\nimport {\n CalendarConfigProviderMoment,\n CalendarLocale,\n} from '@mezzanine-ui/react/moment';\nimport { AuthProvider } from './auth-provider';\nimport {\n BPMLanguageProvider,\n DEFAULT_BPM_LANGUAGE,\n type BPMLanguage,\n} from './language-provider';\nimport { BPMOrganizationTimeZoneProvider } from './organization-time-zone-provider';\nimport { NotificationDrawer } from '../components/notification-drawer';\nimport { NotificationDrawerProvider } from './notification-drawer-provider';\nimport { NotificationUnreadProvider } from './notification-unread-provider';\n\ninterface ProvidersProps {\n readonly children: ReactNode;\n /** Override Mezzanine calendar locale. Defaults to `CalendarLocale.ZH_TW`. */\n readonly locale?: CalendarLocale;\n /**\n * Language BPM views write their messages in, such as publish-lint errors.\n * Defaults to `'zh-TW'`. Pass the user's choice as state to let them switch.\n * Independent of `locale`, which only formats calendar dates.\n */\n readonly language?: BPMLanguage;\n /**\n * IANA zone in which BPM views read \"today\" against organization effective\n * dates. Pass the same value given to `BPMRootModule` as\n * `organizationTimeZone`; leave it unset when the API does not set one.\n * Defaults to `DEFAULT_BPM_ORGANIZATION_TIME_ZONE` (`'Asia/Taipei'`); an\n * unknown zone falls back to it with a console warning.\n */\n readonly organizationTimeZone?: string;\n /**\n * Public paths that should not trigger redirect to `/login` when there\n * is no session. Forwarded to `<AuthProvider>`. Defaults to `['/login']`.\n */\n readonly publicPaths?: readonly string[];\n /** Where to send unauthenticated users. Defaults to `'/login'`. */\n readonly loginPath?: string;\n}\n\n/**\n * One-stop BPM admin provider stack. Wires:\n *\n * - Mezzanine UI calendar locale (moment-based, `ZH_TW` by default)\n * - `<BPMLanguageProvider>` (message language, `'zh-TW'` by default)\n * - `<BPMOrganizationTimeZoneProvider>` (organization \"today\" zone)\n * - `<AuthProvider>` (BPM session via REST `/auth/*`)\n * - `<NotificationUnreadProvider>` (polls unread count)\n * - `<NotificationDrawerProvider>` (controls drawer open/close state)\n * - `<NotificationDrawer />` mounted at the root so the host navigation\n * bell (`<BPMNotificationBellButton />` or any custom trigger calling\n * `useNotificationDrawer().open`) can open it.\n *\n * Consumer hosts wrap this **inside** a `<RouterAdapterProvider>` (provided\n * by the `pages/*` subpath shims when consuming via Next.js, or wired by\n * hand for other frameworks).\n */\nexport function Providers({\n children,\n locale = CalendarLocale.ZH_TW,\n language = DEFAULT_BPM_LANGUAGE,\n organizationTimeZone,\n publicPaths,\n loginPath,\n}: ProvidersProps): ReactElement {\n return (\n <CalendarConfigProviderMoment locale={locale}>\n <BPMLanguageProvider language={language}>\n <BPMOrganizationTimeZoneProvider timeZone={organizationTimeZone}>\n <AuthProvider publicPaths={publicPaths} loginPath={loginPath}>\n <NotificationUnreadProvider>\n <NotificationDrawerProvider>\n {children}\n <NotificationDrawer />\n </NotificationDrawerProvider>\n </NotificationUnreadProvider>\n </AuthProvider>\n </BPMOrganizationTimeZoneProvider>\n </BPMLanguageProvider>\n </CalendarConfigProviderMoment>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { ReactElement } from 'react';
2
+ import { FilterProps } from '@mezzanine-ui/react';
3
+ import { FilterableFieldPath } from '@rytass/bpm-core-shared';
4
+ import { FieldFilterDraft, FieldFilterDrafts } from './approval-instance-field-filters.cjs';
5
+ export type FieldFilterDraftChange = (path: string, draft: FieldFilterDraft) => void;
6
+ /**
7
+ * One list-page `Filter` per condition the template's filterable fields need.
8
+ * A number field yields two (lower and upper bound) because Mezzanine has no
9
+ * number range control. Returned as elements so the caller can place them
10
+ * directly inside `FilterLine`, which only accepts `Filter` children.
11
+ */
12
+ export declare function readFieldFilterElements(paths: readonly FilterableFieldPath[], drafts: FieldFilterDrafts, onDraftChange: FieldFilterDraftChange): readonly ReactElement<FilterProps>[];
@@ -0,0 +1,12 @@
1
+ import { ReactElement } from 'react';
2
+ import { FilterProps } from '@mezzanine-ui/react';
3
+ import { FilterableFieldPath } from '@rytass/bpm-core-shared';
4
+ import { FieldFilterDraft, FieldFilterDrafts } from './approval-instance-field-filters.js';
5
+ export type FieldFilterDraftChange = (path: string, draft: FieldFilterDraft) => void;
6
+ /**
7
+ * One list-page `Filter` per condition the template's filterable fields need.
8
+ * A number field yields two (lower and upper bound) because Mezzanine has no
9
+ * number range control. Returned as elements so the caller can place them
10
+ * directly inside `FilterLine`, which only accepts `Filter` children.
11
+ */
12
+ export declare function readFieldFilterElements(paths: readonly FilterableFieldPath[], drafts: FieldFilterDrafts, onDraftChange: FieldFilterDraftChange): readonly ReactElement<FilterProps>[];
@@ -0,0 +1,86 @@
1
+ import { ApprovalInstanceFieldFilter, ApprovalInstanceSort, ApprovalInstanceSortDirection, FilterableFieldPath, FormFieldOption } from '@rytass/bpm-core-shared';
2
+ /**
3
+ * Pure logic behind the list page's field filters (ADR 25 §7). Turns the
4
+ * filterable fields of a template's schema into controls, and the control
5
+ * values into `fieldFilters` and `sort` for `listApprovalInstancesPage`. The
6
+ * operator for each control is one `readFilterOperatorsForFieldType` allows.
7
+ */
8
+ /** Which control a filterable field gets on the list page. */
9
+ export type FieldFilterControl =
10
+ /** Text, textarea and dynamic (data-source) options: CONTAINS on a text input. */
11
+ {
12
+ readonly kind: 'contains';
13
+ }
14
+ /** Static options: IN on a multi-select. */
15
+ | {
16
+ readonly kind: 'option-in';
17
+ readonly options: readonly FormFieldOption[];
18
+ }
19
+ /** Member and org unit: IN on comma-separated ids (no picker yet). */
20
+ | {
21
+ readonly kind: 'id-in';
22
+ }
23
+ /** Number and money: RANGE with two number inputs. */
24
+ | {
25
+ readonly kind: 'number-range';
26
+ }
27
+ /** Date: RANGE with a date range picker. */
28
+ | {
29
+ readonly kind: 'date-range';
30
+ }
31
+ /** Datetime: RANGE with a datetime range picker. */
32
+ | {
33
+ readonly kind: 'datetime-range';
34
+ }
35
+ /** Boolean: EQUALS with a yes / no select. */
36
+ | {
37
+ readonly kind: 'boolean';
38
+ };
39
+ export declare function readFieldFilterControl(item: FilterableFieldPath): FieldFilterControl;
40
+ /**
41
+ * One field's entered condition. `from` and `to` hold canonical values
42
+ * (decimal string, `YYYY-MM-DD`, or ISO 8601) as the controls produce them.
43
+ */
44
+ export interface FieldFilterDraft {
45
+ readonly from: string;
46
+ readonly to: string;
47
+ /** CONTAINS text, or comma-separated ids for IN on member and org unit. */
48
+ readonly text: string;
49
+ /** Selected option values (IN), or `['true']` / `['false']` for boolean. */
50
+ readonly values: readonly string[];
51
+ }
52
+ export declare const EMPTY_FIELD_FILTER_DRAFT: FieldFilterDraft;
53
+ /** Drafts keyed by the field path, so a path can be looked up directly. */
54
+ export type FieldFilterDrafts = Readonly<Record<string, FieldFilterDraft>>;
55
+ /**
56
+ * Whether a number control's text is a decimal. Surrounding spaces and
57
+ * thousands separators (`1,000`) are accepted and dropped before sending.
58
+ */
59
+ export declare function isDecimalText(value: string): boolean;
60
+ /**
61
+ * The condition one draft asks for, or `null` when it is empty. Empty means
62
+ * no condition, so it must not reach the server as a filter.
63
+ */
64
+ export declare function toApprovalInstanceFieldFilter(item: FilterableFieldPath, draft: FieldFilterDraft): ApprovalInstanceFieldFilter | null;
65
+ /** Every non-empty condition, in schema order. */
66
+ export declare function buildApprovalInstanceFieldFilters(paths: readonly FilterableFieldPath[], drafts: FieldFilterDrafts): readonly ApprovalInstanceFieldFilter[];
67
+ /** The sort the user picked. An empty `field` keeps the server default. */
68
+ export interface SortDraft {
69
+ readonly direction: ApprovalInstanceSortDirection;
70
+ readonly field: string;
71
+ }
72
+ export declare const DEFAULT_SORT_DRAFT: SortDraft;
73
+ export interface SortFieldOption {
74
+ readonly id: string;
75
+ readonly label: string;
76
+ }
77
+ export declare const DEFAULT_SORT_OPTION: SortFieldOption;
78
+ /**
79
+ * Sort choices. Time fields are always offered. Form fields are offered only
80
+ * with a template selected, because the server needs a template to sort by a
81
+ * form field.
82
+ */
83
+ export declare function readSortFieldOptions(paths: readonly FilterableFieldPath[], templateSelected: boolean): readonly SortFieldOption[];
84
+ /** Whether a sort on `field` is valid for the current template selection. */
85
+ export declare function isSortFieldAvailable(field: string, paths: readonly FilterableFieldPath[], templateSelected: boolean): boolean;
86
+ export declare function toApprovalInstanceSort(draft: SortDraft): readonly ApprovalInstanceSort[] | undefined;
@@ -0,0 +1,86 @@
1
+ import { ApprovalInstanceFieldFilter, ApprovalInstanceSort, ApprovalInstanceSortDirection, FilterableFieldPath, FormFieldOption } from '@rytass/bpm-core-shared';
2
+ /**
3
+ * Pure logic behind the list page's field filters (ADR 25 §7). Turns the
4
+ * filterable fields of a template's schema into controls, and the control
5
+ * values into `fieldFilters` and `sort` for `listApprovalInstancesPage`. The
6
+ * operator for each control is one `readFilterOperatorsForFieldType` allows.
7
+ */
8
+ /** Which control a filterable field gets on the list page. */
9
+ export type FieldFilterControl =
10
+ /** Text, textarea and dynamic (data-source) options: CONTAINS on a text input. */
11
+ {
12
+ readonly kind: 'contains';
13
+ }
14
+ /** Static options: IN on a multi-select. */
15
+ | {
16
+ readonly kind: 'option-in';
17
+ readonly options: readonly FormFieldOption[];
18
+ }
19
+ /** Member and org unit: IN on comma-separated ids (no picker yet). */
20
+ | {
21
+ readonly kind: 'id-in';
22
+ }
23
+ /** Number and money: RANGE with two number inputs. */
24
+ | {
25
+ readonly kind: 'number-range';
26
+ }
27
+ /** Date: RANGE with a date range picker. */
28
+ | {
29
+ readonly kind: 'date-range';
30
+ }
31
+ /** Datetime: RANGE with a datetime range picker. */
32
+ | {
33
+ readonly kind: 'datetime-range';
34
+ }
35
+ /** Boolean: EQUALS with a yes / no select. */
36
+ | {
37
+ readonly kind: 'boolean';
38
+ };
39
+ export declare function readFieldFilterControl(item: FilterableFieldPath): FieldFilterControl;
40
+ /**
41
+ * One field's entered condition. `from` and `to` hold canonical values
42
+ * (decimal string, `YYYY-MM-DD`, or ISO 8601) as the controls produce them.
43
+ */
44
+ export interface FieldFilterDraft {
45
+ readonly from: string;
46
+ readonly to: string;
47
+ /** CONTAINS text, or comma-separated ids for IN on member and org unit. */
48
+ readonly text: string;
49
+ /** Selected option values (IN), or `['true']` / `['false']` for boolean. */
50
+ readonly values: readonly string[];
51
+ }
52
+ export declare const EMPTY_FIELD_FILTER_DRAFT: FieldFilterDraft;
53
+ /** Drafts keyed by the field path, so a path can be looked up directly. */
54
+ export type FieldFilterDrafts = Readonly<Record<string, FieldFilterDraft>>;
55
+ /**
56
+ * Whether a number control's text is a decimal. Surrounding spaces and
57
+ * thousands separators (`1,000`) are accepted and dropped before sending.
58
+ */
59
+ export declare function isDecimalText(value: string): boolean;
60
+ /**
61
+ * The condition one draft asks for, or `null` when it is empty. Empty means
62
+ * no condition, so it must not reach the server as a filter.
63
+ */
64
+ export declare function toApprovalInstanceFieldFilter(item: FilterableFieldPath, draft: FieldFilterDraft): ApprovalInstanceFieldFilter | null;
65
+ /** Every non-empty condition, in schema order. */
66
+ export declare function buildApprovalInstanceFieldFilters(paths: readonly FilterableFieldPath[], drafts: FieldFilterDrafts): readonly ApprovalInstanceFieldFilter[];
67
+ /** The sort the user picked. An empty `field` keeps the server default. */
68
+ export interface SortDraft {
69
+ readonly direction: ApprovalInstanceSortDirection;
70
+ readonly field: string;
71
+ }
72
+ export declare const DEFAULT_SORT_DRAFT: SortDraft;
73
+ export interface SortFieldOption {
74
+ readonly id: string;
75
+ readonly label: string;
76
+ }
77
+ export declare const DEFAULT_SORT_OPTION: SortFieldOption;
78
+ /**
79
+ * Sort choices. Time fields are always offered. Form fields are offered only
80
+ * with a template selected, because the server needs a template to sort by a
81
+ * form field.
82
+ */
83
+ export declare function readSortFieldOptions(paths: readonly FilterableFieldPath[], templateSelected: boolean): readonly SortFieldOption[];
84
+ /** Whether a sort on `field` is valid for the current template selection. */
85
+ export declare function isSortFieldAvailable(field: string, paths: readonly FilterableFieldPath[], templateSelected: boolean): boolean;
86
+ export declare function toApprovalInstanceSort(draft: SortDraft): readonly ApprovalInstanceSort[] | undefined;
@@ -13,5 +13,9 @@ export interface ApprovalInstanceListPageProps {
13
13
  * delegated). Caller picks the view + default state filter; the page renders
14
14
  * the standard BPM filter bar + paginated table and navigates to
15
15
  * `/instances/:id` on row action.
16
+ *
17
+ * With a template selected, the filter bar also carries one control per
18
+ * filterable field of that template's current form (ADR 25 §7), and the sort
19
+ * may use those fields. Without a template only time fields can sort.
16
20
  */
17
21
  export declare function ApprovalInstanceListPage({ defaultState, description, emptyMessage, searchPlaceholder, title, view, }: ApprovalInstanceListPageProps): ReactElement;
@@ -13,5 +13,9 @@ export interface ApprovalInstanceListPageProps {
13
13
  * delegated). Caller picks the view + default state filter; the page renders
14
14
  * the standard BPM filter bar + paginated table and navigates to
15
15
  * `/instances/:id` on row action.
16
+ *
17
+ * With a template selected, the filter bar also carries one control per
18
+ * filterable field of that template's current form (ADR 25 §7), and the sort
19
+ * may use those fields. Without a template only time fields can sort.
16
20
  */
17
21
  export declare function ApprovalInstanceListPage({ defaultState, description, emptyMessage, searchPlaceholder, title, view, }: ApprovalInstanceListPageProps): ReactElement;
@@ -0,0 +1,18 @@
1
+ import { DropdownOption } from '@mezzanine-ui/core/dropdown/dropdown';
2
+ import { NotificationRecord } from '@rytass/bpm-core-client/workflow';
3
+ export type NotificationAction = 'approve' | 'archive' | 'reject' | 'unarchive' | 'view' | 'read';
4
+ /**
5
+ * Build the `...` dropdown options for one notification. Approve / reject are
6
+ * offered only while the server still reports the notification as `actionable`
7
+ * (an unresolved task assignment) — once the task is decided / cancelled the
8
+ * backend flips `actionable` to false, so a stale "同意" can never appear.
9
+ * A task that fills in form fields (`hasFillableFields`) cannot be approved
10
+ * from here — the fill has to be entered on the case page — so its "同意"
11
+ * becomes "開啟案件". So does a task at a node that requires an approval
12
+ * comment (`requireApprovalComment`): the drawer has no comment dialog of its
13
+ * own for that, the case page does.
14
+ * "查看案件" needs an `instanceId`; "標為已讀" only shows while unread.
15
+ * Archiving is independent of read state, so the entry flips between
16
+ * "封存" / "取消封存" for every notification.
17
+ */
18
+ export declare function buildNotificationOptions(record: NotificationRecord): readonly DropdownOption[];
@@ -0,0 +1,18 @@
1
+ import { DropdownOption } from '@mezzanine-ui/core/dropdown/dropdown';
2
+ import { NotificationRecord } from '@rytass/bpm-core-client/workflow';
3
+ export type NotificationAction = 'approve' | 'archive' | 'reject' | 'unarchive' | 'view' | 'read';
4
+ /**
5
+ * Build the `...` dropdown options for one notification. Approve / reject are
6
+ * offered only while the server still reports the notification as `actionable`
7
+ * (an unresolved task assignment) — once the task is decided / cancelled the
8
+ * backend flips `actionable` to false, so a stale "同意" can never appear.
9
+ * A task that fills in form fields (`hasFillableFields`) cannot be approved
10
+ * from here — the fill has to be entered on the case page — so its "同意"
11
+ * becomes "開啟案件". So does a task at a node that requires an approval
12
+ * comment (`requireApprovalComment`): the drawer has no comment dialog of its
13
+ * own for that, the case page does.
14
+ * "查看案件" needs an `instanceId`; "標為已讀" only shows while unread.
15
+ * Archiving is independent of read state, so the entry flips between
16
+ * "封存" / "取消封存" for every notification.
17
+ */
18
+ export declare function buildNotificationOptions(record: NotificationRecord): readonly DropdownOption[];
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});require('./index.css');const e=require("./chunks/auth-provider-4BeCw7cI.cjs"),t=require("./chunks/format-date-time-D-cx5M8z.cjs"),n=require("./chunks/language-provider-Bdnx3Gxl.cjs"),r=require("./chunks/providers-D_KAgOOu.cjs"),i=require("./chunks/router-adapter--gYs13E8.cjs"),a=require("./chunks/routes-config-C1j_vFKo.cjs"),o=require("./chunks/use-bpm-member-DpNAQtNC.cjs"),s=require("./chunks/admin-pickers-BjbkBpSJ.cjs"),c=require("./chunks/approval-instance-list-page-fqvmkBlg.cjs"),l=require("./chunks/bpm-form-field-Bc6k4ZEO.cjs"),u=require("./chunks/dashboard-page-D7YJ3udk.cjs");let d=require("@mezzanine-ui/react"),f=require("react/jsx-runtime"),p=require("@mezzanine-ui/icons");function m(){let{logout:t}=e.n();return t}var h={root:`bpm_root_jl5S-`,badge:`bpm_badge_SIKJk`};function g({label:e=`通知中心`}={}){let{open:t}=r.o(),{unreadCount:n}=r.i(),i=n>0?`${e},${n} 則未讀`:e;return(0,f.jsxs)(`span`,{className:h.root,children:[(0,f.jsx)(d.NavigationIconButton,{"aria-label":i,icon:p.NotificationUnreadIcon,onClick:()=>{t()},title:e,type:`button`}),n>0?(0,f.jsx)(`span`,{className:h.badge,children:n>99?`99+`:n}):null]})}exports.ApprovalInstanceListPage=c.t,exports.AuthProvider=e.t,exports.BPMFormField=l.t,exports.BPMLanguageProvider=n.t,exports.BPMNotificationBellButton=g,exports.BPMRoutesProvider=a.t,exports.BPM_LANGUAGES=n.n,exports.DEFAULT_BPM_LANGUAGE=n.r,exports.DashboardPage=u.t,exports.MemberPicker=s.t,exports.NotificationDrawer=r.n,exports.NotificationDrawerProvider=r.a,exports.NotificationUnreadProvider=r.r,exports.OrgUnitPicker=s.n,exports.PositionPicker=s.r,exports.Providers=r.t,exports.RouterAdapterProvider=i.t,exports.createDefaultBPMRoutes=a.n,exports.defaultBrowserSearchParams=i.n,exports.formatDateTime=t.t,exports.isBPMAdminMember=o.t,exports.readMemberOption=s.i,exports.readOrgUnitOption=s.a,exports.readPositionOption=s.o,exports.useAuth=e.n,exports.useBPMLanguage=n.i,exports.useBPMLogout=m,exports.useBPMMember=o.n,exports.useBPMRoutes=a.r,exports.useNotificationDrawer=r.o,exports.useNotificationUnread=r.i,exports.useRouterAdapter=i.r;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});require('./index.css');const e=require("./chunks/auth-provider-4BeCw7cI.cjs"),t=require("./chunks/format-date-time-D-cx5M8z.cjs"),n=require("./chunks/organization-time-zone-provider-B7CXQril.cjs"),r=require("./chunks/providers-VUOh6i0i.cjs"),i=require("./chunks/router-adapter--gYs13E8.cjs"),a=require("./chunks/routes-config-C1j_vFKo.cjs"),o=require("./chunks/use-bpm-member-DpNAQtNC.cjs"),s=require("./chunks/admin-pickers-BjbkBpSJ.cjs"),c=require("./chunks/approval-instance-list-page-Cv5I1YPX.cjs"),l=require("./chunks/bpm-form-field-Bc6k4ZEO.cjs"),u=require("./chunks/dashboard-page-D7YJ3udk.cjs");let d=require("@mezzanine-ui/react"),f=require("react/jsx-runtime"),p=require("@mezzanine-ui/icons");function m(){let{logout:t}=e.n();return t}var h={root:`bpm_root_jl5S-`,badge:`bpm_badge_SIKJk`};function g({label:e=`通知中心`}={}){let{open:t}=r.o(),{unreadCount:n}=r.i(),i=n>0?`${e},${n} 則未讀`:e;return(0,f.jsxs)(`span`,{className:h.root,children:[(0,f.jsx)(d.NavigationIconButton,{"aria-label":i,icon:p.NotificationUnreadIcon,onClick:()=>{t()},title:e,type:`button`}),n>0?(0,f.jsx)(`span`,{className:h.badge,children:n>99?`99+`:n}):null]})}exports.ApprovalInstanceListPage=c.t,exports.AuthProvider=e.t,exports.BPMFormField=l.t,exports.BPMLanguageProvider=n.r,exports.BPMNotificationBellButton=g,exports.BPMOrganizationTimeZoneProvider=n.t,exports.BPMRoutesProvider=a.t,exports.BPM_LANGUAGES=n.i,exports.DEFAULT_BPM_LANGUAGE=n.a,exports.DashboardPage=u.t,exports.MemberPicker=s.t,exports.NotificationDrawer=r.n,exports.NotificationDrawerProvider=r.a,exports.NotificationUnreadProvider=r.r,exports.OrgUnitPicker=s.n,exports.PositionPicker=s.r,exports.Providers=r.t,exports.RouterAdapterProvider=i.t,exports.createDefaultBPMRoutes=a.n,exports.defaultBrowserSearchParams=i.n,exports.formatDateTime=t.t,exports.isBPMAdminMember=o.t,exports.readMemberOption=s.i,exports.readOrgUnitOption=s.a,exports.readPositionOption=s.o,exports.useAuth=e.n,exports.useBPMLanguage=n.o,exports.useBPMLogout=m,exports.useBPMMember=o.n,exports.useBPMOrganizationTimeZone=n.n,exports.useBPMRoutes=a.r,exports.useNotificationDrawer=r.o,exports.useNotificationUnread=r.i,exports.useRouterAdapter=i.r;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["useAuth","useNotificationDrawer","useNotificationUnread","styles","NavigationIconButton","NotificationUnreadIcon","require_auth_provider","require_format_date_time","require_language_provider","require_providers","require_router_adapter","require_routes_config","require_use_bpm_member","require_admin_pickers","require_approval_instance_list_page","require_bpm_form_field","require_dashboard_page","_mezzanine_ui_react","react_jsx_runtime","_mezzanine_ui_icons","useBPMLogout","logout","bpm_notification_bell_button_module_default","BPMNotificationBellButton","ariaLabel","unreadCount","label","open"],"sources":["../src/lib/use-bpm-logout.ts","../src/components/bpm-notification-bell-button.module.scss","../src/components/bpm-notification-bell-button.tsx"],"sourcesContent":["'use client';\n\nimport { useAuth } from './auth-provider';\n\n/**\n * Drop-in logout handler for host navigation menus. Wraps the BPM auth\n * context's logout flow — calls `logoutApi()` against the BPM REST\n * session endpoint, clears the in-memory member, then redirects to the\n * configured `loginPath`.\n *\n * Hosts mount this on their own logout buttons / menu items so they do\n * not need to touch `useAuth()` directly. To customize the post-logout\n * destination, override the `loginPath` prop on `<BPMNextProviders>` or\n * `<AuthProvider>`.\n */\nexport function useBPMLogout(): () => Promise<void> {\n const { logout } = useAuth();\n return logout;\n}\n",".root {\n display: inline-flex;\n position: relative;\n}\n\n.badge {\n align-items: center;\n background: #d92d20;\n border: 1px solid #fff;\n border-radius: 999px;\n color: #fff;\n display: inline-flex;\n font-size: 10px;\n font-weight: 600;\n height: 16px;\n justify-content: center;\n line-height: 1;\n min-width: 16px;\n padding: 0 4px;\n pointer-events: none;\n position: absolute;\n right: -4px;\n top: -4px;\n}\n","'use client';\n\nimport type { ReactElement } from 'react';\nimport { NavigationIconButton } from '@mezzanine-ui/react';\nimport { NotificationUnreadIcon } from '@mezzanine-ui/icons';\nimport { useNotificationDrawer } from '../lib/notification-drawer-provider';\nimport { useNotificationUnread } from '../lib/notification-unread-provider';\nimport styles from './bpm-notification-bell-button.module.scss';\n\nexport interface BPMNotificationBellButtonProps {\n /** Override the aria-label / tooltip. Defaults to `通知中心`. */\n readonly label?: string;\n}\n\n/**\n * Drop-in notification bell. Reads the unread count from\n * `<NotificationUnreadProvider>`, opens the BPM `<NotificationDrawer />`\n * mounted by `<Providers>` (or `<BPMNextProviders>`) on click, and\n * renders a small red badge with the unread count.\n *\n * Use this when the host navigation wants the BPM notification UX with\n * minimum wiring. Hosts that need a fully custom button can skip this\n * widget and consume `useNotificationDrawer()` + `useNotificationUnread()`\n * directly to wire their own trigger.\n *\n * Visual: uses Mezzanine `NavigationIconButton` so the bell aligns with\n * surrounding Mezzanine navigation chrome. The button is decoupled from\n * the `<Navigation>` container — it does not require a Mezzanine\n * navigation tree to render.\n */\nexport function BPMNotificationBellButton({\n label = '通知中心',\n}: BPMNotificationBellButtonProps = {}): ReactElement {\n const { open } = useNotificationDrawer();\n const { unreadCount } = useNotificationUnread();\n const ariaLabel = unreadCount > 0 ? `${label},${unreadCount} 則未讀` : label;\n return (\n <span className={styles.root}>\n <NavigationIconButton\n aria-label={ariaLabel}\n icon={NotificationUnreadIcon}\n onClick={(): void => {\n open();\n }}\n title={label}\n type=\"button\"\n />\n {unreadCount > 0 ? (\n <span className={styles.badge}>\n {unreadCount > 99 ? '99+' : unreadCount}\n </span>\n ) : null}\n </span>\n );\n}\n"],"mappings":"A,2uBAeA,SAAgBoB,GAAoC,CAClD,GAAM,CAAE,UAAWpB,EAAAA,EAAQ,EAC3B,OAAOqB,CACT,C,sDEYA,SAAgBE,EAA0B,CACxC,QAAQ,QAC0B,CAAC,EAAiB,CACpD,GAAM,CAAE,QAAStB,EAAAA,EAAsB,EACjC,CAAE,eAAgBC,EAAAA,EAAsB,EACxCsB,EAAYC,EAAc,EAAI,GAAGC,EAAM,GAAGD,EAAY,MAAQC,EACpE,OACE,EAAAR,EAAA,KAAA,CAAC,OAAD,CAAM,UAAWf,EAAO,KAAxB,SAAA,EACE,EAAAe,EAAA,IAAA,CAACd,EAAAA,qBAAD,CACE,aAAYoB,EACZ,KAAMnB,EAAAA,uBACN,YAAqB,CACnBsB,EAAK,CACP,EACA,MAAOD,EACP,KAAK,QACN,CAAA,EACAD,EAAc,GACb,EAAAP,EAAA,IAAA,CAAC,OAAD,CAAM,UAAWf,EAAO,MACrB,SAAAsB,EAAc,GAAK,MAAQA,CACxB,CAAA,EACJ,IACA,C,EAEV,C"}
1
+ {"version":3,"file":"index.cjs","names":["useAuth","useNotificationDrawer","useNotificationUnread","styles","NavigationIconButton","NotificationUnreadIcon","require_auth_provider","require_format_date_time","require_organization_time_zone_provider","require_providers","require_router_adapter","require_routes_config","require_use_bpm_member","require_admin_pickers","require_approval_instance_list_page","require_bpm_form_field","require_dashboard_page","_mezzanine_ui_react","react_jsx_runtime","_mezzanine_ui_icons","useBPMLogout","logout","bpm_notification_bell_button_module_default","BPMNotificationBellButton","ariaLabel","unreadCount","label","open"],"sources":["../src/lib/use-bpm-logout.ts","../src/components/bpm-notification-bell-button.module.scss","../src/components/bpm-notification-bell-button.tsx"],"sourcesContent":["'use client';\n\nimport { useAuth } from './auth-provider';\n\n/**\n * Drop-in logout handler for host navigation menus. Wraps the BPM auth\n * context's logout flow — calls `logoutApi()` against the BPM REST\n * session endpoint, clears the in-memory member, then redirects to the\n * configured `loginPath`.\n *\n * Hosts mount this on their own logout buttons / menu items so they do\n * not need to touch `useAuth()` directly. To customize the post-logout\n * destination, override the `loginPath` prop on `<BPMNextProviders>` or\n * `<AuthProvider>`.\n */\nexport function useBPMLogout(): () => Promise<void> {\n const { logout } = useAuth();\n return logout;\n}\n",".root {\n display: inline-flex;\n position: relative;\n}\n\n.badge {\n align-items: center;\n background: #d92d20;\n border: 1px solid #fff;\n border-radius: 999px;\n color: #fff;\n display: inline-flex;\n font-size: 10px;\n font-weight: 600;\n height: 16px;\n justify-content: center;\n line-height: 1;\n min-width: 16px;\n padding: 0 4px;\n pointer-events: none;\n position: absolute;\n right: -4px;\n top: -4px;\n}\n","'use client';\n\nimport type { ReactElement } from 'react';\nimport { NavigationIconButton } from '@mezzanine-ui/react';\nimport { NotificationUnreadIcon } from '@mezzanine-ui/icons';\nimport { useNotificationDrawer } from '../lib/notification-drawer-provider';\nimport { useNotificationUnread } from '../lib/notification-unread-provider';\nimport styles from './bpm-notification-bell-button.module.scss';\n\nexport interface BPMNotificationBellButtonProps {\n /** Override the aria-label / tooltip. Defaults to `通知中心`. */\n readonly label?: string;\n}\n\n/**\n * Drop-in notification bell. Reads the unread count from\n * `<NotificationUnreadProvider>`, opens the BPM `<NotificationDrawer />`\n * mounted by `<Providers>` (or `<BPMNextProviders>`) on click, and\n * renders a small red badge with the unread count.\n *\n * Use this when the host navigation wants the BPM notification UX with\n * minimum wiring. Hosts that need a fully custom button can skip this\n * widget and consume `useNotificationDrawer()` + `useNotificationUnread()`\n * directly to wire their own trigger.\n *\n * Visual: uses Mezzanine `NavigationIconButton` so the bell aligns with\n * surrounding Mezzanine navigation chrome. The button is decoupled from\n * the `<Navigation>` container — it does not require a Mezzanine\n * navigation tree to render.\n */\nexport function BPMNotificationBellButton({\n label = '通知中心',\n}: BPMNotificationBellButtonProps = {}): ReactElement {\n const { open } = useNotificationDrawer();\n const { unreadCount } = useNotificationUnread();\n const ariaLabel = unreadCount > 0 ? `${label},${unreadCount} 則未讀` : label;\n return (\n <span className={styles.root}>\n <NavigationIconButton\n aria-label={ariaLabel}\n icon={NotificationUnreadIcon}\n onClick={(): void => {\n open();\n }}\n title={label}\n type=\"button\"\n />\n {unreadCount > 0 ? (\n <span className={styles.badge}>\n {unreadCount > 99 ? '99+' : unreadCount}\n </span>\n ) : null}\n </span>\n );\n}\n"],"mappings":"A,yvBAeA,SAAgBoB,GAAoC,CAClD,GAAM,CAAE,UAAWpB,EAAAA,EAAQ,EAC3B,OAAOqB,CACT,C,sDEYA,SAAgBE,EAA0B,CACxC,QAAQ,QAC0B,CAAC,EAAiB,CACpD,GAAM,CAAE,QAAStB,EAAAA,EAAsB,EACjC,CAAE,eAAgBC,EAAAA,EAAsB,EACxCsB,EAAYC,EAAc,EAAI,GAAGC,EAAM,GAAGD,EAAY,MAAQC,EACpE,OACE,EAAAR,EAAA,KAAA,CAAC,OAAD,CAAM,UAAWf,EAAO,KAAxB,SAAA,EACE,EAAAe,EAAA,IAAA,CAACd,EAAAA,qBAAD,CACE,aAAYoB,EACZ,KAAMnB,EAAAA,uBACN,YAAqB,CACnBsB,EAAK,CACP,EACA,MAAOD,EACP,KAAK,QACN,CAAA,EACAD,EAAc,GACb,EAAAP,EAAA,IAAA,CAAC,OAAD,CAAM,UAAWf,EAAO,MACrB,SAAAsB,EAAc,GAAK,MAAQA,CACxB,CAAA,EACJ,IACA,C,EAEV,C"}
package/dist/index.d.cts CHANGED
@@ -3,6 +3,7 @@ export * from './lib/format-date-time.cjs';
3
3
  export * from './lib/language-provider.cjs';
4
4
  export * from './lib/notification-drawer-provider.cjs';
5
5
  export * from './lib/notification-unread-provider.cjs';
6
+ export * from './lib/organization-time-zone-provider.cjs';
6
7
  export * from './lib/providers.cjs';
7
8
  export * from './lib/router-adapter.cjs';
8
9
  export * from './lib/routes-config.cjs';
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './lib/format-date-time.js';
3
3
  export * from './lib/language-provider.js';
4
4
  export * from './lib/notification-drawer-provider.js';
5
5
  export * from './lib/notification-unread-provider.js';
6
+ export * from './lib/organization-time-zone-provider.js';
6
7
  export * from './lib/providers.js';
7
8
  export * from './lib/router-adapter.js';
8
9
  export * from './lib/routes-config.js';