@rytass/bpm-core-react 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +21 -0
  3. package/dist/chunks/FormBuilderView-B_KGPjlp.cjs +3 -0
  4. package/dist/chunks/FormBuilderView-B_KGPjlp.cjs.map +1 -0
  5. package/dist/chunks/FormBuilderView-D8DrQOXD.js +1090 -0
  6. package/dist/chunks/FormBuilderView-D8DrQOXD.js.map +1 -0
  7. package/dist/chunks/{approval-instance-list-page-C5ZKPHdA.cjs → approval-instance-list-page-BMUKxzcz.cjs} +2 -2
  8. package/dist/chunks/{approval-instance-list-page-C5ZKPHdA.cjs.map → approval-instance-list-page-BMUKxzcz.cjs.map} +1 -1
  9. package/dist/chunks/{approval-instance-list-page-BF2r5D2-.js → approval-instance-list-page-YZcGGDD8.js} +2 -2
  10. package/dist/chunks/{approval-instance-list-page-BF2r5D2-.js.map → approval-instance-list-page-YZcGGDD8.js.map} +1 -1
  11. package/dist/chunks/compose-PMrmi-LE.js +451 -0
  12. package/dist/chunks/compose-PMrmi-LE.js.map +1 -0
  13. package/dist/chunks/compose-ziVbRYdo.cjs +2 -0
  14. package/dist/chunks/compose-ziVbRYdo.cjs.map +1 -0
  15. package/dist/chunks/{dashboard-page-Ib8srCMy.js → dashboard-page-DJ9vOPga.js} +2 -2
  16. package/dist/chunks/{dashboard-page-Ib8srCMy.js.map → dashboard-page-DJ9vOPga.js.map} +1 -1
  17. package/dist/chunks/{dashboard-page-CddG1MnK.cjs → dashboard-page-DwHQY6Ki.cjs} +2 -2
  18. package/dist/chunks/{dashboard-page-CddG1MnK.cjs.map → dashboard-page-DwHQY6Ki.cjs.map} +1 -1
  19. package/dist/chunks/designer-DCn6_v4b.cjs +65 -0
  20. package/dist/chunks/designer-DCn6_v4b.cjs.map +1 -0
  21. package/dist/chunks/designer-mOMxJ0Py.js +2576 -0
  22. package/dist/chunks/designer-mOMxJ0Py.js.map +1 -0
  23. package/dist/chunks/detail-Cci9tnoC.cjs +2 -0
  24. package/dist/chunks/detail-Cci9tnoC.cjs.map +1 -0
  25. package/dist/chunks/detail-kyolfdBu.js +1957 -0
  26. package/dist/chunks/detail-kyolfdBu.js.map +1 -0
  27. package/dist/chunks/{routes-config-dxahImVe.js → routes-config-RBYQtUd0.js} +2 -3
  28. package/dist/chunks/routes-config-RBYQtUd0.js.map +1 -0
  29. package/dist/chunks/routes-config-fDVHmvXi.cjs +2 -0
  30. package/dist/chunks/routes-config-fDVHmvXi.cjs.map +1 -0
  31. package/dist/index.cjs +1 -1
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.js +268 -128
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/routes-config.d.ts +6 -4
  36. package/dist/next/index.cjs +1 -1
  37. package/dist/next/index.js +1 -1
  38. package/dist/next/workflow-chat-route.cjs +19 -0
  39. package/dist/next/workflow-chat-route.cjs.map +1 -0
  40. package/dist/next/workflow-chat-route.d.ts +17 -0
  41. package/dist/next/workflow-chat-route.js +31 -0
  42. package/dist/next/workflow-chat-route.js.map +1 -0
  43. package/dist/pages/instances/detail/index.cjs +1 -1
  44. package/dist/pages/instances/detail/index.js +1 -1
  45. package/dist/pages/templates/compose/index.cjs +2 -0
  46. package/dist/pages/templates/compose/index.cjs.map +1 -0
  47. package/dist/pages/templates/compose/index.d.ts +13 -0
  48. package/dist/pages/templates/compose/index.js +14 -0
  49. package/dist/pages/templates/compose/index.js.map +1 -0
  50. package/dist/pages/templates/designer/index.cjs +1 -1
  51. package/dist/pages/templates/designer/index.cjs.map +1 -1
  52. package/dist/pages/templates/designer/index.js +7 -2
  53. package/dist/pages/templates/designer/index.js.map +1 -1
  54. package/dist/pages/templates/index.cjs +1 -1
  55. package/dist/pages/templates/index.cjs.map +1 -1
  56. package/dist/pages/templates/index.js +3 -3
  57. package/dist/pages/templates/index.js.map +1 -1
  58. package/dist/views/cc/index.cjs +1 -1
  59. package/dist/views/cc/index.js +1 -1
  60. package/dist/views/dashboard/index.cjs +1 -1
  61. package/dist/views/dashboard/index.js +1 -1
  62. package/dist/views/forms/builder/FormBuilderView.d.ts +13 -4
  63. package/dist/views/forms/builder/index.cjs +1 -1
  64. package/dist/views/forms/builder/index.js +1 -1
  65. package/dist/views/forms/builder/json-code-editor.d.ts +1 -1
  66. package/dist/views/inbox/index.cjs +1 -1
  67. package/dist/views/inbox/index.js +1 -1
  68. package/dist/views/instances/detail/InstanceDetailView.d.ts +11 -1
  69. package/dist/views/instances/detail/index.cjs +1 -1
  70. package/dist/views/instances/detail/index.d.ts +5 -0
  71. package/dist/views/instances/detail/index.js +2 -2
  72. package/dist/views/instances/detail/sections/InstanceAttachmentsSection.d.ts +15 -0
  73. package/dist/views/instances/detail/sections/InstanceFormSection.d.ts +33 -0
  74. package/dist/views/instances/detail/sections/InstanceHistorySection.d.ts +29 -0
  75. package/dist/views/instances/detail/sections/InstanceSignaturesSection.d.ts +14 -0
  76. package/dist/views/instances/detail/sections/InstanceTasksSection.d.ts +51 -0
  77. package/dist/views/instances/detail/sections/container-helpers.d.ts +9 -0
  78. package/dist/views/instances/detail/sections/shared.d.ts +103 -0
  79. package/dist/views/instances/new/index.cjs +1 -1
  80. package/dist/views/instances/new/index.js +1 -1
  81. package/dist/views/search/index.cjs +1 -1
  82. package/dist/views/search/index.js +1 -1
  83. package/dist/views/sent/index.cjs +1 -1
  84. package/dist/views/sent/index.js +1 -1
  85. package/dist/views/templates/TemplatesView.d.ts +5 -0
  86. package/dist/views/templates/compose/TemplateComposeWizardView.d.ts +8 -0
  87. package/dist/views/templates/compose/index.cjs +1 -0
  88. package/dist/views/templates/compose/index.d.ts +2 -0
  89. package/dist/views/templates/compose/index.js +2 -0
  90. package/dist/views/templates/compose/steps/ComposeFormStep.d.ts +15 -0
  91. package/dist/views/templates/compose/steps/ComposeReviewStep.d.ts +12 -0
  92. package/dist/views/templates/compose/steps/ComposeWorkflowStep.d.ts +11 -0
  93. package/dist/views/templates/compose/use-template-compose-wizard.d.ts +46 -0
  94. package/dist/views/templates/designer/TemplateDesignerView.d.ts +60 -2
  95. package/dist/views/templates/designer/chrome-workflow-chat.d.ts +12 -0
  96. package/dist/views/templates/designer/index.cjs +1 -51
  97. package/dist/views/templates/designer/index.js +2 -2272
  98. package/dist/views/templates/designer/use-workflow-chat.d.ts +21 -0
  99. package/dist/views/templates/designer/use-workflow-designer-controller.d.ts +41 -0
  100. package/dist/views/templates/designer/workflow-chat-drawer.d.ts +16 -0
  101. package/dist/views/templates/index.cjs +2 -1
  102. package/dist/views/templates/index.cjs.map +1 -0
  103. package/dist/views/templates/index.js +265 -4
  104. package/dist/views/templates/index.js.map +1 -0
  105. package/dist/views/templates/versions/index.cjs +1 -1
  106. package/dist/views/templates/versions/index.cjs.map +1 -1
  107. package/dist/views/templates/versions/index.js +38 -42
  108. package/dist/views/templates/versions/index.js.map +1 -1
  109. package/package.json +22 -19
  110. package/dist/chunks/builder-BLVnnpnP.js +0 -1300
  111. package/dist/chunks/builder-BLVnnpnP.js.map +0 -1
  112. package/dist/chunks/builder-DVE9zIKH.cjs +0 -3
  113. package/dist/chunks/builder-DVE9zIKH.cjs.map +0 -1
  114. package/dist/chunks/detail-Dcr5mM8g.cjs +0 -2
  115. package/dist/chunks/detail-Dcr5mM8g.cjs.map +0 -1
  116. package/dist/chunks/detail-u9DdLhDW.js +0 -1518
  117. package/dist/chunks/detail-u9DdLhDW.js.map +0 -1
  118. package/dist/chunks/form-name-modal-C3OEvkCV.js +0 -64
  119. package/dist/chunks/form-name-modal-C3OEvkCV.js.map +0 -1
  120. package/dist/chunks/form-name-modal-uZCHbtRH.cjs +0 -2
  121. package/dist/chunks/form-name-modal-uZCHbtRH.cjs.map +0 -1
  122. package/dist/chunks/routes-config-2aKbWq2H.cjs +0 -2
  123. package/dist/chunks/routes-config-2aKbWq2H.cjs.map +0 -1
  124. package/dist/chunks/routes-config-dxahImVe.js.map +0 -1
  125. package/dist/chunks/templates-D44FSB46.js +0 -380
  126. package/dist/chunks/templates-D44FSB46.js.map +0 -1
  127. package/dist/chunks/templates-w96t83N-.cjs +0 -2
  128. package/dist/chunks/templates-w96t83N-.cjs.map +0 -1
  129. package/dist/pages/forms/builder/index.cjs +0 -2
  130. package/dist/pages/forms/builder/index.cjs.map +0 -1
  131. package/dist/pages/forms/builder/index.d.ts +0 -21
  132. package/dist/pages/forms/builder/index.js +0 -15
  133. package/dist/pages/forms/builder/index.js.map +0 -1
  134. package/dist/pages/forms/index.cjs +0 -2
  135. package/dist/pages/forms/index.cjs.map +0 -1
  136. package/dist/pages/forms/index.d.ts +0 -17
  137. package/dist/pages/forms/index.js +0 -14
  138. package/dist/pages/forms/index.js.map +0 -1
  139. package/dist/views/forms/FormsView.d.ts +0 -2
  140. package/dist/views/forms/form-name-modal.d.ts +0 -12
  141. package/dist/views/forms/index.cjs +0 -2
  142. package/dist/views/forms/index.cjs.map +0 -1
  143. package/dist/views/forms/index.d.ts +0 -2
  144. package/dist/views/forms/index.js +0 -186
  145. package/dist/views/forms/index.js.map +0 -1
  146. package/dist/views/templates/designer/index.cjs.map +0 -1
  147. package/dist/views/templates/designer/index.js.map +0 -1
  148. package/dist/views/templates/template-name-modal.d.ts +0 -22
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/lib/notification-drawer-provider.tsx","../src/lib/notification-unread-provider.tsx","../src/components/notification-drawer.tsx","../src/lib/providers.tsx","../src/lib/use-bpm-logout.ts","../src/lib/use-bpm-member.ts","../src/components/bpm-notification-bell-button.module.scss","../src/components/bpm-notification-bell-button.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","'use client';\n\nimport {\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n type ChangeEvent,\n type ReactElement,\n} from 'react';\nimport Drawer from '@mezzanine-ui/react/Drawer';\nimport NotificationCenter from '@mezzanine-ui/react/NotificationCenter';\nimport type { NotificationSeverity } from '@mezzanine-ui/core/notification-center';\nimport {\n listNotifications,\n markAllNotificationsRead,\n markNotificationRead,\n type NotificationRecord,\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';\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/**\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, bulk mark-all-read, and\n * load-more pagination. Clicking a row with an `instanceId` navigates to\n * `/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 [filter, setFilter] = useState<FilterValue>('all');\n\n const loadPage = useCallback(\n async (nextPage: number, append: boolean): Promise<void> => {\n if (!currentMemberId) return;\n setLoading(true);\n setError(null);\n try {\n const result = await listNotifications({\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 } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setLoading(false);\n }\n },\n [currentMemberId, refreshUnreadCount],\n );\n\n useEffect((): void => {\n if (!isOpen || !currentMemberId) return;\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 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 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 <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}:${rows.length}`}\n filterAreaAllRadioLabel=\"全部\"\n filterAreaOnRadioChange={handleFilterChange}\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 {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], groupIndex) => (\n <Fragment key={group}>\n {items.map((record, itemIndex) => (\n <NotificationCenter\n appendTips={\n groupIndex === groupedRows.length - 1 &&\n itemIndex === items.length - 1 &&\n !hasMore\n ? '已顯示全部通知'\n : undefined\n }\n cancelButtonText={\n record.status !== 'READ' ? '標為已讀' : undefined\n }\n description={record.body}\n key={record.id}\n onCancel={\n record.status !== 'READ'\n ? (): void => {\n void handleMarkRead(record.id);\n }\n : undefined\n }\n onConfirm={\n record.instanceId\n ? (): void => {\n void handleOpenInstance(record);\n }\n : undefined\n }\n confirmButtonText={record.instanceId ? '查看案件' : undefined}\n prependTips={itemIndex === 0 ? TIME_GROUP_LABEL[group] : undefined}\n reference={record.id}\n severity={toSeverity(record.type)}\n showBadge={record.status !== 'READ'}\n timeStamp={record.createdAt}\n title={record.title}\n type=\"drawer\"\n />\n ))}\n </Fragment>\n ))}\n </div>\n </Drawer>\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 { 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 * 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 * - `<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 publicPaths,\n loginPath,\n}: ProvidersProps): ReactElement {\n return (\n <CalendarConfigProviderMoment locale={locale}>\n <AuthProvider publicPaths={publicPaths} loginPath={loginPath}>\n <NotificationUnreadProvider>\n <NotificationDrawerProvider>\n {children}\n <NotificationDrawer />\n </NotificationDrawerProvider>\n </NotificationUnreadProvider>\n </AuthProvider>\n </CalendarConfigProviderMoment>\n );\n}\n","'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","'use client';\n\nimport type { ApiMember } from '@rytass/bpm-core-client';\nimport { useAuth } from './auth-provider';\n\n/**\n * Read the currently authenticated BPM member. Returns `null` when there\n * is no active session — typically only seen on the login page or during\n * the brief loading window before `<AuthProvider>` resolves the cookie.\n *\n * Convenience alias for `useAuth().member` aimed at host navigations\n * (avatar, display name, role-based menu visibility) that should not\n * depend on the broader `useAuth()` surface.\n */\nexport function useBPMMember(): ApiMember | null {\n const { member } = useAuth();\n return member;\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":";;;;;;;;;;;;;;;;;;AAmBA,IAAM,IACJ,EAAqD,IAAI;AAgB3D,SAAgB,EAA2B,EACzC,eACgD;CAChD,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAK,GAEpC,IAAO,QAAwB;EACnC,EAAU,EAAI;CAChB,GAAG,CAAC,CAAC,GACC,IAAQ,QAAwB;EACpC,EAAU,EAAK;CACjB,GAAG,CAAC,CAAC,GACC,IAAS,QAAwB;EACrC,GAAW,MAAY,CAAC,CAAO;CACjC,GAAG,CAAC,CAAC,GAEC,IAAQ,SACL;EAAE;EAAO;EAAQ;EAAM;CAAO,IACrC;EAAC;EAAO;EAAQ;EAAM;CAAM,CAC9B;CAEA,OACE,kBAAC,EAA0B,UAA3B;EAA2C;EACxC;CACiC,CAAA;AAExC;AAMA,SAAgB,IAAwD;CAUtE,OATgB,EAAW,CACtB,KACI;EACL,aAAmB,KAAA;EACnB,QAAQ;EACR,YAAkB,KAAA;EAClB,cAAoB,KAAA;CACtB;AAGJ;;;AC1DA,IAAM,IACJ,EAAqD,IAAI;AAe3D,SAAgB,EAA2B,EACzC,eACgD;CAChD,IAAM,EAAE,cAAW,EAAQ,GACrB,IAAkB,GAAQ,YAAY,MACtC,CAAC,GAAa,KAAkB,EAAS,CAAC,GAE1C,IAAqB,EAAY,YAA6B;EAClE,IAAI,CAAC,GAEH,OADA,EAAe,CAAC,GACT;EAET,IAAM,IAAO,MAAM,EAA4B,CAAe;EAE9D,OADA,EAAe,CAAI,GACZ;CACT,GAAG,CAAC,CAAe,CAAC;CAEpB,QAA8B;EAC5B,IAAI,IAAS;EASb,QARM,YAAY;GAChB,IAAI;IACF,IAAM,IAAO,MAAM,EAAmB;IACtC,AAAI,KAAQ,EAAe,CAAI;GACjC,QAAQ;IACN,AAAI,KAAQ,EAAe,CAAC;GAC9B;EACF,GAAG,SACgB;GACjB,IAAS;EACX;CACF,GAAG,CAAC,CAAkB,CAAC;CAEvB,IAAM,IAAQ,SACL;EAAE;EAAoB;CAAY,IACzC,CAAC,GAAoB,CAAW,CAClC;CAEA,OACE,kBAAC,EAA0B,UAA3B;EAA2C;EACxC;CACiC,CAAA;AAExC;AAOA,SAAgB,IAAwD;CAQtE,OAPgB,EAAW,CACtB,KACI;EACL,oBAAoB,YAA6B;EACjD,aAAa;CACf;AAGJ;;;AC/DA,IAAM,IAAyC;CAC7C;CACA;CACA;CACA;AACF,GAEM,IAAwD;CAC5D,SAAS;CACT,WAAW;CACX,OAAO;CACP,WAAW;AACb,GAEM,IAAY;AAUlB,SAAgB,IAA0C;CACxD,IAAM,IAAS,EAAiB,GAC1B,IAAS,EAAa,GACtB,EAAE,cAAW,EAAQ,GACrB,EAAE,UAAO,cAAW,EAAsB,GAC1C,EAAE,0BAAuB,EAAsB,GAC/C,IAAkB,GAAQ,YAAY,MACtC,CAAC,GAAM,KAAW,EAAwC,CAAC,CAAC,GAC5D,CAAC,GAAY,KAAiB,EAAS,CAAC,GACxC,CAAC,GAAM,KAAW,EAAS,CAAC,GAC5B,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,CAAC,GAAO,KAAY,EAAwB,IAAI,GAChD,CAAC,GAAQ,KAAa,EAAsB,KAAK,GAEjD,IAAW,EACf,OAAO,GAAkB,MAAmC;EACrD,OAEL;GADA,EAAW,EAAI,GACf,EAAS,IAAI;GACb,IAAI;IACF,IAAM,IAAS,MAAM,EAAkB;KACrC,aAAa;KACb,MAAM;KACN,UAAU;KACV,mBAAmB;IACrB,CAAC;IAMD,AALA,GAAS,MACP,IAAS,CAAC,GAAG,GAAS,GAAG,EAAO,aAAa,IAAI,EAAO,aAC1D,GACA,EAAc,EAAO,UAAU,GAC/B,EAAQ,CAAQ,GAChB,MAAM,EAAmB;GAC3B,SAAS,GAAY;IACnB,EAAS,EAAiB,CAAC,CAAC;GAC9B,UAAU;IACR,EAAW,EAAK;GAClB;EAlBa;CAmBf,GACA,CAAC,GAAiB,CAAkB,CACtC;CAEA,QAAsB;EAChB,CAAC,KAAU,CAAC,KAChB,EAAc,GAAG,EAAK;CACxB,GAAG;EAAC;EAAQ;EAAiB;CAAQ,CAAC;CAEtC,IAAM,IAAqB,GACxB,MAA+C;EAC9C,IAAM,IAAO,EAAM,OAAO;EAC1B,CAAI,MAAS,SAAS,MAAS,UAAU,MAAS,aAAU,EAAU,CAAI;CAC5E,GACA,CAAC,CACH,GAEM,IAAoB,EAAY,YAA2B;EAC3D,OAAC,KAAmB,IAExB;GADA,EAAe,EAAI,GACnB,EAAS,IAAI;GACb,IAAI;IAEF,AADA,MAAM,EAAyB,EAAE,mBAAmB,EAAgB,CAAC,GACrE,MAAM,EAAS,GAAG,EAAK;GACzB,SAAS,GAAY;IACnB,EAAS,EAAiB,CAAC,CAAC;GAC9B,UAAU;IACR,EAAe,EAAK;GACtB;EARa;CASf,GAAG;EAAC;EAAa;EAAiB;CAAQ,CAAC,GAErC,IAAiB,QAAwB;EACzC,KACJ,EAAc,IAAO,GAAG,EAAI;CAC9B,GAAG;EAAC;EAAS;EAAU;CAAI,CAAC,GAEtB,IAAiB,EACrB,OAAO,MAA8B;EAC9B,OACL,IAAI;GAEF,AADA,MAAM,EAAqB;IAAE;IAAI,gBAAgB;GAAgB,CAAC,GAClE,MAAM,EAAS,GAAG,EAAK;EACzB,SAAS,GAAY;GACnB,EAAS,EAAiB,CAAC,CAAC;EAC9B;CACF,GACA,CAAC,GAAiB,CAAQ,CAC5B,GAEM,IAAqB,EACzB,OAAO,MAA8C;EAC/C,OAAC,EAAO,cAAc,CAAC,IAC3B,IAAI;GASF,AARI,EAAO,WAAW,WACpB,MAAM,EAAqB;IACzB,IAAI,EAAO;IACX,gBAAgB;GAClB,CAAC,GACD,MAAM,EAAmB,IAE3B,EAAM,GACN,EAAO,KAAK,EAAO,WAAW,EAAO,UAAU,CAAC;EAClD,SAAS,GAAY;GACnB,EAAS,EAAiB,CAAC,CAAC;EAC9B;CACF,GACA;EAAC;EAAO;EAAiB;EAAoB;EAAQ;CAAM,CAC7D,GAEM,IAAe,QAEjB,EAAK,QAAQ,MACP,MAAW,QAAc,KACzB,MAAW,SAAe,EAAI,WAAW,SACtC,EAAI,WAAW,MACvB,GACH,CAAC,GAAQ,CAAI,CACf,GAEM,IAAc,QACwD;EACxE,IAAM,oBAAM,IAAI,KAAK,GACf,IAAU,EAAiB,QAG9B,GAAa,OACZ,EAAY,KAAS,CAAC,GACf,IAET;GAAE,SAAS,CAAC;GAAG,WAAW,CAAC;GAAG,OAAO,CAAC;GAAG,WAAW,CAAC;EAAE,CACzD;EAIA,OAHA,EAAa,SAAS,MAAc;GAClC,EAAQ,EAAiB,EAAI,WAAW,CAAG,GAAG,KAAK,CAAG;EACxD,CAAC,GACM,EAAiB,KACrB,MAAU,CAAC,GAAO,EAAQ,EAAM,CACnC,EAAE,QAAQ,GAAG,OAAW,EAAM,SAAS,CAAC;CAC1C,GACA,CAAC,CAAY,CACf,GAEM,IAAU,EAAK,SAAS;CAI9B,OAFK,IAGH,kBAAC,GAAD;EACE,2BAA2B,KAAe;EAC1C,0BAA0B;EAC1B,uBAAsB;EACtB,gCAAsC;GACpC,EAAuB;EACzB;EACA,kCAAwC;GACtC,EAAe;EACjB;EACA,6BAA6B,CAAC,KAAW;EACzC,4BAA4B,KAAW;EACvC,yBAAyB,IAAU,SAAS;EAC5C,YAAY,GAAG,EAAO,GAAG,EAAK;EAC9B,yBAAwB;EACxB,yBAAyB;EACzB,0BAAyB;EACzB,gBAAA;EACA,4BAA2B;EAC3B,iBAAiB;EACjB,aAAY;EACZ,iBAAA;EACA,iBAAA;EACA,SAAS;EACT,MAAM;EACN,MAAK;YAEL,kBAAC,OAAD;GAAK,MAAK;aAAV;IACG,IACC,kBAAC,KAAD;KACE,MAAK;KACL,OAAO;MACL,OAAO;MACP,SAAS;KACX;eAEC;IACA,CAAA,IACD;IACH,EAAY,WAAW,IACtB,kBAAC,KAAD;KACE,OAAO;MACL,OAAO;MACP,SAAS;MACT,WAAW;KACb;eAEC,IAAU,SAAS;IACnB,CAAA,IACD;IACH,EAAY,KAAK,CAAC,GAAO,IAAQ,MAChC,kBAAC,GAAD,EAAA,UACG,EAAM,KAAK,GAAQ,MAClB,kBAAC,GAAD;KACE,YACE,MAAe,EAAY,SAAS,KACpC,MAAc,EAAM,SAAS,KAC7B,CAAC,IACG,YACA,KAAA;KAEN,kBACE,EAAO,WAAW,SAAkB,KAAA,IAAT;KAE7B,aAAa,EAAO;KAEpB,UACE,EAAO,WAAW,SAId,KAAA,UAHY;MACV,EAAoB,EAAO,EAAE;KAC/B;KAGN,WACE,EAAO,mBACS;MACV,EAAwB,CAAM;KAChC,IACA,KAAA;KAEN,mBAAmB,EAAO,aAAa,SAAS,KAAA;KAChD,aAAa,MAAc,IAAI,EAAiB,KAAS,KAAA;KACzD,WAAW,EAAO;KAClB,UAAU,EAAW,EAAO,IAAI;KAChC,WAAW,EAAO,WAAW;KAC7B,WAAW,EAAO;KAClB,OAAO,EAAO;KACd,MAAK;IACN,GAvBM,EAAO,EAuBb,CACF,EACO,GAvCK,CAuCL,CACX;GACE;;CACC,CAAA,IAhGmB;AAkG/B;AAEA,SAAS,EAAW,GAA8C;CAIhE,OAHI,MAAS,gBAAsB,UAC/B,MAAS,gBAAsB,YAC/B,MAAS,uBAA6B,YACnC;AACT;AAEA,SAAS,EAAiB,GAAe,GAAsB;CAC7D,IAAM,IAAmB,IAAI,KAAK,CAAK;CACvC,IAAI,OAAO,MAAM,EAAiB,QAAQ,CAAC,GAAG,OAAO;CACrD,IAAM,IAAgB,IAAI,KAAK,EAAI,YAAY,GAAG,EAAI,SAAS,GAAG,EAAI,QAAQ,CAAC,GACzE,IAAyB,IAAI,KACjC,EAAiB,YAAY,GAC7B,EAAiB,SAAS,GAC1B,EAAiB,QAAQ,CAC3B;CACA,IAAI,EAAuB,QAAQ,MAAM,EAAc,QAAQ,GAAG,OAAO;CACzE,IAAM,IAAsB,IAAI,KAAK,CAAa;CAOlD,OANA,EAAoB,QAAQ,EAAoB,QAAQ,IAAI,CAAC,GACzD,EAAuB,QAAQ,MAAM,EAAoB,QAAQ,IAC5D,eAEN,EAAI,QAAQ,IAAI,EAAiB,QAAQ,MAAM,MAAO,KAAK,KAAK,OACjD,IAAU,cACrB;AACT;AAEA,SAAS,EAAiB,GAAwB;CAChD,OAAO,aAAiB,QAAQ,EAAM,UAAU;AAClD;;;AC7RA,SAAgB,EAAU,EACxB,aACA,YAAS,EAAe,OACxB,gBACA,gBAC+B;CAC/B,OACE,kBAAC,GAAD;EAAsC;YACpC,kBAAC,GAAD;GAA2B;GAAwB;aACjD,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA,CACG,GACD,kBAAC,GAAD,CAAqB,CAAA,CACK,EAAA,CAAA,EACF,CAAA;EAChB,CAAA;CACc,CAAA;AAElC;;;AC3CA,SAAgB,IAAoC;CAClD,IAAM,EAAE,cAAW,EAAQ;CAC3B,OAAO;AACT;;;ACJA,SAAgB,IAAiC;CAC/C,IAAM,EAAE,cAAW,EAAQ;CAC3B,OAAO;AACT;;;;;;;AEaA,SAAgB,GAA0B,EACxC,WAAQ,WAC0B,CAAC,GAAiB;CACpD,IAAM,EAAE,YAAS,EAAsB,GACjC,EAAE,mBAAgB,EAAsB,GACxC,IAAY,IAAc,IAAI,GAAG,EAAM,GAAG,EAAY,QAAQ;CACpE,OACE,kBAAC,QAAD;EAAM,WAAW,EAAO;YAAxB,CACE,kBAAC,GAAD;GACE,cAAY;GACZ,MAAM;GACN,eAAqB;IACnB,EAAK;GACP;GACA,OAAO;GACP,MAAK;EACN,CAAA,GACA,IAAc,IACb,kBAAC,QAAD;GAAM,WAAW,EAAO;aACrB,IAAc,KAAK,QAAQ;EACxB,CAAA,IACJ,IACA;;AAEV"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/lib/notification-drawer-provider.tsx","../src/lib/notification-unread-provider.tsx","../src/components/notification-drawer.tsx","../src/lib/providers.tsx","../src/lib/use-bpm-logout.ts","../src/lib/use-bpm-member.ts","../src/components/bpm-notification-bell-button.module.scss","../src/components/bpm-notification-bell-button.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","'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 type { NotificationSeverity } from '@mezzanine-ui/core/notification-center';\nimport type { DropdownOption } from '@mezzanine-ui/core/dropdown/dropdown';\nimport {\n decideTask,\n listNotifications,\n markAllNotificationsRead,\n markNotificationRead,\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';\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/** Identifiers for the per-notification `...` dropdown menu entries. */\ntype NotificationAction = 'approve' | 'reject' | '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 * \"查看案件\" needs an `instanceId`; \"標為已讀\" only shows while unread.\n */\nfunction buildNotificationOptions(\n record: NotificationRecord,\n): readonly DropdownOption[] {\n return [\n ...(record.actionable\n ? ([\n { id: 'approve', name: '同意' },\n { id: 'reject', name: '拒絕' },\n ] satisfies DropdownOption[])\n : []),\n ...(record.instanceId\n ? ([{ id: 'view', name: '查看案件' }] satisfies DropdownOption[])\n : []),\n ...(record.status !== 'READ'\n ? ([{ id: 'read', name: '標為已讀' }] satisfies DropdownOption[])\n : []),\n ];\n}\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, bulk mark-all-read, and\n * load-more pagination. Clicking a row with an `instanceId` navigates to\n * `/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 [rejectTarget, setRejectTarget] = useState<NotificationRecord | null>(\n null,\n );\n const [rejectReason, setRejectReason] = useState('');\n const [deciding, setDeciding] = useState(false);\n\n const trimmedRejectReason = rejectReason.trim();\n\n const loadPage = useCallback(\n async (nextPage: number, append: boolean): Promise<void> => {\n if (!currentMemberId) return;\n setLoading(true);\n setError(null);\n try {\n const result = await listNotifications({\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 } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setLoading(false);\n }\n },\n [currentMemberId, 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 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 handleApprove = useCallback(\n async (record: NotificationRecord): Promise<void> => {\n if (!record.taskId || !currentMemberId || deciding) return;\n setDeciding(true);\n setError(null);\n setNotice(null);\n try {\n await decideTask({\n action: 'APPROVED',\n comment: null,\n decidedByMemberId: currentMemberId,\n taskId: record.taskId,\n });\n setNotice(`已同意「${record.title}」。`);\n await loadPage(1, false);\n } catch (e: unknown) {\n setError(readErrorMessage(e));\n } finally {\n setDeciding(false);\n }\n },\n [currentMemberId, deciding, loadPage],\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') void handleApprove(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 },\n [handleApprove, handleMarkRead, handleOpenInstance, openRejectModal],\n );\n\n const handleCardActivate = useCallback(\n (record: NotificationRecord, target: EventTarget | null): void => {\n // Let clicks on the `...` menu button (and its icon) open the dropdown\n // instead of navigating away. The icon renders as an <svg>, so guard on\n // `Element` (not `HTMLElement`) — SVG nodes are not HTMLElements.\n if (target instanceof Element && target.closest('button')) return;\n void handleOpenInstance(record);\n },\n [handleOpenInstance],\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 (event.target instanceof Element && event.target.closest('button'))\n return;\n event.preventDefault();\n void handleOpenInstance(record);\n },\n [handleOpenInstance],\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}:${rows.length}`}\n filterAreaAllRadioLabel=\"全部\"\n filterAreaOnRadioChange={handleFilterChange}\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.target);\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 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 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 { 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 * 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 * - `<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 publicPaths,\n loginPath,\n}: ProvidersProps): ReactElement {\n return (\n <CalendarConfigProviderMoment locale={locale}>\n <AuthProvider publicPaths={publicPaths} loginPath={loginPath}>\n <NotificationUnreadProvider>\n <NotificationDrawerProvider>\n {children}\n <NotificationDrawer />\n </NotificationDrawerProvider>\n </NotificationUnreadProvider>\n </AuthProvider>\n </CalendarConfigProviderMoment>\n );\n}\n","'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","'use client';\n\nimport type { ApiMember } from '@rytass/bpm-core-client';\nimport { useAuth } from './auth-provider';\n\n/**\n * Read the currently authenticated BPM member. Returns `null` when there\n * is no active session — typically only seen on the login page or during\n * the brief loading window before `<AuthProvider>` resolves the cookie.\n *\n * Convenience alias for `useAuth().member` aimed at host navigations\n * (avatar, display name, role-based menu visibility) that should not\n * depend on the broader `useAuth()` surface.\n */\nexport function useBPMMember(): ApiMember | null {\n const { member } = useAuth();\n return member;\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":"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"}
@@ -58,16 +58,18 @@ export interface BPMRoutes {
58
58
  caseNew(templateId?: string): string;
59
59
  /** Template index. */
60
60
  templates(): string;
61
+ /**
62
+ * Unified "form + flow" creation wizard. Designs a form and an approval
63
+ * flow together, then publishes both atomically. This is the only place
64
+ * forms are designed — there is no separate standalone form builder page.
65
+ */
66
+ templateCompose(): string;
61
67
  /** Template designer (xyflow canvas). */
62
68
  templateDesigner(templateId: string): string;
63
69
  /** Template version history. */
64
70
  templateVersions(templateId: string): string;
65
71
  /** Template categories admin. */
66
72
  templateCategories(): string;
67
- /** Form definitions index. */
68
- forms(): string;
69
- /** Form-builder (CodeMirror schema editor + preview). */
70
- formBuilder(formId: string): string;
71
73
  /** Per-member notification preferences. */
72
74
  notificationSettings(): string;
73
75
  /** Admin: organization tree management. */
@@ -1,2 +1,2 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/routes-config-2aKbWq2H.cjs");let t=require("react"),n=require("react/jsx-runtime"),r=require("next/navigation"),i=require("@rytass/bpm-core-react");function a({children:e,locale:a,publicPaths:o,loginPath:s}){let c=(0,r.useRouter)(),l=(0,r.usePathname)();return(0,n.jsx)(i.RouterAdapterProvider,{value:(0,t.useMemo)(()=>({pathname:l,push:e=>c.push(e),replace:e=>c.replace(e),back:()=>c.back(),searchParams:()=>(0,i.defaultBrowserSearchParams)()}),[c,l]),children:(0,n.jsx)(i.Providers,{locale:a,publicPaths:o,loginPath:s,children:e})})}exports.BPMNextProviders=a,exports.BPMRoutesProvider=e.t,exports.createDefaultBPMRoutes=e.n,exports.useBPMRoutes=e.r;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../chunks/routes-config-fDVHmvXi.cjs");let t=require("react"),n=require("react/jsx-runtime"),r=require("next/navigation"),i=require("@rytass/bpm-core-react");function a({children:e,locale:a,publicPaths:o,loginPath:s}){let c=(0,r.useRouter)(),l=(0,r.usePathname)();return(0,n.jsx)(i.RouterAdapterProvider,{value:(0,t.useMemo)(()=>({pathname:l,push:e=>c.push(e),replace:e=>c.replace(e),back:()=>c.back(),searchParams:()=>(0,i.defaultBrowserSearchParams)()}),[c,l]),children:(0,n.jsx)(i.Providers,{locale:a,publicPaths:o,loginPath:s,children:e})})}exports.BPMNextProviders=a,exports.BPMRoutesProvider=e.t,exports.createDefaultBPMRoutes=e.n,exports.useBPMRoutes=e.r;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { n as e, r as t, t as n } from "../chunks/routes-config-dxahImVe.js";
2
+ import { n as e, r as t, t as n } from "../chunks/routes-config-RBYQtUd0.js";
3
3
  import { useMemo as r } from "react";
4
4
  import { jsx as i } from "react/jsx-runtime";
5
5
  import { usePathname as a, useRouter as o } from "next/navigation";
@@ -0,0 +1,19 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("ai"),t=require("@ai-sdk/openai"),n=require("@rytass/bpm-core-shared");var r=`gpt-5.4-mini`;function i(e){return(0,t.openai)(e.replace(/^openai\//u,``))}var a=`你是「簽核流程設計器」頁面內建的 AI 助理,唯一職責是協助使用者在這個畫布上設計/修改一個簽核審批流程(workflow)。
2
+
3
+ 你只能透過系統提供的工具(tools)來操作流程:新增/重命名/刪除節點、連線、設定簽核人、設定條件分流、設定系統節點動作、自動排版、查詢現況與驗證等。除了這些工具,你沒有其他能力。
4
+
5
+ 工作守則:
6
+ 1. 動作前,先呼叫 get_workflow_snapshot 了解目前畫布的節點與連線,再規劃要呼叫哪些工具。
7
+ 2. 盡量用高階工具(insert_approval_step / insert_notification / insert_conditional_branch)一次完成多步;需要細修時再用細顆粒工具。
8
+ 3. **重要:要果斷、先把流程畫出來。** 使用者只給一兩句話時,直接依常見公司慣例建立初稿,不要反覆追問細節。簽核人若使用者沒指定,就省略 approverResolver(系統會自動用「直屬主管」為預設,不需要真實會員 id);通知節點同理可省略 action(預設站內通知)。先把結構建好、再請使用者調整。
9
+ 4. 工具呼叫不會因為「缺簽核人/缺資料」而失敗;缺的部分會以合理預設補上,並由 validate_workflow 標示。所以放心連續呼叫工具把流程建完,不要因為怕缺欄位就停下來問。
10
+ 5. 當使用者「點名」某個人或部門當簽核人/收件者時,先用 search_members(依姓名關鍵字)、list_org_units、list_positions 查到真實 id,再用 set_user_task_approver / set_service_action 指定(如 DIRECT memberIds、ORG_UNIT_MANAGER orgUnitId、POSITION positionId)。查不到就回報並請使用者確認,不要編造 id。
11
+ 6. 節點 id 可從 snapshot 取得;連線條件需要先綁定表單欄位(若 list_form_fields 為空,提醒使用者先綁定表單版本)。
12
+ 7. **條件分流務必主動補上「其他情況」**:設計 exclusiveGateway(條件分流)時,除了符合條件的分支,一定要保留一條未設條件、標記為預設(isDefault)的「其他情況」路徑當 else(通常指向後續節點或完成)。不要把所有輸出連線都設成條件而沒有預設出口;缺預設出口時,用 set_edge_default 把其中一條設為預設。
13
+ 8. 完成後,呼叫 validate_workflow,並用一兩句話說明你建了什麼流程、哪些地方(如簽核人、條件門檻)建議使用者再確認或指定。
14
+ 9. 全程使用繁體中文(台灣用語)回覆。
15
+
16
+ 嚴格界線:
17
+ - 只做「設計這個簽核流程」相關的事。任何與此無關的請求(閒聊、寫詩、寫程式、查天氣/新聞、操作其他系統或頁面、回答一般知識問題等)一律婉拒,並簡短引導使用者回到流程設計,例如:「我只能協助你設計這個簽核流程,需要我幫你新增關卡或設定條件嗎?」
18
+ - 不要假裝執行你工具以外的動作,也不要編造結果。`;function o(){let t=n.WORKFLOW_TOOLSET.map(t=>[t.name,(0,e.tool)({description:t.description,inputSchema:(0,e.jsonSchema)(t.inputSchema)})]);return Object.fromEntries(t)}function s(t={}){let n=o();return async function(o){return(0,e.streamText)({messages:await(0,e.convertToModelMessages)([...(await o.json()).messages]),model:i(t.model??process.env.BPM_LLM_MODEL??r),system:t.system??a,tools:n}).toUIMessageStreamResponse()}}exports.WORKFLOW_CHAT_SYSTEM_PROMPT=a,exports.buildWorkflowAiSdkTools=o,exports.createWorkflowChatPOST=s;
19
+ //# sourceMappingURL=workflow-chat-route.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-chat-route.cjs","names":[],"sources":["../../src/next/workflow-chat-route.ts"],"sourcesContent":["import {\n convertToModelMessages,\n jsonSchema,\n streamText,\n tool,\n type LanguageModel,\n type Tool,\n type ToolSet,\n type UIMessage,\n} from 'ai';\nimport { openai } from '@ai-sdk/openai';\nimport { WORKFLOW_TOOLSET } from '@rytass/bpm-core-shared';\n\n/**\n * Next.js route handler for the workflow designer LLM assistant.\n *\n * The model is advertised the workflow {@link WORKFLOW_TOOLSET} but the tools\n * carry **no `execute`** — so every tool call is forwarded to the browser,\n * where `useChat`'s `onToolCall` runs it against the React designer controller\n * (see `use-workflow-chat.ts`). This route only holds the API key, runs the\n * model, and streams the tool-call plan back. It never touches the canvas.\n *\n * Host usage (apps/client/src/app/api/chat/route.ts):\n * export const runtime = 'nodejs';\n * export const POST = createWorkflowChatPOST();\n */\n\n/** Default OpenAI model id (overridable via BPM_LLM_MODEL). */\nconst DEFAULT_MODEL = 'gpt-5.4-mini';\n\n/**\n * Resolve the model via the OpenAI provider directly (reads `OPENAI_API_KEY`).\n * Accepts a bare id (`gpt-5.4-mini`) or a `openai/`-prefixed id; the prefix is\n * stripped. This deployment talks to OpenAI directly — no AI Gateway.\n */\nfunction resolveModel(setting: string): LanguageModel {\n return openai(setting.replace(/^openai\\//u, ''));\n}\n\n/**\n * Strict guardrail: the assistant may ONLY design this approval flow, using the\n * provided tools. Anything else is declined. Lives server-side so it cannot be\n * overridden by client-supplied messages.\n */\nexport const WORKFLOW_CHAT_SYSTEM_PROMPT = `你是「簽核流程設計器」頁面內建的 AI 助理,唯一職責是協助使用者在這個畫布上設計/修改一個簽核審批流程(workflow)。\n\n你只能透過系統提供的工具(tools)來操作流程:新增/重命名/刪除節點、連線、設定簽核人、設定條件分流、設定系統節點動作、自動排版、查詢現況與驗證等。除了這些工具,你沒有其他能力。\n\n工作守則:\n1. 動作前,先呼叫 get_workflow_snapshot 了解目前畫布的節點與連線,再規劃要呼叫哪些工具。\n2. 盡量用高階工具(insert_approval_step / insert_notification / insert_conditional_branch)一次完成多步;需要細修時再用細顆粒工具。\n3. **重要:要果斷、先把流程畫出來。** 使用者只給一兩句話時,直接依常見公司慣例建立初稿,不要反覆追問細節。簽核人若使用者沒指定,就省略 approverResolver(系統會自動用「直屬主管」為預設,不需要真實會員 id);通知節點同理可省略 action(預設站內通知)。先把結構建好、再請使用者調整。\n4. 工具呼叫不會因為「缺簽核人/缺資料」而失敗;缺的部分會以合理預設補上,並由 validate_workflow 標示。所以放心連續呼叫工具把流程建完,不要因為怕缺欄位就停下來問。\n5. 當使用者「點名」某個人或部門當簽核人/收件者時,先用 search_members(依姓名關鍵字)、list_org_units、list_positions 查到真實 id,再用 set_user_task_approver / set_service_action 指定(如 DIRECT memberIds、ORG_UNIT_MANAGER orgUnitId、POSITION positionId)。查不到就回報並請使用者確認,不要編造 id。\n6. 節點 id 可從 snapshot 取得;連線條件需要先綁定表單欄位(若 list_form_fields 為空,提醒使用者先綁定表單版本)。\n7. **條件分流務必主動補上「其他情況」**:設計 exclusiveGateway(條件分流)時,除了符合條件的分支,一定要保留一條未設條件、標記為預設(isDefault)的「其他情況」路徑當 else(通常指向後續節點或完成)。不要把所有輸出連線都設成條件而沒有預設出口;缺預設出口時,用 set_edge_default 把其中一條設為預設。\n8. 完成後,呼叫 validate_workflow,並用一兩句話說明你建了什麼流程、哪些地方(如簽核人、條件門檻)建議使用者再確認或指定。\n9. 全程使用繁體中文(台灣用語)回覆。\n\n嚴格界線:\n- 只做「設計這個簽核流程」相關的事。任何與此無關的請求(閒聊、寫詩、寫程式、查天氣/新聞、操作其他系統或頁面、回答一般知識問題等)一律婉拒,並簡短引導使用者回到流程設計,例如:「我只能協助你設計這個簽核流程,需要我幫你新增關卡或設定條件嗎?」\n- 不要假裝執行你工具以外的動作,也不要編造結果。`;\n\n/** Build AI SDK tools from WORKFLOW_TOOLSET — no `execute`, so the browser runs them. */\nexport function buildWorkflowAiSdkTools(): ToolSet {\n const entries: readonly (readonly [string, Tool])[] = WORKFLOW_TOOLSET.map(\n (workflowTool) =>\n [\n workflowTool.name,\n tool({\n description: workflowTool.description,\n inputSchema: jsonSchema(\n workflowTool.inputSchema as Parameters<typeof jsonSchema>[0],\n ),\n }),\n ] as const,\n );\n\n return Object.fromEntries(entries) as ToolSet;\n}\n\nexport interface WorkflowChatRouteOptions {\n /** Override the OpenAI model id (else BPM_LLM_MODEL env, else the default). */\n readonly model?: string;\n /** Override the system prompt (else the strict design-only guardrail). */\n readonly system?: string;\n}\n\ninterface WorkflowChatRequestBody {\n readonly messages: readonly UIMessage[];\n}\n\n/** Create the `POST` handler for the workflow chat route. */\nexport function createWorkflowChatPOST(\n options: WorkflowChatRouteOptions = {},\n): (request: Request) => Promise<Response> {\n const tools = buildWorkflowAiSdkTools();\n\n return async function POST(request: Request): Promise<Response> {\n const body = (await request.json()) as WorkflowChatRequestBody;\n const result = streamText({\n messages: await convertToModelMessages([...body.messages]),\n model: resolveModel(\n options.model ?? process.env.BPM_LLM_MODEL ?? DEFAULT_MODEL,\n ),\n system: options.system ?? WORKFLOW_CHAT_SYSTEM_PROMPT,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n };\n}\n"],"mappings":"wJA4BA,IAAM,EAAgB,eAOtB,SAAS,EAAa,EAAgC,CACpD,OAAA,EAAA,EAAA,QAAc,EAAQ,QAAQ,aAAc,EAAE,CAAC,CACjD,CAOA,IAAa,EAA8B;;;;;;;;;;;;;;;;;2BAoB3C,SAAgB,GAAmC,CACjD,IAAM,EAAgD,EAAA,iBAAiB,IACpE,GACC,CACE,EAAa,MAAA,EAAA,EAAA,MACR,CACH,YAAa,EAAa,YAC1B,aAAA,EAAA,EAAA,YACE,EAAa,WACf,CACF,CAAC,CACH,CACJ,EAEA,OAAO,OAAO,YAAY,CAAO,CACnC,CAcA,SAAgB,EACd,EAAoC,CAAC,EACI,CACzC,IAAM,EAAQ,EAAwB,EAEtC,OAAO,eAAoB,EAAqC,CAW9D,OAAA,EAAA,EAAA,YAT0B,CACxB,SAAU,MAAA,EAAA,EAAA,wBAA6B,CAAC,IAAG,MAFzB,EAAQ,KAAK,GAEiB,QAAQ,CAAC,EACzD,MAAO,EACL,EAAQ,OAAS,QAAQ,IAAI,eAAiB,CAChD,EACA,OAAQ,EAAQ,QAAU,EAC1B,OACF,CAEO,EAAO,0BAA0B,CAC1C,CACF"}
@@ -0,0 +1,17 @@
1
+ import { ToolSet } from 'ai';
2
+ /**
3
+ * Strict guardrail: the assistant may ONLY design this approval flow, using the
4
+ * provided tools. Anything else is declined. Lives server-side so it cannot be
5
+ * overridden by client-supplied messages.
6
+ */
7
+ export declare const WORKFLOW_CHAT_SYSTEM_PROMPT = "\u4F60\u662F\u300C\u7C3D\u6838\u6D41\u7A0B\u8A2D\u8A08\u5668\u300D\u9801\u9762\u5167\u5EFA\u7684 AI \u52A9\u7406\uFF0C\u552F\u4E00\u8077\u8CAC\u662F\u5354\u52A9\u4F7F\u7528\u8005\u5728\u9019\u500B\u756B\u5E03\u4E0A\u8A2D\u8A08\uFF0F\u4FEE\u6539\u4E00\u500B\u7C3D\u6838\u5BE9\u6279\u6D41\u7A0B\uFF08workflow\uFF09\u3002\n\n\u4F60\u53EA\u80FD\u900F\u904E\u7CFB\u7D71\u63D0\u4F9B\u7684\u5DE5\u5177\uFF08tools\uFF09\u4F86\u64CD\u4F5C\u6D41\u7A0B\uFF1A\u65B0\u589E/\u91CD\u547D\u540D/\u522A\u9664\u7BC0\u9EDE\u3001\u9023\u7DDA\u3001\u8A2D\u5B9A\u7C3D\u6838\u4EBA\u3001\u8A2D\u5B9A\u689D\u4EF6\u5206\u6D41\u3001\u8A2D\u5B9A\u7CFB\u7D71\u7BC0\u9EDE\u52D5\u4F5C\u3001\u81EA\u52D5\u6392\u7248\u3001\u67E5\u8A62\u73FE\u6CC1\u8207\u9A57\u8B49\u7B49\u3002\u9664\u4E86\u9019\u4E9B\u5DE5\u5177\uFF0C\u4F60\u6C92\u6709\u5176\u4ED6\u80FD\u529B\u3002\n\n\u5DE5\u4F5C\u5B88\u5247\uFF1A\n1. \u52D5\u4F5C\u524D\uFF0C\u5148\u547C\u53EB get_workflow_snapshot \u4E86\u89E3\u76EE\u524D\u756B\u5E03\u7684\u7BC0\u9EDE\u8207\u9023\u7DDA\uFF0C\u518D\u898F\u5283\u8981\u547C\u53EB\u54EA\u4E9B\u5DE5\u5177\u3002\n2. \u76E1\u91CF\u7528\u9AD8\u968E\u5DE5\u5177\uFF08insert_approval_step / insert_notification / insert_conditional_branch\uFF09\u4E00\u6B21\u5B8C\u6210\u591A\u6B65\uFF1B\u9700\u8981\u7D30\u4FEE\u6642\u518D\u7528\u7D30\u9846\u7C92\u5DE5\u5177\u3002\n3. **\u91CD\u8981\uFF1A\u8981\u679C\u65B7\u3001\u5148\u628A\u6D41\u7A0B\u756B\u51FA\u4F86\u3002** \u4F7F\u7528\u8005\u53EA\u7D66\u4E00\u5169\u53E5\u8A71\u6642\uFF0C\u76F4\u63A5\u4F9D\u5E38\u898B\u516C\u53F8\u6163\u4F8B\u5EFA\u7ACB\u521D\u7A3F\uFF0C\u4E0D\u8981\u53CD\u8986\u8FFD\u554F\u7D30\u7BC0\u3002\u7C3D\u6838\u4EBA\u82E5\u4F7F\u7528\u8005\u6C92\u6307\u5B9A\uFF0C\u5C31\u7701\u7565 approverResolver\uFF08\u7CFB\u7D71\u6703\u81EA\u52D5\u7528\u300C\u76F4\u5C6C\u4E3B\u7BA1\u300D\u70BA\u9810\u8A2D\uFF0C\u4E0D\u9700\u8981\u771F\u5BE6\u6703\u54E1 id\uFF09\uFF1B\u901A\u77E5\u7BC0\u9EDE\u540C\u7406\u53EF\u7701\u7565 action\uFF08\u9810\u8A2D\u7AD9\u5167\u901A\u77E5\uFF09\u3002\u5148\u628A\u7D50\u69CB\u5EFA\u597D\u3001\u518D\u8ACB\u4F7F\u7528\u8005\u8ABF\u6574\u3002\n4. \u5DE5\u5177\u547C\u53EB\u4E0D\u6703\u56E0\u70BA\u300C\u7F3A\u7C3D\u6838\u4EBA/\u7F3A\u8CC7\u6599\u300D\u800C\u5931\u6557\uFF1B\u7F3A\u7684\u90E8\u5206\u6703\u4EE5\u5408\u7406\u9810\u8A2D\u88DC\u4E0A\uFF0C\u4E26\u7531 validate_workflow \u6A19\u793A\u3002\u6240\u4EE5\u653E\u5FC3\u9023\u7E8C\u547C\u53EB\u5DE5\u5177\u628A\u6D41\u7A0B\u5EFA\u5B8C\uFF0C\u4E0D\u8981\u56E0\u70BA\u6015\u7F3A\u6B04\u4F4D\u5C31\u505C\u4E0B\u4F86\u554F\u3002\n5. \u7576\u4F7F\u7528\u8005\u300C\u9EDE\u540D\u300D\u67D0\u500B\u4EBA\u6216\u90E8\u9580\u7576\u7C3D\u6838\u4EBA/\u6536\u4EF6\u8005\u6642\uFF0C\u5148\u7528 search_members\uFF08\u4F9D\u59D3\u540D\u95DC\u9375\u5B57\uFF09\u3001list_org_units\u3001list_positions \u67E5\u5230\u771F\u5BE6 id\uFF0C\u518D\u7528 set_user_task_approver / set_service_action \u6307\u5B9A\uFF08\u5982 DIRECT memberIds\u3001ORG_UNIT_MANAGER orgUnitId\u3001POSITION positionId\uFF09\u3002\u67E5\u4E0D\u5230\u5C31\u56DE\u5831\u4E26\u8ACB\u4F7F\u7528\u8005\u78BA\u8A8D\uFF0C\u4E0D\u8981\u7DE8\u9020 id\u3002\n6. \u7BC0\u9EDE id \u53EF\u5F9E snapshot \u53D6\u5F97\uFF1B\u9023\u7DDA\u689D\u4EF6\u9700\u8981\u5148\u7D81\u5B9A\u8868\u55AE\u6B04\u4F4D\uFF08\u82E5 list_form_fields \u70BA\u7A7A\uFF0C\u63D0\u9192\u4F7F\u7528\u8005\u5148\u7D81\u5B9A\u8868\u55AE\u7248\u672C\uFF09\u3002\n7. **\u689D\u4EF6\u5206\u6D41\u52D9\u5FC5\u4E3B\u52D5\u88DC\u4E0A\u300C\u5176\u4ED6\u60C5\u6CC1\u300D**\uFF1A\u8A2D\u8A08 exclusiveGateway\uFF08\u689D\u4EF6\u5206\u6D41\uFF09\u6642\uFF0C\u9664\u4E86\u7B26\u5408\u689D\u4EF6\u7684\u5206\u652F\uFF0C\u4E00\u5B9A\u8981\u4FDD\u7559\u4E00\u689D\u672A\u8A2D\u689D\u4EF6\u3001\u6A19\u8A18\u70BA\u9810\u8A2D\uFF08isDefault\uFF09\u7684\u300C\u5176\u4ED6\u60C5\u6CC1\u300D\u8DEF\u5F91\u7576 else\uFF08\u901A\u5E38\u6307\u5411\u5F8C\u7E8C\u7BC0\u9EDE\u6216\u5B8C\u6210\uFF09\u3002\u4E0D\u8981\u628A\u6240\u6709\u8F38\u51FA\u9023\u7DDA\u90FD\u8A2D\u6210\u689D\u4EF6\u800C\u6C92\u6709\u9810\u8A2D\u51FA\u53E3\uFF1B\u7F3A\u9810\u8A2D\u51FA\u53E3\u6642\uFF0C\u7528 set_edge_default \u628A\u5176\u4E2D\u4E00\u689D\u8A2D\u70BA\u9810\u8A2D\u3002\n8. \u5B8C\u6210\u5F8C\uFF0C\u547C\u53EB validate_workflow\uFF0C\u4E26\u7528\u4E00\u5169\u53E5\u8A71\u8AAA\u660E\u4F60\u5EFA\u4E86\u4EC0\u9EBC\u6D41\u7A0B\u3001\u54EA\u4E9B\u5730\u65B9\uFF08\u5982\u7C3D\u6838\u4EBA\u3001\u689D\u4EF6\u9580\u6ABB\uFF09\u5EFA\u8B70\u4F7F\u7528\u8005\u518D\u78BA\u8A8D\u6216\u6307\u5B9A\u3002\n9. \u5168\u7A0B\u4F7F\u7528\u7E41\u9AD4\u4E2D\u6587\uFF08\u53F0\u7063\u7528\u8A9E\uFF09\u56DE\u8986\u3002\n\n\u56B4\u683C\u754C\u7DDA\uFF1A\n- \u53EA\u505A\u300C\u8A2D\u8A08\u9019\u500B\u7C3D\u6838\u6D41\u7A0B\u300D\u76F8\u95DC\u7684\u4E8B\u3002\u4EFB\u4F55\u8207\u6B64\u7121\u95DC\u7684\u8ACB\u6C42\uFF08\u9592\u804A\u3001\u5BEB\u8A69\u3001\u5BEB\u7A0B\u5F0F\u3001\u67E5\u5929\u6C23\uFF0F\u65B0\u805E\u3001\u64CD\u4F5C\u5176\u4ED6\u7CFB\u7D71\u6216\u9801\u9762\u3001\u56DE\u7B54\u4E00\u822C\u77E5\u8B58\u554F\u984C\u7B49\uFF09\u4E00\u5F8B\u5A49\u62D2\uFF0C\u4E26\u7C21\u77ED\u5F15\u5C0E\u4F7F\u7528\u8005\u56DE\u5230\u6D41\u7A0B\u8A2D\u8A08\uFF0C\u4F8B\u5982\uFF1A\u300C\u6211\u53EA\u80FD\u5354\u52A9\u4F60\u8A2D\u8A08\u9019\u500B\u7C3D\u6838\u6D41\u7A0B\uFF0C\u9700\u8981\u6211\u5E6B\u4F60\u65B0\u589E\u95DC\u5361\u6216\u8A2D\u5B9A\u689D\u4EF6\u55CE\uFF1F\u300D\n- \u4E0D\u8981\u5047\u88DD\u57F7\u884C\u4F60\u5DE5\u5177\u4EE5\u5916\u7684\u52D5\u4F5C\uFF0C\u4E5F\u4E0D\u8981\u7DE8\u9020\u7D50\u679C\u3002";
8
+ /** Build AI SDK tools from WORKFLOW_TOOLSET — no `execute`, so the browser runs them. */
9
+ export declare function buildWorkflowAiSdkTools(): ToolSet;
10
+ export interface WorkflowChatRouteOptions {
11
+ /** Override the OpenAI model id (else BPM_LLM_MODEL env, else the default). */
12
+ readonly model?: string;
13
+ /** Override the system prompt (else the strict design-only guardrail). */
14
+ readonly system?: string;
15
+ }
16
+ /** Create the `POST` handler for the workflow chat route. */
17
+ export declare function createWorkflowChatPOST(options?: WorkflowChatRouteOptions): (request: Request) => Promise<Response>;
@@ -0,0 +1,31 @@
1
+ import { convertToModelMessages as e, jsonSchema as t, streamText as n, tool as r } from "ai";
2
+ import { openai as i } from "@ai-sdk/openai";
3
+ import { WORKFLOW_TOOLSET as a } from "@rytass/bpm-core-shared";
4
+ //#region src/next/workflow-chat-route.ts
5
+ var o = "gpt-5.4-mini";
6
+ function s(e) {
7
+ return i(e.replace(/^openai\//u, ""));
8
+ }
9
+ var c = "你是「簽核流程設計器」頁面內建的 AI 助理,唯一職責是協助使用者在這個畫布上設計/修改一個簽核審批流程(workflow)。\n\n你只能透過系統提供的工具(tools)來操作流程:新增/重命名/刪除節點、連線、設定簽核人、設定條件分流、設定系統節點動作、自動排版、查詢現況與驗證等。除了這些工具,你沒有其他能力。\n\n工作守則:\n1. 動作前,先呼叫 get_workflow_snapshot 了解目前畫布的節點與連線,再規劃要呼叫哪些工具。\n2. 盡量用高階工具(insert_approval_step / insert_notification / insert_conditional_branch)一次完成多步;需要細修時再用細顆粒工具。\n3. **重要:要果斷、先把流程畫出來。** 使用者只給一兩句話時,直接依常見公司慣例建立初稿,不要反覆追問細節。簽核人若使用者沒指定,就省略 approverResolver(系統會自動用「直屬主管」為預設,不需要真實會員 id);通知節點同理可省略 action(預設站內通知)。先把結構建好、再請使用者調整。\n4. 工具呼叫不會因為「缺簽核人/缺資料」而失敗;缺的部分會以合理預設補上,並由 validate_workflow 標示。所以放心連續呼叫工具把流程建完,不要因為怕缺欄位就停下來問。\n5. 當使用者「點名」某個人或部門當簽核人/收件者時,先用 search_members(依姓名關鍵字)、list_org_units、list_positions 查到真實 id,再用 set_user_task_approver / set_service_action 指定(如 DIRECT memberIds、ORG_UNIT_MANAGER orgUnitId、POSITION positionId)。查不到就回報並請使用者確認,不要編造 id。\n6. 節點 id 可從 snapshot 取得;連線條件需要先綁定表單欄位(若 list_form_fields 為空,提醒使用者先綁定表單版本)。\n7. **條件分流務必主動補上「其他情況」**:設計 exclusiveGateway(條件分流)時,除了符合條件的分支,一定要保留一條未設條件、標記為預設(isDefault)的「其他情況」路徑當 else(通常指向後續節點或完成)。不要把所有輸出連線都設成條件而沒有預設出口;缺預設出口時,用 set_edge_default 把其中一條設為預設。\n8. 完成後,呼叫 validate_workflow,並用一兩句話說明你建了什麼流程、哪些地方(如簽核人、條件門檻)建議使用者再確認或指定。\n9. 全程使用繁體中文(台灣用語)回覆。\n\n嚴格界線:\n- 只做「設計這個簽核流程」相關的事。任何與此無關的請求(閒聊、寫詩、寫程式、查天氣/新聞、操作其他系統或頁面、回答一般知識問題等)一律婉拒,並簡短引導使用者回到流程設計,例如:「我只能協助你設計這個簽核流程,需要我幫你新增關卡或設定條件嗎?」\n- 不要假裝執行你工具以外的動作,也不要編造結果。";
10
+ function l() {
11
+ let e = a.map((e) => [e.name, r({
12
+ description: e.description,
13
+ inputSchema: t(e.inputSchema)
14
+ })]);
15
+ return Object.fromEntries(e);
16
+ }
17
+ function u(t = {}) {
18
+ let r = l();
19
+ return async function(i) {
20
+ return n({
21
+ messages: await e([...(await i.json()).messages]),
22
+ model: s(t.model ?? process.env.BPM_LLM_MODEL ?? o),
23
+ system: t.system ?? c,
24
+ tools: r
25
+ }).toUIMessageStreamResponse();
26
+ };
27
+ }
28
+ //#endregion
29
+ export { c as WORKFLOW_CHAT_SYSTEM_PROMPT, l as buildWorkflowAiSdkTools, u as createWorkflowChatPOST };
30
+
31
+ //# sourceMappingURL=workflow-chat-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-chat-route.js","names":[],"sources":["../../src/next/workflow-chat-route.ts"],"sourcesContent":["import {\n convertToModelMessages,\n jsonSchema,\n streamText,\n tool,\n type LanguageModel,\n type Tool,\n type ToolSet,\n type UIMessage,\n} from 'ai';\nimport { openai } from '@ai-sdk/openai';\nimport { WORKFLOW_TOOLSET } from '@rytass/bpm-core-shared';\n\n/**\n * Next.js route handler for the workflow designer LLM assistant.\n *\n * The model is advertised the workflow {@link WORKFLOW_TOOLSET} but the tools\n * carry **no `execute`** — so every tool call is forwarded to the browser,\n * where `useChat`'s `onToolCall` runs it against the React designer controller\n * (see `use-workflow-chat.ts`). This route only holds the API key, runs the\n * model, and streams the tool-call plan back. It never touches the canvas.\n *\n * Host usage (apps/client/src/app/api/chat/route.ts):\n * export const runtime = 'nodejs';\n * export const POST = createWorkflowChatPOST();\n */\n\n/** Default OpenAI model id (overridable via BPM_LLM_MODEL). */\nconst DEFAULT_MODEL = 'gpt-5.4-mini';\n\n/**\n * Resolve the model via the OpenAI provider directly (reads `OPENAI_API_KEY`).\n * Accepts a bare id (`gpt-5.4-mini`) or a `openai/`-prefixed id; the prefix is\n * stripped. This deployment talks to OpenAI directly — no AI Gateway.\n */\nfunction resolveModel(setting: string): LanguageModel {\n return openai(setting.replace(/^openai\\//u, ''));\n}\n\n/**\n * Strict guardrail: the assistant may ONLY design this approval flow, using the\n * provided tools. Anything else is declined. Lives server-side so it cannot be\n * overridden by client-supplied messages.\n */\nexport const WORKFLOW_CHAT_SYSTEM_PROMPT = `你是「簽核流程設計器」頁面內建的 AI 助理,唯一職責是協助使用者在這個畫布上設計/修改一個簽核審批流程(workflow)。\n\n你只能透過系統提供的工具(tools)來操作流程:新增/重命名/刪除節點、連線、設定簽核人、設定條件分流、設定系統節點動作、自動排版、查詢現況與驗證等。除了這些工具,你沒有其他能力。\n\n工作守則:\n1. 動作前,先呼叫 get_workflow_snapshot 了解目前畫布的節點與連線,再規劃要呼叫哪些工具。\n2. 盡量用高階工具(insert_approval_step / insert_notification / insert_conditional_branch)一次完成多步;需要細修時再用細顆粒工具。\n3. **重要:要果斷、先把流程畫出來。** 使用者只給一兩句話時,直接依常見公司慣例建立初稿,不要反覆追問細節。簽核人若使用者沒指定,就省略 approverResolver(系統會自動用「直屬主管」為預設,不需要真實會員 id);通知節點同理可省略 action(預設站內通知)。先把結構建好、再請使用者調整。\n4. 工具呼叫不會因為「缺簽核人/缺資料」而失敗;缺的部分會以合理預設補上,並由 validate_workflow 標示。所以放心連續呼叫工具把流程建完,不要因為怕缺欄位就停下來問。\n5. 當使用者「點名」某個人或部門當簽核人/收件者時,先用 search_members(依姓名關鍵字)、list_org_units、list_positions 查到真實 id,再用 set_user_task_approver / set_service_action 指定(如 DIRECT memberIds、ORG_UNIT_MANAGER orgUnitId、POSITION positionId)。查不到就回報並請使用者確認,不要編造 id。\n6. 節點 id 可從 snapshot 取得;連線條件需要先綁定表單欄位(若 list_form_fields 為空,提醒使用者先綁定表單版本)。\n7. **條件分流務必主動補上「其他情況」**:設計 exclusiveGateway(條件分流)時,除了符合條件的分支,一定要保留一條未設條件、標記為預設(isDefault)的「其他情況」路徑當 else(通常指向後續節點或完成)。不要把所有輸出連線都設成條件而沒有預設出口;缺預設出口時,用 set_edge_default 把其中一條設為預設。\n8. 完成後,呼叫 validate_workflow,並用一兩句話說明你建了什麼流程、哪些地方(如簽核人、條件門檻)建議使用者再確認或指定。\n9. 全程使用繁體中文(台灣用語)回覆。\n\n嚴格界線:\n- 只做「設計這個簽核流程」相關的事。任何與此無關的請求(閒聊、寫詩、寫程式、查天氣/新聞、操作其他系統或頁面、回答一般知識問題等)一律婉拒,並簡短引導使用者回到流程設計,例如:「我只能協助你設計這個簽核流程,需要我幫你新增關卡或設定條件嗎?」\n- 不要假裝執行你工具以外的動作,也不要編造結果。`;\n\n/** Build AI SDK tools from WORKFLOW_TOOLSET — no `execute`, so the browser runs them. */\nexport function buildWorkflowAiSdkTools(): ToolSet {\n const entries: readonly (readonly [string, Tool])[] = WORKFLOW_TOOLSET.map(\n (workflowTool) =>\n [\n workflowTool.name,\n tool({\n description: workflowTool.description,\n inputSchema: jsonSchema(\n workflowTool.inputSchema as Parameters<typeof jsonSchema>[0],\n ),\n }),\n ] as const,\n );\n\n return Object.fromEntries(entries) as ToolSet;\n}\n\nexport interface WorkflowChatRouteOptions {\n /** Override the OpenAI model id (else BPM_LLM_MODEL env, else the default). */\n readonly model?: string;\n /** Override the system prompt (else the strict design-only guardrail). */\n readonly system?: string;\n}\n\ninterface WorkflowChatRequestBody {\n readonly messages: readonly UIMessage[];\n}\n\n/** Create the `POST` handler for the workflow chat route. */\nexport function createWorkflowChatPOST(\n options: WorkflowChatRouteOptions = {},\n): (request: Request) => Promise<Response> {\n const tools = buildWorkflowAiSdkTools();\n\n return async function POST(request: Request): Promise<Response> {\n const body = (await request.json()) as WorkflowChatRequestBody;\n const result = streamText({\n messages: await convertToModelMessages([...body.messages]),\n model: resolveModel(\n options.model ?? process.env.BPM_LLM_MODEL ?? DEFAULT_MODEL,\n ),\n system: options.system ?? WORKFLOW_CHAT_SYSTEM_PROMPT,\n tools,\n });\n\n return result.toUIMessageStreamResponse();\n };\n}\n"],"mappings":";;;;AA4BA,IAAM,IAAgB;AAOtB,SAAS,EAAa,GAAgC;CACpD,OAAO,EAAO,EAAQ,QAAQ,cAAc,EAAE,CAAC;AACjD;AAOA,IAAa,IAA8B;AAoB3C,SAAgB,IAAmC;CACjD,IAAM,IAAgD,EAAiB,KACpE,MACC,CACE,EAAa,MACb,EAAK;EACH,aAAa,EAAa;EAC1B,aAAa,EACX,EAAa,WACf;CACF,CAAC,CACH,CACJ;CAEA,OAAO,OAAO,YAAY,CAAO;AACnC;AAcA,SAAgB,EACd,IAAoC,CAAC,GACI;CACzC,IAAM,IAAQ,EAAwB;CAEtC,OAAO,eAAoB,GAAqC;EAW9D,OATe,EAAW;GACxB,UAAU,MAAM,EAAuB,CAAC,IAAG,MAFzB,EAAQ,KAAK,GAEiB,QAAQ,CAAC;GACzD,OAAO,EACL,EAAQ,SAAS,QAAQ,IAAI,iBAAiB,CAChD;GACA,QAAQ,EAAQ,UAAU;GAC1B;EACF,CAEO,EAAO,0BAA0B;CAC1C;AACF"}
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/detail-Dcr5mM8g.cjs");let t=require("react/jsx-runtime");var n={title:`案件詳情 | BPM Admin`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{instanceId:r})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/detail-Cci9tnoC.cjs");let t=require("react/jsx-runtime");var n={title:`案件詳情 | BPM Admin`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{instanceId:r})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,4 @@
1
- import { t as e } from "../../../chunks/detail-u9DdLhDW.js";
1
+ import { t as e } from "../../../chunks/detail-kyolfdBu.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/pages/instances/detail/index.tsx
4
4
  var n = { title: "案件詳情 | BPM Admin" };
@@ -0,0 +1,2 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/compose-ziVbRYdo.cjs");let t=require("react/jsx-runtime");var n={title:`建立模板(表單 + 流程) | BPM Admin`,description:`以精靈式流程一次完成 BPM 表單與簽核流程設計並發佈。`};function r(){return(0,t.jsx)(e.t,{})}exports.default=r,exports.metadata=n;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/pages/templates/compose/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplateComposeWizardView } from '../../../views/templates/compose';\n\nexport const metadata: Metadata = {\n title: '建立模板(表單 + 流程) | BPM Admin',\n description: '以精靈式流程一次完成 BPM 表單與簽核流程設計並發佈。',\n};\n\n/**\n * Drop-in Next.js App Router page for the unified template wizard.\n *\n * Consumer usage in `app/templates/compose/page.tsx`:\n *\n * ```ts\n * export { default, metadata } from '@rytass/bpm-core-react/pages/templates/compose';\n * ```\n */\nexport default function TemplateComposePage(): ReactElement {\n return <TemplateComposeWizardView />;\n}\n"],"mappings":"0LAIA,IAAa,EAAqB,CAChC,MAAO,4BACP,YAAa,8BACf,EAWA,SAAwB,GAAoC,CAC1D,OAAO,EAAA,EAAA,KAAC,EAAA,EAAD,CAA4B,CAAA,CACrC"}
@@ -0,0 +1,13 @@
1
+ import { Metadata } from 'next';
2
+ import { ReactElement } from 'react';
3
+ export declare const metadata: Metadata;
4
+ /**
5
+ * Drop-in Next.js App Router page for the unified template wizard.
6
+ *
7
+ * Consumer usage in `app/templates/compose/page.tsx`:
8
+ *
9
+ * ```ts
10
+ * export { default, metadata } from '@rytass/bpm-core-react/pages/templates/compose';
11
+ * ```
12
+ */
13
+ export default function TemplateComposePage(): ReactElement;
@@ -0,0 +1,14 @@
1
+ import { t as e } from "../../../chunks/compose-PMrmi-LE.js";
2
+ import { jsx as t } from "react/jsx-runtime";
3
+ //#region src/pages/templates/compose/index.tsx
4
+ var n = {
5
+ title: "建立模板(表單 + 流程) | BPM Admin",
6
+ description: "以精靈式流程一次完成 BPM 表單與簽核流程設計並發佈。"
7
+ };
8
+ function r() {
9
+ return /* @__PURE__ */ t(e, {});
10
+ }
11
+ //#endregion
12
+ export { r as default, n as metadata };
13
+
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/pages/templates/compose/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplateComposeWizardView } from '../../../views/templates/compose';\n\nexport const metadata: Metadata = {\n title: '建立模板(表單 + 流程) | BPM Admin',\n description: '以精靈式流程一次完成 BPM 表單與簽核流程設計並發佈。',\n};\n\n/**\n * Drop-in Next.js App Router page for the unified template wizard.\n *\n * Consumer usage in `app/templates/compose/page.tsx`:\n *\n * ```ts\n * export { default, metadata } from '@rytass/bpm-core-react/pages/templates/compose';\n * ```\n */\nexport default function TemplateComposePage(): ReactElement {\n return <TemplateComposeWizardView />;\n}\n"],"mappings":";;;AAIA,IAAa,IAAqB;CAChC,OAAO;CACP,aAAa;AACf;AAWA,SAAwB,IAAoC;CAC1D,OAAO,kBAAC,GAAD,CAA4B,CAAA;AACrC"}
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../views/templates/designer/index.cjs");let t=require("react/jsx-runtime");var n={title:`流程設計器 | BPM Admin`,description:`以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.TemplateDesignerView,{templateId:r})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../../chunks/designer-DCn6_v4b.cjs");let t=require("react/jsx-runtime");var n={title:`流程設計器 | BPM Admin`,description:`以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。`};async function r({params:n}){let{id:r}=await n;return(0,t.jsx)(e.t,{aiAssistantAvailable:!!process.env.OPENAI_API_KEY,showAiAssistant:process.env.BPM_AI_ASSISTANT_ENABLED===`true`,showDryRun:process.env.BPM_TEMPLATE_DRY_RUN_ENABLED!==`false`,templateId:r})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/pages/templates/designer/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplateDesignerView } from '../../../views/templates/designer';\n\nexport const metadata: Metadata = {\n title: '流程設計器 | BPM Admin',\n description: '以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。',\n};\n\nexport default async function TemplateDesignerPage({\n params,\n}: {\n readonly params: Promise<{ readonly id: string }>;\n}): Promise<ReactElement> {\n const { id } = await params;\n return <TemplateDesignerView templateId={id} />;\n}\n"],"mappings":"iMAIA,IAAa,EAAqB,CAChC,MAAO,oBACP,YAAa,+BACf,EAEA,eAA8B,EAAqB,CACjD,UAGwB,CACxB,GAAM,CAAE,MAAO,MAAM,EACrB,OAAO,EAAA,EAAA,KAAC,EAAA,qBAAD,CAAsB,WAAY,CAAK,CAAA,CAChD"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/pages/templates/designer/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplateDesignerView } from '../../../views/templates/designer';\n\nexport const metadata: Metadata = {\n title: '流程設計器 | BPM Admin',\n description: '以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。',\n};\n\nexport default async function TemplateDesignerPage({\n params,\n}: {\n readonly params: Promise<{ readonly id: string }>;\n}): Promise<ReactElement> {\n const { id } = await params;\n\n // AI assistant is opt-in per deployment. `BPM_AI_ASSISTANT_ENABLED` shows the\n // feature; `OPENAI_API_KEY` decides whether it's usable (else a disabled\n // placeholder). Both are server-only env on the Next.js host.\n //\n // The dry-run button shows by default; set `BPM_TEMPLATE_DRY_RUN_ENABLED` to\n // `'false'` to hide it for a deployment.\n return (\n <TemplateDesignerView\n aiAssistantAvailable={Boolean(process.env.OPENAI_API_KEY)}\n showAiAssistant={process.env.BPM_AI_ASSISTANT_ENABLED === 'true'}\n showDryRun={process.env.BPM_TEMPLATE_DRY_RUN_ENABLED !== 'false'}\n templateId={id}\n />\n );\n}\n"],"mappings":"2LAIA,IAAa,EAAqB,CAChC,MAAO,oBACP,YAAa,+BACf,EAEA,eAA8B,EAAqB,CACjD,UAGwB,CACxB,GAAM,CAAE,MAAO,MAAM,EAQrB,OACE,EAAA,EAAA,KAAC,EAAA,EAAD,CACE,qBAAsB,EAAQ,QAAQ,IAAI,eAC1C,gBAAiB,QAAQ,IAAI,2BAA6B,OAC1D,WAAY,QAAQ,IAAI,+BAAiC,QACzD,WAAY,CACb,CAAA,CAEL"}
@@ -1,4 +1,4 @@
1
- import { TemplateDesignerView as e } from "../../../views/templates/designer/index.js";
1
+ import { t as e } from "../../../chunks/designer-mOMxJ0Py.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/pages/templates/designer/index.tsx
4
4
  var n = {
@@ -7,7 +7,12 @@ var n = {
7
7
  };
8
8
  async function r({ params: n }) {
9
9
  let { id: r } = await n;
10
- return /* @__PURE__ */ t(e, { templateId: r });
10
+ return /* @__PURE__ */ t(e, {
11
+ aiAssistantAvailable: !!process.env.OPENAI_API_KEY,
12
+ showAiAssistant: process.env.BPM_AI_ASSISTANT_ENABLED === "true",
13
+ showDryRun: process.env.BPM_TEMPLATE_DRY_RUN_ENABLED !== "false",
14
+ templateId: r
15
+ });
11
16
  }
12
17
  //#endregion
13
18
  export { r as default, n as metadata };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/pages/templates/designer/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplateDesignerView } from '../../../views/templates/designer';\n\nexport const metadata: Metadata = {\n title: '流程設計器 | BPM Admin',\n description: '以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。',\n};\n\nexport default async function TemplateDesignerPage({\n params,\n}: {\n readonly params: Promise<{ readonly id: string }>;\n}): Promise<ReactElement> {\n const { id } = await params;\n return <TemplateDesignerView templateId={id} />;\n}\n"],"mappings":";;;AAIA,IAAa,IAAqB;CAChC,OAAO;CACP,aAAa;AACf;AAEA,eAA8B,EAAqB,EACjD,aAGwB;CACxB,IAAM,EAAE,UAAO,MAAM;CACrB,OAAO,kBAAC,GAAD,EAAsB,YAAY,EAAK,CAAA;AAChD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/pages/templates/designer/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplateDesignerView } from '../../../views/templates/designer';\n\nexport const metadata: Metadata = {\n title: '流程設計器 | BPM Admin',\n description: '以視覺化設計器編輯 BPM 簽核流程節點、條件與發佈版本。',\n};\n\nexport default async function TemplateDesignerPage({\n params,\n}: {\n readonly params: Promise<{ readonly id: string }>;\n}): Promise<ReactElement> {\n const { id } = await params;\n\n // AI assistant is opt-in per deployment. `BPM_AI_ASSISTANT_ENABLED` shows the\n // feature; `OPENAI_API_KEY` decides whether it's usable (else a disabled\n // placeholder). Both are server-only env on the Next.js host.\n //\n // The dry-run button shows by default; set `BPM_TEMPLATE_DRY_RUN_ENABLED` to\n // `'false'` to hide it for a deployment.\n return (\n <TemplateDesignerView\n aiAssistantAvailable={Boolean(process.env.OPENAI_API_KEY)}\n showAiAssistant={process.env.BPM_AI_ASSISTANT_ENABLED === 'true'}\n showDryRun={process.env.BPM_TEMPLATE_DRY_RUN_ENABLED !== 'false'}\n templateId={id}\n />\n );\n}\n"],"mappings":";;;AAIA,IAAa,IAAqB;CAChC,OAAO;CACP,aAAa;AACf;AAEA,eAA8B,EAAqB,EACjD,aAGwB;CACxB,IAAM,EAAE,UAAO,MAAM;CAQrB,OACE,kBAAC,GAAD;EACE,sBAAsB,EAAQ,QAAQ,IAAI;EAC1C,iBAAiB,QAAQ,IAAI,6BAA6B;EAC1D,YAAY,QAAQ,IAAI,iCAAiC;EACzD,YAAY;CACb,CAAA;AAEL"}
@@ -1,2 +1,2 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../chunks/templates-w96t83N-.cjs");let t=require("react/jsx-runtime");var n={title:`流程範本 | BPM Admin`,description:`管理 BPM 流程範本,建立、編輯與發佈簽核模板。`};function r(){return(0,t.jsx)(e.t,{})}exports.default=r,exports.metadata=n;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../../views/templates/index.cjs");let t=require("react/jsx-runtime");var n={title:`簽核模板 | BPM Admin`,description:`設計簽核表單與流程,建立、編輯與發佈簽核模板。`};function r(){return(0,t.jsx)(e.TemplatesView,{})}exports.default=r,exports.metadata=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/pages/templates/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplatesView } from '../../views/templates';\n\nexport const metadata: Metadata = {\n title: '流程範本 | BPM Admin',\n description: '管理 BPM 流程範本,建立、編輯與發佈簽核模板。',\n};\n\nexport default function TemplatesPage(): ReactElement {\n return <TemplatesView />;\n}\n"],"mappings":"yLAIA,IAAa,EAAqB,CAChC,MAAO,mBACP,YAAa,2BACf,EAEA,SAAwB,GAA8B,CACpD,OAAO,EAAA,EAAA,KAAC,EAAA,EAAD,CAAgB,CAAA,CACzB"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/pages/templates/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplatesView } from '../../views/templates';\n\nexport const metadata: Metadata = {\n title: '簽核模板 | BPM Admin',\n description: '設計簽核表單與流程,建立、編輯與發佈簽核模板。',\n};\n\nexport default function TemplatesPage(): ReactElement {\n return <TemplatesView />;\n}\n"],"mappings":"qLAIA,IAAa,EAAqB,CAChC,MAAO,mBACP,YAAa,yBACf,EAEA,SAAwB,GAA8B,CACpD,OAAO,EAAA,EAAA,KAAC,EAAA,cAAD,CAAgB,CAAA,CACzB"}
@@ -1,9 +1,9 @@
1
- import { t as e } from "../../chunks/templates-D44FSB46.js";
1
+ import { TemplatesView as e } from "../../views/templates/index.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/pages/templates/index.tsx
4
4
  var n = {
5
- title: "流程範本 | BPM Admin",
6
- description: "管理 BPM 流程範本,建立、編輯與發佈簽核模板。"
5
+ title: "簽核模板 | BPM Admin",
6
+ description: "設計簽核表單與流程,建立、編輯與發佈簽核模板。"
7
7
  };
8
8
  function r() {
9
9
  return /* @__PURE__ */ t(e, {});
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/pages/templates/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplatesView } from '../../views/templates';\n\nexport const metadata: Metadata = {\n title: '流程範本 | BPM Admin',\n description: '管理 BPM 流程範本,建立、編輯與發佈簽核模板。',\n};\n\nexport default function TemplatesPage(): ReactElement {\n return <TemplatesView />;\n}\n"],"mappings":";;;AAIA,IAAa,IAAqB;CAChC,OAAO;CACP,aAAa;AACf;AAEA,SAAwB,IAA8B;CACpD,OAAO,kBAAC,GAAD,CAAgB,CAAA;AACzB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/pages/templates/index.tsx"],"sourcesContent":["import type { Metadata } from 'next';\nimport type { ReactElement } from 'react';\nimport { TemplatesView } from '../../views/templates';\n\nexport const metadata: Metadata = {\n title: '簽核模板 | BPM Admin',\n description: '設計簽核表單與流程,建立、編輯與發佈簽核模板。',\n};\n\nexport default function TemplatesPage(): ReactElement {\n return <TemplatesView />;\n}\n"],"mappings":";;;AAIA,IAAa,IAAqB;CAChC,OAAO;CACP,aAAa;AACf;AAEA,SAAwB,IAA8B;CACpD,OAAO,kBAAC,GAAD,CAAgB,CAAA;AACzB"}
@@ -1,2 +1,2 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/approval-instance-list-page-C5ZKPHdA.cjs");let t=require("react/jsx-runtime");function n(){return(0,t.jsx)(e.t,{defaultState:null,description:`查看抄送給你的簽核案件。`,emptyMessage:`目前沒有抄送給你的簽核案件。`,searchPlaceholder:`關鍵字:搜尋案件、發起人、模板或狀態`,title:`抄送給我`,view:`CC`})}exports.CcView=n;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/approval-instance-list-page-BMUKxzcz.cjs");let t=require("react/jsx-runtime");function n(){return(0,t.jsx)(e.t,{defaultState:null,description:`查看抄送給你的簽核案件。`,emptyMessage:`目前沒有抄送給你的簽核案件。`,searchPlaceholder:`關鍵字:搜尋案件、發起人、模板或狀態`,title:`抄送給我`,view:`CC`})}exports.CcView=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as e } from "../../chunks/approval-instance-list-page-BF2r5D2-.js";
2
+ import { t as e } from "../../chunks/approval-instance-list-page-YZcGGDD8.js";
3
3
  import { jsx as t } from "react/jsx-runtime";
4
4
  //#region src/views/cc/CcView.tsx
5
5
  function n() {
@@ -1,2 +1,2 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/dashboard-page-CddG1MnK.cjs");let t=require("react/jsx-runtime");function n(){return(0,t.jsx)(e.t,{})}exports.DashboardView=n;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../chunks/dashboard-page-DwHQY6Ki.cjs");let t=require("react/jsx-runtime");function n(){return(0,t.jsx)(e.t,{})}exports.DashboardView=n;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as e } from "../../chunks/dashboard-page-Ib8srCMy.js";
2
+ import { t as e } from "../../chunks/dashboard-page-DJ9vOPga.js";
3
3
  import { jsx as t } from "react/jsx-runtime";
4
4
  //#region src/views/dashboard/DashboardView.tsx
5
5
  function n() {
@@ -1,9 +1,18 @@
1
1
  import { ReactElement } from 'react';
2
+ import { FormDefinitionSchema, FormUiSchema } from '@rytass/bpm-core-shared/form';
2
3
  export interface FormBuilderViewProps {
3
4
  /**
4
- * The id of the form definition being edited. Routed via the host's
5
- * dynamic segment (e.g. `[id]` in Next.js).
5
+ * Initial (or controlled) schema value.
6
+ * If omitted, the builder starts with empty schema / uiSchema.
6
7
  */
7
- readonly formId: string;
8
+ readonly value?: {
9
+ readonly schema: FormDefinitionSchema;
10
+ readonly uiSchema: FormUiSchema;
11
+ };
12
+ /** Called whenever the schema or uiSchema changes. */
13
+ readonly onChange?: (next: {
14
+ readonly schema: FormDefinitionSchema;
15
+ readonly uiSchema: FormUiSchema;
16
+ }) => void;
8
17
  }
9
- export declare function FormBuilderView({ formId }: FormBuilderViewProps): ReactElement;
18
+ export declare function FormBuilderView({ onChange, value, }: FormBuilderViewProps): ReactElement;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../chunks/builder-DVE9zIKH.cjs");exports.FormBuilderView=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../chunks/FormBuilderView-B_KGPjlp.cjs");exports.FormBuilderView=e.t;
@@ -1,2 +1,2 @@
1
- import { t as e } from "../../../chunks/builder-BLVnnpnP.js";
1
+ import { t as e } from "../../../chunks/FormBuilderView-D8DrQOXD.js";
2
2
  export { e as FormBuilderView };
@@ -1,6 +1,6 @@
1
1
  import { ReactElement } from 'react';
2
2
  interface JsonCodeEditorProps {
3
- readonly disabled: boolean;
3
+ readonly disabled?: boolean;
4
4
  readonly height: string;
5
5
  readonly name: string;
6
6
  readonly onChange: (value: string) => void;