@rytass/bpm-core-react 0.3.7 → 0.4.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.
- package/CHANGELOG.md +138 -0
- package/README.md +69 -4
- package/dist/chunks/approval-instance-list-page-BtEc8Cs3.js +278 -0
- package/dist/chunks/approval-instance-list-page-BtEc8Cs3.js.map +1 -0
- package/dist/chunks/approval-instance-list-page-UNIIgUZy.cjs +2 -0
- package/dist/chunks/approval-instance-list-page-UNIIgUZy.cjs.map +1 -0
- package/dist/chunks/auth-provider-D2P-qWmY.cjs +2 -0
- package/dist/chunks/auth-provider-D2P-qWmY.cjs.map +1 -0
- package/dist/chunks/auth-provider-TTO9eNZV.js +83 -0
- package/dist/chunks/auth-provider-TTO9eNZV.js.map +1 -0
- package/dist/chunks/{builder-D950gct_.js → builder-C3E-8OJu.js} +474 -478
- package/dist/chunks/builder-C3E-8OJu.js.map +1 -0
- package/dist/chunks/builder-f-Q_0NUs.cjs +3 -0
- package/dist/chunks/builder-f-Q_0NUs.cjs.map +1 -0
- package/dist/chunks/categories-B6QZKZRt.cjs +2 -0
- package/dist/chunks/categories-B6QZKZRt.cjs.map +1 -0
- package/dist/chunks/categories-DBPoSrsi.js +382 -0
- package/dist/chunks/categories-DBPoSrsi.js.map +1 -0
- package/dist/chunks/chunk-CMqjfN_6.cjs +1 -0
- package/dist/chunks/dashboard-page-CQRBJxze.js +119 -0
- package/dist/chunks/dashboard-page-CQRBJxze.js.map +1 -0
- package/dist/chunks/dashboard-page-DrDChhg1.cjs +2 -0
- package/dist/chunks/dashboard-page-DrDChhg1.cjs.map +1 -0
- package/dist/chunks/delegations-CFXaJrdX.cjs +2 -0
- package/dist/chunks/delegations-CFXaJrdX.cjs.map +1 -0
- package/dist/chunks/delegations-D5pPEWsP.js +641 -0
- package/dist/chunks/delegations-D5pPEWsP.js.map +1 -0
- package/dist/chunks/delegations-DwbYkNUg.cjs +2 -0
- package/dist/chunks/delegations-DwbYkNUg.cjs.map +1 -0
- package/dist/chunks/delegations-FTLaWo1Y.js +568 -0
- package/dist/chunks/delegations-FTLaWo1Y.js.map +1 -0
- package/dist/chunks/detail-B9JkYNHc.cjs +2 -0
- package/dist/chunks/detail-B9JkYNHc.cjs.map +1 -0
- package/dist/chunks/detail-CSxI04gB.js +1518 -0
- package/dist/chunks/detail-CSxI04gB.js.map +1 -0
- package/dist/chunks/format-date-time-XxBzF0F5.cjs +2 -0
- package/dist/chunks/{format-date-time-26_pFvv4.cjs.map → format-date-time-XxBzF0F5.cjs.map} +1 -1
- package/dist/chunks/login-BfmfCclF.cjs +2 -0
- package/dist/chunks/{login-CQ9MfwcC.cjs.map → login-BfmfCclF.cjs.map} +1 -1
- package/dist/chunks/{login-C20yVxbc.js → login-xgI4wLHe.js} +3 -2
- package/dist/chunks/{login-C20yVxbc.js.map → login-xgI4wLHe.js.map} +1 -1
- package/dist/chunks/notifications-BoNa1BXD.js +193 -0
- package/dist/chunks/notifications-BoNa1BXD.js.map +1 -0
- package/dist/chunks/notifications-a-FCxV02.cjs +2 -0
- package/dist/chunks/notifications-a-FCxV02.cjs.map +1 -0
- package/dist/chunks/orgs-BIiqzHvb.cjs +2 -0
- package/dist/chunks/orgs-BIiqzHvb.cjs.map +1 -0
- package/dist/chunks/orgs-Cc18umVt.js +1944 -0
- package/dist/chunks/orgs-Cc18umVt.js.map +1 -0
- package/dist/chunks/router-adapter-BdHZXLS3.js +23 -0
- package/dist/chunks/router-adapter-BdHZXLS3.js.map +1 -0
- package/dist/chunks/router-adapter-BybHrCNP.cjs +2 -0
- package/dist/chunks/router-adapter-BybHrCNP.cjs.map +1 -0
- package/dist/chunks/routes-config-2aKbWq2H.cjs.map +1 -1
- package/dist/chunks/routes-config-dxahImVe.js.map +1 -1
- package/dist/chunks/templates-CL8bPvgn.cjs +2 -0
- package/dist/chunks/templates-CL8bPvgn.cjs.map +1 -0
- package/dist/chunks/templates-DNfDOPGm.js +380 -0
- package/dist/chunks/templates-DNfDOPGm.js.map +1 -0
- package/dist/chunks/users-CUY139DF.js +214 -0
- package/dist/chunks/users-CUY139DF.js.map +1 -0
- package/dist/chunks/users-qghSMtLn.cjs +2 -0
- package/dist/chunks/users-qghSMtLn.cjs.map +1 -0
- package/dist/components/approval-instance-list-page.d.ts +1 -2
- package/dist/components/bpm-notification-bell-button.d.ts +22 -0
- package/dist/components/dashboard-page.d.ts +1 -4
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +206 -97
- package/dist/index.js.map +1 -1
- package/dist/lib/notification-drawer-provider.d.ts +3 -2
- package/dist/lib/notification-unread-provider.d.ts +6 -5
- package/dist/lib/providers.d.ts +3 -2
- package/dist/lib/router-adapter.d.ts +2 -2
- package/dist/lib/routes-config.d.ts +11 -3
- package/dist/lib/use-bpm-logout.d.ts +12 -0
- package/dist/lib/use-bpm-member.d.ts +11 -0
- package/dist/pages/admin/delegations/index.cjs +1 -1
- package/dist/pages/admin/delegations/index.js +1 -1
- package/dist/pages/admin/orgs/index.cjs +1 -1
- package/dist/pages/admin/orgs/index.js +1 -1
- package/dist/pages/admin/users/index.cjs +1 -1
- package/dist/pages/admin/users/index.js +1 -1
- package/dist/pages/delegations/index.cjs +1 -1
- package/dist/pages/delegations/index.js +1 -1
- package/dist/pages/forms/builder/index.cjs +1 -1
- package/dist/pages/forms/builder/index.js +1 -1
- package/dist/pages/instances/detail/index.cjs +1 -1
- package/dist/pages/instances/detail/index.js +1 -1
- package/dist/pages/login/index.cjs +1 -1
- package/dist/pages/login/index.js +1 -1
- package/dist/pages/settings/notifications/index.cjs +1 -1
- package/dist/pages/settings/notifications/index.js +1 -1
- package/dist/pages/templates/categories/index.cjs +1 -1
- package/dist/pages/templates/categories/index.js +1 -1
- package/dist/pages/templates/index.cjs +1 -1
- package/dist/pages/templates/index.js +1 -1
- package/dist/views/admin/delegations/AdminDelegationsView.d.ts +1 -4
- package/dist/views/admin/delegations/index.cjs +1 -1
- package/dist/views/admin/delegations/index.js +1 -1
- package/dist/views/admin/index.cjs +1 -1
- package/dist/views/admin/index.js +3 -3
- package/dist/views/admin/orgs/AdminOrgsView.d.ts +1 -4
- package/dist/views/admin/orgs/index.cjs +1 -1
- package/dist/views/admin/orgs/index.js +1 -1
- package/dist/views/admin/users/AdminUsersView.d.ts +1 -4
- package/dist/views/admin/users/index.cjs +1 -1
- package/dist/views/admin/users/index.js +1 -1
- package/dist/views/cc/CcView.d.ts +1 -3
- package/dist/views/cc/index.cjs +1 -1
- package/dist/views/cc/index.cjs.map +1 -1
- package/dist/views/cc/index.js +2 -3
- package/dist/views/cc/index.js.map +1 -1
- package/dist/views/dashboard/DashboardView.d.ts +1 -3
- package/dist/views/dashboard/index.cjs +1 -1
- package/dist/views/dashboard/index.cjs.map +1 -1
- package/dist/views/dashboard/index.js +3 -3
- package/dist/views/dashboard/index.js.map +1 -1
- package/dist/views/delegations/DelegationsView.d.ts +1 -4
- package/dist/views/delegations/index.cjs +1 -1
- package/dist/views/delegations/index.js +1 -1
- package/dist/views/forms/FormsView.d.ts +1 -3
- package/dist/views/forms/builder/index.cjs +1 -1
- package/dist/views/forms/builder/index.js +1 -1
- package/dist/views/forms/index.cjs +1 -1
- package/dist/views/forms/index.cjs.map +1 -1
- package/dist/views/forms/index.js +95 -99
- package/dist/views/forms/index.js.map +1 -1
- package/dist/views/inbox/InboxView.d.ts +1 -3
- package/dist/views/inbox/index.cjs +1 -1
- package/dist/views/inbox/index.cjs.map +1 -1
- package/dist/views/inbox/index.js +91 -94
- package/dist/views/inbox/index.js.map +1 -1
- package/dist/views/instances/detail/index.cjs +1 -1
- package/dist/views/instances/detail/index.js +1 -1
- package/dist/views/instances/new/index.cjs +1 -1
- package/dist/views/instances/new/index.cjs.map +1 -1
- package/dist/views/instances/new/index.js +71 -77
- package/dist/views/instances/new/index.js.map +1 -1
- package/dist/views/login/index.cjs +1 -1
- package/dist/views/login/index.js +1 -1
- package/dist/views/root/RootView.d.ts +1 -3
- package/dist/views/search/SearchView.d.ts +1 -3
- package/dist/views/search/index.cjs +1 -1
- package/dist/views/search/index.cjs.map +1 -1
- package/dist/views/search/index.js +2 -3
- package/dist/views/search/index.js.map +1 -1
- package/dist/views/sent/SentView.d.ts +1 -3
- package/dist/views/sent/index.cjs +1 -1
- package/dist/views/sent/index.cjs.map +1 -1
- package/dist/views/sent/index.js +2 -3
- package/dist/views/sent/index.js.map +1 -1
- package/dist/views/settings/index.cjs +1 -1
- package/dist/views/settings/index.js +1 -1
- package/dist/views/settings/notifications/SettingsNotificationsView.d.ts +1 -4
- package/dist/views/settings/notifications/index.cjs +1 -1
- package/dist/views/settings/notifications/index.js +1 -1
- package/dist/views/templates/TemplatesView.d.ts +1 -4
- package/dist/views/templates/categories/TemplateCategoriesView.d.ts +1 -4
- package/dist/views/templates/categories/index.cjs +1 -1
- package/dist/views/templates/categories/index.js +1 -1
- package/dist/views/templates/designer/TemplateDesignerView.d.ts +1 -2
- package/dist/views/templates/designer/index.cjs +7 -7
- package/dist/views/templates/designer/index.cjs.map +1 -1
- package/dist/views/templates/designer/index.js +707 -711
- package/dist/views/templates/designer/index.js.map +1 -1
- package/dist/views/templates/index.cjs +1 -1
- package/dist/views/templates/index.js +2 -2
- package/dist/views/templates/versions/TemplateVersionsView.d.ts +1 -2
- package/dist/views/templates/versions/index.cjs +1 -1
- package/dist/views/templates/versions/index.cjs.map +1 -1
- package/dist/views/templates/versions/index.js +45 -49
- package/dist/views/templates/versions/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/app-navigation.css +0 -1
- package/dist/chunks/app-navigation-BSkMsEhy.js +0 -268
- package/dist/chunks/app-navigation-BSkMsEhy.js.map +0 -1
- package/dist/chunks/app-navigation-KnlJCUp1.cjs +0 -2
- package/dist/chunks/app-navigation-KnlJCUp1.cjs.map +0 -1
- package/dist/chunks/approval-instance-list-page-CVXgE2K3.cjs +0 -2
- package/dist/chunks/approval-instance-list-page-CVXgE2K3.cjs.map +0 -1
- package/dist/chunks/approval-instance-list-page-CqNdoZqx.js +0 -282
- package/dist/chunks/approval-instance-list-page-CqNdoZqx.js.map +0 -1
- package/dist/chunks/auth-provider-BV8Iiwfb.cjs +0 -2
- package/dist/chunks/auth-provider-BV8Iiwfb.cjs.map +0 -1
- package/dist/chunks/auth-provider-Bnox5gsx.js +0 -98
- package/dist/chunks/auth-provider-Bnox5gsx.js.map +0 -1
- package/dist/chunks/builder-CMlJfQHE.cjs +0 -3
- package/dist/chunks/builder-CMlJfQHE.cjs.map +0 -1
- package/dist/chunks/builder-D950gct_.js.map +0 -1
- package/dist/chunks/categories-5yEM3p3N.cjs +0 -2
- package/dist/chunks/categories-5yEM3p3N.cjs.map +0 -1
- package/dist/chunks/categories-BIpOG451.js +0 -387
- package/dist/chunks/categories-BIpOG451.js.map +0 -1
- package/dist/chunks/dashboard-page-1K_jQXQk.cjs +0 -2
- package/dist/chunks/dashboard-page-1K_jQXQk.cjs.map +0 -1
- package/dist/chunks/dashboard-page-R_T2OEiE.js +0 -122
- package/dist/chunks/dashboard-page-R_T2OEiE.js.map +0 -1
- package/dist/chunks/delegations-B2j-wNEO.js +0 -646
- package/dist/chunks/delegations-B2j-wNEO.js.map +0 -1
- package/dist/chunks/delegations-CsB9ozLu.cjs +0 -2
- package/dist/chunks/delegations-CsB9ozLu.cjs.map +0 -1
- package/dist/chunks/delegations-CvtwTXNP.cjs +0 -2
- package/dist/chunks/delegations-CvtwTXNP.cjs.map +0 -1
- package/dist/chunks/delegations-dKodb0WW.js +0 -573
- package/dist/chunks/delegations-dKodb0WW.js.map +0 -1
- package/dist/chunks/detail-BcGAqJ_R.js +0 -1523
- package/dist/chunks/detail-BcGAqJ_R.js.map +0 -1
- package/dist/chunks/detail-CqjqLd65.cjs +0 -2
- package/dist/chunks/detail-CqjqLd65.cjs.map +0 -1
- package/dist/chunks/format-date-time-26_pFvv4.cjs +0 -2
- package/dist/chunks/login-CQ9MfwcC.cjs +0 -2
- package/dist/chunks/notifications-2swRqDPF.js +0 -198
- package/dist/chunks/notifications-2swRqDPF.js.map +0 -1
- package/dist/chunks/notifications-BaYDebFt.cjs +0 -2
- package/dist/chunks/notifications-BaYDebFt.cjs.map +0 -1
- package/dist/chunks/orgs-CuHxxd_n.js +0 -1949
- package/dist/chunks/orgs-CuHxxd_n.js.map +0 -1
- package/dist/chunks/orgs-YMiVLNvL.cjs +0 -2
- package/dist/chunks/orgs-YMiVLNvL.cjs.map +0 -1
- package/dist/chunks/templates-DTkbSgFY.cjs +0 -2
- package/dist/chunks/templates-DTkbSgFY.cjs.map +0 -1
- package/dist/chunks/templates-DoDWM68t.js +0 -384
- package/dist/chunks/templates-DoDWM68t.js.map +0 -1
- package/dist/chunks/users-3ySyUW4u.cjs +0 -2
- package/dist/chunks/users-3ySyUW4u.cjs.map +0 -1
- package/dist/chunks/users-sMfrSjRQ.js +0 -219
- package/dist/chunks/users-sMfrSjRQ.js.map +0 -1
- package/dist/components/app-navigation.d.ts +0 -41
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,144 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
Releases are managed by [`nx release`](https://nx.dev/recipes/nx-release) with
|
|
9
9
|
Conventional Commits — see `nx.json` for the release config.
|
|
10
10
|
|
|
11
|
+
## 0.4.0 — 2026-05-28
|
|
12
|
+
|
|
13
|
+
### Breaking
|
|
14
|
+
|
|
15
|
+
- **`<AppLayout>` removed from the public surface.** The 4-group
|
|
16
|
+
Mezzanine `<Layout>` + `<Navigation>` shell BPM used to ship is no
|
|
17
|
+
longer part of `@rytass/bpm-core-react`. Hosts are expected to own
|
|
18
|
+
their navigation chrome and mount BPM views inside their existing
|
|
19
|
+
sidebar / top bar. Together with `AppLayout`, the following exports
|
|
20
|
+
are gone: `AppLayout`, `AppLayoutProps`, `AppNavigationGroup`.
|
|
21
|
+
- **Views no longer self-wrap.** Every view (`InboxView`, `FormsView`,
|
|
22
|
+
`TemplatesView`, `AdminOrgsView`, … 13 in total) used to render
|
|
23
|
+
`<AppLayout activeHref=…>…</AppLayout>` internally. They now return
|
|
24
|
+
the page content fragment (a `<PageHeader>` + `<SectionGroup>`
|
|
25
|
+
composition) so a host layout can wrap them. The `activeHref?` prop
|
|
26
|
+
on `TemplatesView` / `DelegationsView` / `TemplateDesignerView` /
|
|
27
|
+
`TemplateVersionsView` / `TemplateCategoriesView` / `AdminOrgsView` /
|
|
28
|
+
`AdminUsersView` / `AdminDelegationsView` /
|
|
29
|
+
`SettingsNotificationsView` is removed because it had no consumer
|
|
30
|
+
after the layout was lifted out.
|
|
31
|
+
- **`DashboardPage` props simplified to `{}`** — `activeHref` removed
|
|
32
|
+
for the same reason. The component still renders the five-metric
|
|
33
|
+
workflow dashboard fragment unchanged.
|
|
34
|
+
- **`ApprovalInstanceListPage` no longer accepts `activeHref`.** The
|
|
35
|
+
thin delegators (`SentView`, `CcView`, `SearchView`) drop the
|
|
36
|
+
hard-coded `activeHref` argument accordingly.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`<BPMNotificationBellButton />`** — drop-in notification bell for
|
|
41
|
+
host navigations. Opens the BPM `<NotificationDrawer />` (mounted by
|
|
42
|
+
`<Providers>`) on click and renders an unread-count badge from
|
|
43
|
+
`<NotificationUnreadProvider>`. Visual chrome uses Mezzanine
|
|
44
|
+
`NavigationIconButton`, but the button is decoupled from the
|
|
45
|
+
Mezzanine `<Navigation>` container — drop it anywhere in your nav.
|
|
46
|
+
Hosts that want a fully custom trigger can skip this widget and
|
|
47
|
+
consume `useNotificationDrawer().open` + `useNotificationUnread().unreadCount`
|
|
48
|
+
directly.
|
|
49
|
+
- **`useBPMMember()` hook** — host-facing alias of `useAuth().member`.
|
|
50
|
+
Returns the currently authenticated `ApiMember | null` without
|
|
51
|
+
exposing the broader `useAuth()` surface.
|
|
52
|
+
- **`useBPMLogout()` hook** — host-facing alias of `useAuth().logout`.
|
|
53
|
+
Returns `() => Promise<void>` that calls `logoutApi()` and redirects
|
|
54
|
+
to the configured `loginPath`. Mount on host logout buttons / menu
|
|
55
|
+
items so the host nav does not need to import `useAuth()` directly.
|
|
56
|
+
|
|
57
|
+
### Removed
|
|
58
|
+
|
|
59
|
+
- `AppLayout`, `AppLayoutProps`, `AppNavigationGroup` (see Breaking).
|
|
60
|
+
- `components/app-navigation.tsx` and `components/app-navigation.module.scss`
|
|
61
|
+
are deleted from the lib source tree.
|
|
62
|
+
|
|
63
|
+
### Why a minor (0.x semver)
|
|
64
|
+
|
|
65
|
+
The library is still in `0.x` so breaking changes ride a minor bump per
|
|
66
|
+
the project's release convention. The change targets a single consumer
|
|
67
|
+
in the monorepo (`apps/client`) plus any external consumer wiring
|
|
68
|
+
`@rytass/bpm-core-react` into their own host — both are expected to
|
|
69
|
+
follow the migration recipe below.
|
|
70
|
+
|
|
71
|
+
### Migration
|
|
72
|
+
|
|
73
|
+
If your host re-exported page shims (`pages/<feature>`), you only need
|
|
74
|
+
to add a layout wrapper:
|
|
75
|
+
|
|
76
|
+
```diff
|
|
77
|
+
// app/layout.tsx
|
|
78
|
+
import { BPMNextProviders } from '@rytass/bpm-core-react/next';
|
|
79
|
+
+ import { MyHostLayout } from './_components/host-layout';
|
|
80
|
+
|
|
81
|
+
export default function RootLayout({ children }) {
|
|
82
|
+
return (
|
|
83
|
+
<html lang="zh-TW"><body>
|
|
84
|
+
- <BPMNextProviders>{children}</BPMNextProviders>
|
|
85
|
+
+ <BPMNextProviders>
|
|
86
|
+
+ <MyHostLayout>{children}</MyHostLayout>
|
|
87
|
+
+ </BPMNextProviders>
|
|
88
|
+
</body></html>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For a reference `MyHostLayout` that reproduces the legacy 4-group BPM
|
|
94
|
+
nav using `useBPMRoutes` + `useBPMMember` + `useBPMLogout` +
|
|
95
|
+
`<BPMNotificationBellButton />`, copy
|
|
96
|
+
`apps/client/src/app/_components/host-layout.tsx` from the BPMCore
|
|
97
|
+
repo. Adjust groups, branding, and which routes you expose to match
|
|
98
|
+
your host.
|
|
99
|
+
|
|
100
|
+
If your host imported `AppLayout` directly, swap to the same host
|
|
101
|
+
layout pattern — `AppLayout` is no longer exported.
|
|
102
|
+
|
|
103
|
+
### Documentation
|
|
104
|
+
|
|
105
|
+
- README's `Usage` section rewritten around the new integration shape
|
|
106
|
+
(host owns the layout; BPM provides widgets).
|
|
107
|
+
- New `docs/integration-guide.md` with a step-by-step host integration
|
|
108
|
+
walkthrough, the recommended 4-group nav structure (`我的工作` /
|
|
109
|
+
`查詢與代理` / `簽核設計` / `系統管理`), and notes on consuming the
|
|
110
|
+
widgets / hooks.
|
|
111
|
+
- `docs/api-reference.md` updated to reflect the new root-barrel
|
|
112
|
+
surface (`useBPMMember`, `useBPMLogout`, `BPMNotificationBellButton`)
|
|
113
|
+
and the removal of `AppLayout` / `AppLayoutProps` /
|
|
114
|
+
`AppNavigationGroup`.
|
|
115
|
+
|
|
116
|
+
## 0.3.8 — 2026-05-28
|
|
117
|
+
|
|
118
|
+
### Fixed
|
|
119
|
+
|
|
120
|
+
- **`React.ReactElement` return-type annotation** in
|
|
121
|
+
`BPMRoutesProvider` and `RouterAdapterProvider` referenced the
|
|
122
|
+
unimported `React` namespace. Compiled fine under typical `jsx:
|
|
123
|
+
'react-jsx'` configs (which globalize the namespace) but broke
|
|
124
|
+
under stricter consumer setups. Now uses the directly-imported
|
|
125
|
+
`ReactElement` type to match the rest of the file.
|
|
126
|
+
|
|
127
|
+
### Changed
|
|
128
|
+
|
|
129
|
+
- **Dashboard "未讀通知" metric tile** now navigates to `routes.inbox()`
|
|
130
|
+
instead of `routes.notifications()`. The previous target had no
|
|
131
|
+
matching `pages/notifications` shim, so the click 404'd on hosts
|
|
132
|
+
that hadn't built their own page at that path. Inbox is the
|
|
133
|
+
most-actionable next step for an unread-notification click; the
|
|
134
|
+
notification drawer bell stays available globally for in-place
|
|
135
|
+
review.
|
|
136
|
+
|
|
137
|
+
### Documentation
|
|
138
|
+
|
|
139
|
+
- **`BPMRoutes.notifications()` JSDoc clarified** as a host-extension
|
|
140
|
+
point: BPM's built-in views do not navigate to it by default. The
|
|
141
|
+
default factory still returns `'/notifications'` so consumers can
|
|
142
|
+
mount their own page at that path if desired; no BPM-shipped
|
|
143
|
+
`pages/notifications` shim exists.
|
|
144
|
+
|
|
145
|
+
### Why a patch
|
|
146
|
+
|
|
147
|
+
Bug fix + behavior preservation. No public API change.
|
|
148
|
+
|
|
11
149
|
## 0.3.7 — 2026-05-28
|
|
12
150
|
|
|
13
151
|
### Documentation
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This package composes [`@mezzanine-ui/react`](https://www.npmjs.com/package/@mez
|
|
|
6
6
|
|
|
7
7
|
## Status
|
|
8
8
|
|
|
9
|
-
`0.
|
|
9
|
+
`0.4.0` (breaking) — drops the bundled navigation shell. BPM views no longer wrap themselves in an `<AppLayout>` / Mezzanine `<Navigation>`; the host owns the layout chrome and mounts BPM views inside its own sidebar / top bar. New host-facing widgets ship in the root barrel: `useBPMMember`, `useBPMLogout`, `<BPMNotificationBellButton />`. See `CHANGELOG.md` for the migration walkthrough, and `docs/integration-guide.md` for a host integration recipe.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -39,13 +39,78 @@ module.exports = {
|
|
|
39
39
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
|
-
###
|
|
42
|
+
### Host integration shape
|
|
43
|
+
|
|
44
|
+
BPM ships **page bodies, providers, and widgets** — never a full layout.
|
|
45
|
+
The host wires its own `<Layout>` / `<Navigation>` (or its own design
|
|
46
|
+
system equivalent), then drops BPM widgets and views into the slots:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// app/layout.tsx
|
|
50
|
+
import { BPMNextProviders } from '@rytass/bpm-core-react/next';
|
|
51
|
+
import { MyHostLayout } from './host-layout';
|
|
52
|
+
|
|
53
|
+
export default function RootLayout({ children }) {
|
|
54
|
+
return (
|
|
55
|
+
<html lang="zh-TW"><body>
|
|
56
|
+
<BPMNextProviders>
|
|
57
|
+
<MyHostLayout>{children}</MyHostLayout>
|
|
58
|
+
</BPMNextProviders>
|
|
59
|
+
</body></html>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// app/host-layout.tsx
|
|
66
|
+
'use client';
|
|
67
|
+
import {
|
|
68
|
+
BPMNotificationBellButton,
|
|
69
|
+
useBPMLogout,
|
|
70
|
+
useBPMMember,
|
|
71
|
+
useBPMRoutes,
|
|
72
|
+
useRouterAdapter,
|
|
73
|
+
} from '@rytass/bpm-core-react';
|
|
74
|
+
|
|
75
|
+
export function MyHostLayout({ children }) {
|
|
76
|
+
const router = useRouterAdapter();
|
|
77
|
+
const routes = useBPMRoutes();
|
|
78
|
+
const member = useBPMMember();
|
|
79
|
+
const logout = useBPMLogout();
|
|
80
|
+
return (
|
|
81
|
+
<div className="grid grid-cols-[240px_1fr]">
|
|
82
|
+
<aside>
|
|
83
|
+
<h1>My Console</h1>
|
|
84
|
+
<ul>
|
|
85
|
+
<li><a href={routes.dashboard()}>工作台</a></li>
|
|
86
|
+
<li><a href={routes.inbox()}>我的待簽</a></li>
|
|
87
|
+
{/* …host's own nav items… */}
|
|
88
|
+
</ul>
|
|
89
|
+
<div className="flex items-center gap-2">
|
|
90
|
+
<span>{member?.name}</span>
|
|
91
|
+
<BPMNotificationBellButton />
|
|
92
|
+
<button onClick={() => logout()}>登出</button>
|
|
93
|
+
</div>
|
|
94
|
+
</aside>
|
|
95
|
+
<main>{children}</main>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The page shims under `pages/*` are unchanged — they remain one-line
|
|
102
|
+
`{ default, metadata }` re-exports and are mounted under the host
|
|
103
|
+
layout exactly like any other Next.js page:
|
|
43
104
|
|
|
44
105
|
```tsx
|
|
45
|
-
// app/
|
|
46
|
-
export { default, metadata } from '@rytass/bpm-core-react/pages/
|
|
106
|
+
// app/inbox/page.tsx
|
|
107
|
+
export { default, metadata } from '@rytass/bpm-core-react/pages/inbox';
|
|
47
108
|
```
|
|
48
109
|
|
|
110
|
+
For an end-to-end reference layout (with the original 4-group BPM nav
|
|
111
|
+
structure, admin-only filtering, and member display), see
|
|
112
|
+
`apps/client/src/app/_components/host-layout.tsx` in the BPMCore repo.
|
|
113
|
+
|
|
49
114
|
### Framework-agnostic view
|
|
50
115
|
|
|
51
116
|
```tsx
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { r as e } from "./router-adapter-BdHZXLS3.js";
|
|
3
|
+
import { t } from "./format-date-time-CB-LxzqT.js";
|
|
4
|
+
import { r as n } from "./routes-config-dxahImVe.js";
|
|
5
|
+
import { useCallback as r, useEffect as i, useMemo as a, useState as o } from "react";
|
|
6
|
+
import { Filter as s, FilterArea as c, FilterLine as l, FormField as u, Input as d, PageHeader as f, Section as ee, SectionGroup as te, Select as ne, Table as p, Typography as m } from "@mezzanine-ui/react";
|
|
7
|
+
import { resolveMembers as h } from "@rytass/bpm-core-client";
|
|
8
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
9
|
+
import { listApprovalInstancesPage as y, readApprovalInstanceCaseTitle as b } from "@rytass/bpm-core-client/workflow";
|
|
10
|
+
import x from "@mezzanine-ui/react/ContentHeader";
|
|
11
|
+
import { FormFieldLayout as S } from "@mezzanine-ui/core/form";
|
|
12
|
+
import '../approval-instance-list-page.css';var C = { instanceFilterArea: "bpm_instanceFilterArea_qpvJq" }, w = [
|
|
13
|
+
10,
|
|
14
|
+
20,
|
|
15
|
+
50
|
|
16
|
+
], T = [
|
|
17
|
+
{
|
|
18
|
+
id: "ALL",
|
|
19
|
+
name: "全部狀態",
|
|
20
|
+
state: null
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "RUNNING",
|
|
24
|
+
name: "進行中",
|
|
25
|
+
state: "RUNNING"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "APPROVED",
|
|
29
|
+
name: "已通過",
|
|
30
|
+
state: "APPROVED"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "REJECTED",
|
|
34
|
+
name: "已拒絕",
|
|
35
|
+
state: "REJECTED"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "RETURNED",
|
|
39
|
+
name: "已退回",
|
|
40
|
+
state: "RETURNED"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "CANCELLED",
|
|
44
|
+
name: "已取消",
|
|
45
|
+
state: "CANCELLED"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "EXPIRED",
|
|
49
|
+
name: "已逾期",
|
|
50
|
+
state: "EXPIRED"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "DRAFT",
|
|
54
|
+
name: "草稿",
|
|
55
|
+
state: "DRAFT"
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
function E({ defaultState: b, description: E, emptyMessage: O, searchPlaceholder: k, title: N, view: P }) {
|
|
59
|
+
let F = e(), I = n(), [L, R] = o(null), [z, B] = o(/* @__PURE__ */ new Map()), [V, H] = o(1), [U, W] = o(10), [G, K] = o(0), [q, J] = o(!0), [Y, oe] = o([]), [X, se] = o(""), [Z, Q] = o(D(b)), $ = r(async () => {
|
|
60
|
+
J(!0), R(null);
|
|
61
|
+
try {
|
|
62
|
+
let e = await y({
|
|
63
|
+
page: V,
|
|
64
|
+
pageSize: U,
|
|
65
|
+
searchText: X,
|
|
66
|
+
state: Z.state,
|
|
67
|
+
templateId: null,
|
|
68
|
+
view: P
|
|
69
|
+
});
|
|
70
|
+
oe(e.instances.map(re)), K(e.totalCount);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
R(M(e));
|
|
73
|
+
} finally {
|
|
74
|
+
J(!1);
|
|
75
|
+
}
|
|
76
|
+
}, [
|
|
77
|
+
V,
|
|
78
|
+
U,
|
|
79
|
+
X,
|
|
80
|
+
Z,
|
|
81
|
+
P
|
|
82
|
+
]);
|
|
83
|
+
i(() => {
|
|
84
|
+
$();
|
|
85
|
+
}, [$]), i(() => {
|
|
86
|
+
let e = Array.from(new Set(Y.map((e) => e.initiatorMemberId).filter(Boolean)));
|
|
87
|
+
if (e.length === 0) {
|
|
88
|
+
B(/* @__PURE__ */ new Map());
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
let t = !1;
|
|
92
|
+
return (async () => {
|
|
93
|
+
try {
|
|
94
|
+
let n = await h(e);
|
|
95
|
+
if (t) return;
|
|
96
|
+
B(new Map(n.map((e) => [e.memberId, e])));
|
|
97
|
+
} catch {
|
|
98
|
+
if (t) return;
|
|
99
|
+
B(/* @__PURE__ */ new Map());
|
|
100
|
+
}
|
|
101
|
+
})(), () => {
|
|
102
|
+
t = !0;
|
|
103
|
+
};
|
|
104
|
+
}, [Y]);
|
|
105
|
+
let ce = a(() => [
|
|
106
|
+
{
|
|
107
|
+
dataIndex: "caseTitle",
|
|
108
|
+
key: "caseTitle",
|
|
109
|
+
title: "案件",
|
|
110
|
+
width: 300
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: "state",
|
|
114
|
+
render: (e) => /* @__PURE__ */ _(m, {
|
|
115
|
+
color: A(e.state),
|
|
116
|
+
component: "span",
|
|
117
|
+
variant: "body",
|
|
118
|
+
children: e.stateLabel
|
|
119
|
+
}),
|
|
120
|
+
title: "狀態",
|
|
121
|
+
width: 120
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
key: "initiatorMemberId",
|
|
125
|
+
render: (e) => /* @__PURE__ */ _(m, {
|
|
126
|
+
component: "span",
|
|
127
|
+
variant: "body",
|
|
128
|
+
children: j(e.initiatorMemberId, z)
|
|
129
|
+
}),
|
|
130
|
+
title: "發起人",
|
|
131
|
+
width: 180
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
key: "startedAt",
|
|
135
|
+
render: (e) => /* @__PURE__ */ _(m, {
|
|
136
|
+
component: "span",
|
|
137
|
+
variant: "body",
|
|
138
|
+
children: t(e.startedAt)
|
|
139
|
+
}),
|
|
140
|
+
title: "發起時間",
|
|
141
|
+
width: 220
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
key: "completedAt",
|
|
145
|
+
render: (e) => /* @__PURE__ */ _(m, {
|
|
146
|
+
component: "span",
|
|
147
|
+
variant: "body",
|
|
148
|
+
children: t(e.completedAt)
|
|
149
|
+
}),
|
|
150
|
+
title: "完成時間",
|
|
151
|
+
width: 220
|
|
152
|
+
}
|
|
153
|
+
], [z]), le = a(() => ({
|
|
154
|
+
render: (e) => [{
|
|
155
|
+
name: "查看",
|
|
156
|
+
onClick: () => F.push(I.caseDetail(e.id))
|
|
157
|
+
}],
|
|
158
|
+
variant: "base-secondary",
|
|
159
|
+
width: 88
|
|
160
|
+
}), [F]);
|
|
161
|
+
return /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _(f, { children: /* @__PURE__ */ _(x, {
|
|
162
|
+
description: E,
|
|
163
|
+
title: N
|
|
164
|
+
}) }), /* @__PURE__ */ _(te, { children: /* @__PURE__ */ v(ee, {
|
|
165
|
+
filterArea: /* @__PURE__ */ _(c, {
|
|
166
|
+
className: C.instanceFilterArea,
|
|
167
|
+
size: "sub",
|
|
168
|
+
children: /* @__PURE__ */ v(l, { children: [/* @__PURE__ */ _(s, {
|
|
169
|
+
span: 3,
|
|
170
|
+
children: /* @__PURE__ */ _(u, {
|
|
171
|
+
fullWidth: !0,
|
|
172
|
+
layout: S.VERTICAL,
|
|
173
|
+
name: "instanceSearchText",
|
|
174
|
+
children: /* @__PURE__ */ _(d, {
|
|
175
|
+
fullWidth: !0,
|
|
176
|
+
onChange: (e) => {
|
|
177
|
+
se(e.target.value), H(1);
|
|
178
|
+
},
|
|
179
|
+
placeholder: k,
|
|
180
|
+
size: "sub",
|
|
181
|
+
value: X,
|
|
182
|
+
variant: "base"
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
}), /* @__PURE__ */ _(s, {
|
|
186
|
+
span: 2,
|
|
187
|
+
children: /* @__PURE__ */ _(u, {
|
|
188
|
+
fullWidth: !0,
|
|
189
|
+
layout: S.VERTICAL,
|
|
190
|
+
name: "instanceState",
|
|
191
|
+
children: /* @__PURE__ */ _(ne, {
|
|
192
|
+
clearable: !1,
|
|
193
|
+
fullWidth: !0,
|
|
194
|
+
onChange: (e) => {
|
|
195
|
+
Q(ie(e)), H(1);
|
|
196
|
+
},
|
|
197
|
+
options: [...T],
|
|
198
|
+
placeholder: "狀態",
|
|
199
|
+
renderValue: (e) => `狀態:${ae(e)}`,
|
|
200
|
+
size: "sub",
|
|
201
|
+
value: Z
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
})] })
|
|
205
|
+
}),
|
|
206
|
+
children: [
|
|
207
|
+
L ? /* @__PURE__ */ _(m, {
|
|
208
|
+
color: "text-error",
|
|
209
|
+
variant: "body",
|
|
210
|
+
children: L
|
|
211
|
+
}) : null,
|
|
212
|
+
!L && !q && Y.length === 0 ? /* @__PURE__ */ _(m, {
|
|
213
|
+
color: "text-neutral",
|
|
214
|
+
variant: "body",
|
|
215
|
+
children: O
|
|
216
|
+
}) : null,
|
|
217
|
+
/* @__PURE__ */ _(p, {
|
|
218
|
+
actions: le,
|
|
219
|
+
columns: ce,
|
|
220
|
+
dataSource: [...Y],
|
|
221
|
+
fullWidth: !0,
|
|
222
|
+
loading: q,
|
|
223
|
+
pagination: {
|
|
224
|
+
current: V,
|
|
225
|
+
onChange: (e) => {
|
|
226
|
+
H(e);
|
|
227
|
+
},
|
|
228
|
+
onChangePageSize: (e) => {
|
|
229
|
+
H(1), W(e);
|
|
230
|
+
},
|
|
231
|
+
pageSize: U,
|
|
232
|
+
pageSizeLabel: "每頁筆數",
|
|
233
|
+
pageSizeOptions: w,
|
|
234
|
+
renderResultSummary: (e, t, n) => `顯示 ${e}-${t} 筆,共 ${n} 筆`,
|
|
235
|
+
showPageSizeOptions: !0,
|
|
236
|
+
total: G
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
]
|
|
240
|
+
}) })] });
|
|
241
|
+
}
|
|
242
|
+
function re(e) {
|
|
243
|
+
return {
|
|
244
|
+
...e,
|
|
245
|
+
caseTitle: b(e),
|
|
246
|
+
key: e.id,
|
|
247
|
+
stateLabel: k(e.state)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function ie(e) {
|
|
251
|
+
return O(e) ? D(e.state) : T[0];
|
|
252
|
+
}
|
|
253
|
+
function D(e) {
|
|
254
|
+
return T.find((t) => t.state === e) ?? T[0];
|
|
255
|
+
}
|
|
256
|
+
function ae(e) {
|
|
257
|
+
return O(e) ? e.name : T[0].name;
|
|
258
|
+
}
|
|
259
|
+
function O(e) {
|
|
260
|
+
return typeof e == "object" && !!e && "id" in e && "name" in e && "state" in e;
|
|
261
|
+
}
|
|
262
|
+
function k(e) {
|
|
263
|
+
return e === "RUNNING" ? "進行中" : e === "APPROVED" ? "已通過" : e === "REJECTED" ? "已拒絕" : e === "RETURNED" ? "已退回" : e === "CANCELLED" ? "已取消" : e === "EXPIRED" ? "已逾期" : "草稿";
|
|
264
|
+
}
|
|
265
|
+
function A(e) {
|
|
266
|
+
return e === "APPROVED" ? "text-success" : e === "REJECTED" || e === "CANCELLED" || e === "EXPIRED" ? "text-error" : "text-neutral";
|
|
267
|
+
}
|
|
268
|
+
function j(e, t) {
|
|
269
|
+
let n = (e ?? "").trim();
|
|
270
|
+
return n ? t.get(n)?.name ?? n : "未知發起人";
|
|
271
|
+
}
|
|
272
|
+
function M(e) {
|
|
273
|
+
return e instanceof Error ? e.message : "讀取簽核案件失敗。";
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
export { E as t };
|
|
277
|
+
|
|
278
|
+
//# sourceMappingURL=approval-instance-list-page-BtEc8Cs3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-instance-list-page-BtEc8Cs3.js","names":[],"sources":["../../src/components/approval-instance-list-page.module.scss","../../src/components/approval-instance-list-page.tsx"],"sourcesContent":[".instanceFilterArea {\n :global(.mzn-filter-area__actions) {\n display: none;\n }\n\n :global(.mzn-form-field__label-area) {\n display: none;\n }\n\n :global(.mzn-form-field__control-field-slot--main) {\n width: 100%;\n min-width: 0;\n }\n}\n","'use client';\n\nimport type { ChangeEvent, ReactElement } from 'react';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n Input,\n PageHeader,\n Section,\n SectionGroup,\n Select,\n Table,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport { resolveMembers, type MemberProfileRecord } from '@rytass/bpm-core-client';\nimport {\n listApprovalInstancesPage,\n readApprovalInstanceCaseTitle,\n type ApprovalInstanceRecord,\n type ApprovalInstanceState,\n type ApprovalInstanceView,\n} from '@rytass/bpm-core-client/workflow';\nimport { useRouterAdapter } from '../lib/router-adapter';\nimport { useBPMRoutes } from '../lib/routes-config';\nimport { formatDateTime } from '../lib/format-date-time';\nimport styles from './approval-instance-list-page.module.scss';\n\nexport interface ApprovalInstanceListPageProps {\n readonly defaultState: ApprovalInstanceState | null;\n readonly description: string;\n readonly emptyMessage: string;\n readonly searchPlaceholder: string;\n readonly title: string;\n readonly view: ApprovalInstanceView;\n}\n\ntype StateFilterOption = Readonly<{\n id: 'ALL' | ApprovalInstanceState;\n name: string;\n state: ApprovalInstanceState | null;\n}>;\n\ntype ApprovalInstanceRow = Readonly<\n Record<string, unknown> &\n ApprovalInstanceRecord & {\n caseTitle: string;\n key: string;\n stateLabel: string;\n }\n>;\n\nconst INSTANCE_PAGE_SIZE_OPTIONS = [10, 20, 50];\nconst STATE_FILTER_OPTIONS: readonly StateFilterOption[] = [\n { id: 'ALL', name: '全部狀態', state: null },\n { id: 'RUNNING', name: '進行中', state: 'RUNNING' },\n { id: 'APPROVED', name: '已通過', state: 'APPROVED' },\n { id: 'REJECTED', name: '已拒絕', state: 'REJECTED' },\n { id: 'RETURNED', name: '已退回', state: 'RETURNED' },\n { id: 'CANCELLED', name: '已取消', state: 'CANCELLED' },\n { id: 'EXPIRED', name: '已逾期', state: 'EXPIRED' },\n { id: 'DRAFT', name: '草稿', state: 'DRAFT' },\n];\n\n/**\n * Shared list page for any approval-instance \"view\" (inbox / sent / cc /\n * delegated). Caller picks the view + default state filter; the page renders\n * the standard BPM filter bar + paginated table and navigates to\n * `/instances/:id` on row action.\n */\nexport function ApprovalInstanceListPage({\n defaultState,\n description,\n emptyMessage,\n searchPlaceholder,\n title,\n view,\n}: ApprovalInstanceListPageProps): ReactElement {\n const router = useRouterAdapter();\n const routes = useBPMRoutes();\n const [error, setError] = useState<string | null>(null);\n const [initiatorProfilesById, setInitiatorProfilesById] = useState<\n ReadonlyMap<string, MemberProfileRecord>\n >(new Map());\n const [instancePage, setInstancePage] = useState(1);\n const [instancePageSize, setInstancePageSize] = useState(10);\n const [instanceTotalCount, setInstanceTotalCount] = useState(0);\n const [loading, setLoading] = useState(true);\n const [rows, setRows] = useState<readonly ApprovalInstanceRow[]>([]);\n const [searchText, setSearchText] = useState('');\n const [stateFilter, setStateFilter] = useState<StateFilterOption>(\n readStateFilterOption(defaultState),\n );\n\n const refreshInstances = useCallback(async (): Promise<void> => {\n setLoading(true);\n setError(null);\n\n try {\n const result = await listApprovalInstancesPage({\n page: instancePage,\n pageSize: instancePageSize,\n searchText,\n state: stateFilter.state,\n templateId: null,\n view,\n });\n\n setRows(result.instances.map(readApprovalInstanceRow));\n setInstanceTotalCount(result.totalCount);\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setLoading(false);\n }\n }, [instancePage, instancePageSize, searchText, stateFilter, view]);\n\n useEffect((): void => {\n void refreshInstances();\n }, [refreshInstances]);\n\n useEffect((): (() => void) | void => {\n const initiatorMemberIds = Array.from(\n new Set(rows.map((row) => row.initiatorMemberId).filter(Boolean)),\n );\n\n if (initiatorMemberIds.length === 0) {\n setInitiatorProfilesById(new Map());\n\n return;\n }\n\n let cancelled = false;\n\n void (async (): Promise<void> => {\n try {\n const profiles = await resolveMembers(initiatorMemberIds);\n\n if (cancelled) {\n return;\n }\n\n setInitiatorProfilesById(\n new Map(profiles.map((profile) => [profile.memberId, profile])),\n );\n } catch {\n if (cancelled) {\n return;\n }\n\n setInitiatorProfilesById(new Map());\n }\n })();\n\n return (): void => {\n cancelled = true;\n };\n }, [rows]);\n\n const columns = useMemo(\n (): TableColumn<ApprovalInstanceRow>[] => [\n { dataIndex: 'caseTitle', key: 'caseTitle', title: '案件', width: 300 },\n {\n key: 'state',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography\n color={readInstanceStateColor(record.state)}\n component=\"span\"\n variant=\"body\"\n >\n {record.stateLabel}\n </Typography>\n ),\n title: '狀態',\n width: 120,\n },\n {\n key: 'initiatorMemberId',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {readInitiatorLabel(\n record.initiatorMemberId,\n initiatorProfilesById,\n )}\n </Typography>\n ),\n title: '發起人',\n width: 180,\n },\n {\n key: 'startedAt',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatDateTime(record.startedAt)}\n </Typography>\n ),\n title: '發起時間',\n width: 220,\n },\n {\n key: 'completedAt',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatDateTime(record.completedAt)}\n </Typography>\n ),\n title: '完成時間',\n width: 220,\n },\n ],\n [initiatorProfilesById],\n );\n const tableActions = useMemo(\n (): TableActions<ApprovalInstanceRow> => ({\n render: (\n record,\n ): ReturnType<TableActions<ApprovalInstanceRow>['render']> => [\n {\n name: '查看',\n onClick: (): void => router.push(routes.caseDetail(record.id)),\n },\n ],\n variant: 'base-secondary',\n width: 88,\n }),\n [router],\n );\n\n return (\n <>\n <PageHeader>\n <ContentHeader description={description} title={title} />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.instanceFilterArea} size=\"sub\">\n <FilterLine>\n <Filter span={3}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"instanceSearchText\"\n >\n <Input\n fullWidth\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n setSearchText(event.target.value);\n setInstancePage(1);\n }}\n placeholder={searchPlaceholder}\n size=\"sub\"\n value={searchText}\n variant=\"base\"\n />\n </FormField>\n </Filter>\n <Filter span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"instanceState\"\n >\n <Select\n clearable={false}\n fullWidth\n onChange={(option): void => {\n setStateFilter(readSelectedStateFilterOption(option));\n setInstancePage(1);\n }}\n options={[...STATE_FILTER_OPTIONS]}\n placeholder=\"狀態\"\n renderValue={(value): string =>\n `狀態:${readStateFilterLabel(value)}`\n }\n size=\"sub\"\n value={stateFilter}\n />\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n {!error && !loading && rows.length === 0 ? (\n <Typography color=\"text-neutral\" variant=\"body\">\n {emptyMessage}\n </Typography>\n ) : null}\n <Table\n actions={tableActions}\n columns={columns}\n dataSource={[...rows]}\n fullWidth\n loading={loading}\n pagination={{\n current: instancePage,\n onChange: (page): void => {\n setInstancePage(page);\n },\n onChangePageSize: (pageSize): void => {\n setInstancePage(1);\n setInstancePageSize(pageSize);\n },\n pageSize: instancePageSize,\n pageSizeLabel: '每頁筆數',\n pageSizeOptions: INSTANCE_PAGE_SIZE_OPTIONS,\n renderResultSummary: (from, to, total): string =>\n `顯示 ${from}-${to} 筆,共 ${total} 筆`,\n showPageSizeOptions: true,\n total: instanceTotalCount,\n }}\n />\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nfunction readApprovalInstanceRow(\n instance: ApprovalInstanceRecord,\n): ApprovalInstanceRow {\n return {\n ...instance,\n caseTitle: readApprovalInstanceCaseTitle(instance),\n key: instance.id,\n stateLabel: readInstanceStateLabel(instance.state),\n };\n}\n\nfunction readSelectedStateFilterOption(option: unknown): StateFilterOption {\n if (!isStateFilterOption(option)) {\n return STATE_FILTER_OPTIONS[0];\n }\n return readStateFilterOption(option.state);\n}\n\nfunction readStateFilterOption(\n state: ApprovalInstanceState | null,\n): StateFilterOption {\n return (\n STATE_FILTER_OPTIONS.find((option) => option.state === state) ??\n STATE_FILTER_OPTIONS[0]\n );\n}\n\nfunction readStateFilterLabel(value: unknown): string {\n return isStateFilterOption(value) ? value.name : STATE_FILTER_OPTIONS[0].name;\n}\n\nfunction isStateFilterOption(value: unknown): value is StateFilterOption {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'id' in value &&\n 'name' in value &&\n 'state' in value\n );\n}\n\nfunction readInstanceStateLabel(state: ApprovalInstanceState): string {\n if (state === 'RUNNING') return '進行中';\n if (state === 'APPROVED') return '已通過';\n if (state === 'REJECTED') return '已拒絕';\n if (state === 'RETURNED') return '已退回';\n if (state === 'CANCELLED') return '已取消';\n if (state === 'EXPIRED') return '已逾期';\n return '草稿';\n}\n\nfunction readInstanceStateColor(\n state: ApprovalInstanceState,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (state === 'APPROVED') return 'text-success';\n if (state === 'REJECTED' || state === 'CANCELLED' || state === 'EXPIRED') {\n return 'text-error';\n }\n return 'text-neutral';\n}\n\nfunction readInitiatorLabel(\n initiatorMemberId: string | null | undefined,\n initiatorProfilesById: ReadonlyMap<string, MemberProfileRecord>,\n): string {\n const trimmedMemberId = (initiatorMemberId ?? '').trim();\n if (!trimmedMemberId) return '未知發起人';\n return initiatorProfilesById.get(trimmedMemberId)?.name ?? trimmedMemberId;\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '讀取簽核案件失敗。';\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";require('../approval-instance-list-page.css');const e=require("./chunk-CMqjfN_6.cjs"),t=require("./router-adapter-BybHrCNP.cjs"),n=require("./format-date-time-XxBzF0F5.cjs"),r=require("./routes-config-2aKbWq2H.cjs");let i=require("react"),a=require("@mezzanine-ui/react"),o=require("@rytass/bpm-core-client"),s=require("react/jsx-runtime"),c=require("@rytass/bpm-core-client/workflow"),l=require("@mezzanine-ui/react/ContentHeader");l=e.t(l,1);let u=require("@mezzanine-ui/core/form");var d={instanceFilterArea:`bpm_instanceFilterArea_qpvJq`},f=[10,20,50],p=[{id:`ALL`,name:`全部狀態`,state:null},{id:`RUNNING`,name:`進行中`,state:`RUNNING`},{id:`APPROVED`,name:`已通過`,state:`APPROVED`},{id:`REJECTED`,name:`已拒絕`,state:`REJECTED`},{id:`RETURNED`,name:`已退回`,state:`RETURNED`},{id:`CANCELLED`,name:`已取消`,state:`CANCELLED`},{id:`EXPIRED`,name:`已逾期`,state:`EXPIRED`},{id:`DRAFT`,name:`草稿`,state:`DRAFT`}];function m({defaultState:e,description:m,emptyMessage:y,searchPlaceholder:b,title:w,view:T}){let E=t.r(),D=r.r(),[O,k]=(0,i.useState)(null),[A,j]=(0,i.useState)(new Map),[M,N]=(0,i.useState)(1),[P,F]=(0,i.useState)(10),[I,L]=(0,i.useState)(0),[R,z]=(0,i.useState)(!0),[B,V]=(0,i.useState)([]),[H,U]=(0,i.useState)(``),[W,G]=(0,i.useState)(_(e)),K=(0,i.useCallback)(async()=>{z(!0),k(null);try{let e=await(0,c.listApprovalInstancesPage)({page:M,pageSize:P,searchText:H,state:W.state,templateId:null,view:T});V(e.instances.map(h)),L(e.totalCount)}catch(e){k(C(e))}finally{z(!1)}},[M,P,H,W,T]);(0,i.useEffect)(()=>{K()},[K]),(0,i.useEffect)(()=>{let e=Array.from(new Set(B.map(e=>e.initiatorMemberId).filter(Boolean)));if(e.length===0){j(new Map);return}let t=!1;return(async()=>{try{let n=await(0,o.resolveMembers)(e);if(t)return;j(new Map(n.map(e=>[e.memberId,e])))}catch{if(t)return;j(new Map)}})(),()=>{t=!0}},[B]);let q=(0,i.useMemo)(()=>[{dataIndex:`caseTitle`,key:`caseTitle`,title:`案件`,width:300},{key:`state`,render:e=>(0,s.jsx)(a.Typography,{color:x(e.state),component:`span`,variant:`body`,children:e.stateLabel}),title:`狀態`,width:120},{key:`initiatorMemberId`,render:e=>(0,s.jsx)(a.Typography,{component:`span`,variant:`body`,children:S(e.initiatorMemberId,A)}),title:`發起人`,width:180},{key:`startedAt`,render:e=>(0,s.jsx)(a.Typography,{component:`span`,variant:`body`,children:n.t(e.startedAt)}),title:`發起時間`,width:220},{key:`completedAt`,render:e=>(0,s.jsx)(a.Typography,{component:`span`,variant:`body`,children:n.t(e.completedAt)}),title:`完成時間`,width:220}],[A]),J=(0,i.useMemo)(()=>({render:e=>[{name:`查看`,onClick:()=>E.push(D.caseDetail(e.id))}],variant:`base-secondary`,width:88}),[E]);return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(a.PageHeader,{children:(0,s.jsx)(l.default,{description:m,title:w})}),(0,s.jsx)(a.SectionGroup,{children:(0,s.jsxs)(a.Section,{filterArea:(0,s.jsx)(a.FilterArea,{className:d.instanceFilterArea,size:`sub`,children:(0,s.jsxs)(a.FilterLine,{children:[(0,s.jsx)(a.Filter,{span:3,children:(0,s.jsx)(a.FormField,{fullWidth:!0,layout:u.FormFieldLayout.VERTICAL,name:`instanceSearchText`,children:(0,s.jsx)(a.Input,{fullWidth:!0,onChange:e=>{U(e.target.value),N(1)},placeholder:b,size:`sub`,value:H,variant:`base`})})}),(0,s.jsx)(a.Filter,{span:2,children:(0,s.jsx)(a.FormField,{fullWidth:!0,layout:u.FormFieldLayout.VERTICAL,name:`instanceState`,children:(0,s.jsx)(a.Select,{clearable:!1,fullWidth:!0,onChange:e=>{G(g(e)),N(1)},options:[...p],placeholder:`狀態`,renderValue:e=>`狀態:${v(e)}`,size:`sub`,value:W})})})]})}),children:[O?(0,s.jsx)(a.Typography,{color:`text-error`,variant:`body`,children:O}):null,!O&&!R&&B.length===0?(0,s.jsx)(a.Typography,{color:`text-neutral`,variant:`body`,children:y}):null,(0,s.jsx)(a.Table,{actions:J,columns:q,dataSource:[...B],fullWidth:!0,loading:R,pagination:{current:M,onChange:e=>{N(e)},onChangePageSize:e=>{N(1),F(e)},pageSize:P,pageSizeLabel:`每頁筆數`,pageSizeOptions:f,renderResultSummary:(e,t,n)=>`顯示 ${e}-${t} 筆,共 ${n} 筆`,showPageSizeOptions:!0,total:I}})]})})]})}function h(e){return{...e,caseTitle:(0,c.readApprovalInstanceCaseTitle)(e),key:e.id,stateLabel:b(e.state)}}function g(e){return y(e)?_(e.state):p[0]}function _(e){return p.find(t=>t.state===e)??p[0]}function v(e){return y(e)?e.name:p[0].name}function y(e){return typeof e==`object`&&!!e&&`id`in e&&`name`in e&&`state`in e}function b(e){return e===`RUNNING`?`進行中`:e===`APPROVED`?`已通過`:e===`REJECTED`?`已拒絕`:e===`RETURNED`?`已退回`:e===`CANCELLED`?`已取消`:e===`EXPIRED`?`已逾期`:`草稿`}function x(e){return e===`APPROVED`?`text-success`:e===`REJECTED`||e===`CANCELLED`||e===`EXPIRED`?`text-error`:`text-neutral`}function S(e,t){let n=(e??``).trim();return n?t.get(n)?.name??n:`未知發起人`}function C(e){return e instanceof Error?e.message:`讀取簽核案件失敗。`}Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return m}});
|
|
2
|
+
//# sourceMappingURL=approval-instance-list-page-UNIIgUZy.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-instance-list-page-UNIIgUZy.cjs","names":[],"sources":["../../src/components/approval-instance-list-page.module.scss","../../src/components/approval-instance-list-page.tsx"],"sourcesContent":[".instanceFilterArea {\n :global(.mzn-filter-area__actions) {\n display: none;\n }\n\n :global(.mzn-form-field__label-area) {\n display: none;\n }\n\n :global(.mzn-form-field__control-field-slot--main) {\n width: 100%;\n min-width: 0;\n }\n}\n","'use client';\n\nimport type { ChangeEvent, ReactElement } from 'react';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n Input,\n PageHeader,\n Section,\n SectionGroup,\n Select,\n Table,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport { resolveMembers, type MemberProfileRecord } from '@rytass/bpm-core-client';\nimport {\n listApprovalInstancesPage,\n readApprovalInstanceCaseTitle,\n type ApprovalInstanceRecord,\n type ApprovalInstanceState,\n type ApprovalInstanceView,\n} from '@rytass/bpm-core-client/workflow';\nimport { useRouterAdapter } from '../lib/router-adapter';\nimport { useBPMRoutes } from '../lib/routes-config';\nimport { formatDateTime } from '../lib/format-date-time';\nimport styles from './approval-instance-list-page.module.scss';\n\nexport interface ApprovalInstanceListPageProps {\n readonly defaultState: ApprovalInstanceState | null;\n readonly description: string;\n readonly emptyMessage: string;\n readonly searchPlaceholder: string;\n readonly title: string;\n readonly view: ApprovalInstanceView;\n}\n\ntype StateFilterOption = Readonly<{\n id: 'ALL' | ApprovalInstanceState;\n name: string;\n state: ApprovalInstanceState | null;\n}>;\n\ntype ApprovalInstanceRow = Readonly<\n Record<string, unknown> &\n ApprovalInstanceRecord & {\n caseTitle: string;\n key: string;\n stateLabel: string;\n }\n>;\n\nconst INSTANCE_PAGE_SIZE_OPTIONS = [10, 20, 50];\nconst STATE_FILTER_OPTIONS: readonly StateFilterOption[] = [\n { id: 'ALL', name: '全部狀態', state: null },\n { id: 'RUNNING', name: '進行中', state: 'RUNNING' },\n { id: 'APPROVED', name: '已通過', state: 'APPROVED' },\n { id: 'REJECTED', name: '已拒絕', state: 'REJECTED' },\n { id: 'RETURNED', name: '已退回', state: 'RETURNED' },\n { id: 'CANCELLED', name: '已取消', state: 'CANCELLED' },\n { id: 'EXPIRED', name: '已逾期', state: 'EXPIRED' },\n { id: 'DRAFT', name: '草稿', state: 'DRAFT' },\n];\n\n/**\n * Shared list page for any approval-instance \"view\" (inbox / sent / cc /\n * delegated). Caller picks the view + default state filter; the page renders\n * the standard BPM filter bar + paginated table and navigates to\n * `/instances/:id` on row action.\n */\nexport function ApprovalInstanceListPage({\n defaultState,\n description,\n emptyMessage,\n searchPlaceholder,\n title,\n view,\n}: ApprovalInstanceListPageProps): ReactElement {\n const router = useRouterAdapter();\n const routes = useBPMRoutes();\n const [error, setError] = useState<string | null>(null);\n const [initiatorProfilesById, setInitiatorProfilesById] = useState<\n ReadonlyMap<string, MemberProfileRecord>\n >(new Map());\n const [instancePage, setInstancePage] = useState(1);\n const [instancePageSize, setInstancePageSize] = useState(10);\n const [instanceTotalCount, setInstanceTotalCount] = useState(0);\n const [loading, setLoading] = useState(true);\n const [rows, setRows] = useState<readonly ApprovalInstanceRow[]>([]);\n const [searchText, setSearchText] = useState('');\n const [stateFilter, setStateFilter] = useState<StateFilterOption>(\n readStateFilterOption(defaultState),\n );\n\n const refreshInstances = useCallback(async (): Promise<void> => {\n setLoading(true);\n setError(null);\n\n try {\n const result = await listApprovalInstancesPage({\n page: instancePage,\n pageSize: instancePageSize,\n searchText,\n state: stateFilter.state,\n templateId: null,\n view,\n });\n\n setRows(result.instances.map(readApprovalInstanceRow));\n setInstanceTotalCount(result.totalCount);\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setLoading(false);\n }\n }, [instancePage, instancePageSize, searchText, stateFilter, view]);\n\n useEffect((): void => {\n void refreshInstances();\n }, [refreshInstances]);\n\n useEffect((): (() => void) | void => {\n const initiatorMemberIds = Array.from(\n new Set(rows.map((row) => row.initiatorMemberId).filter(Boolean)),\n );\n\n if (initiatorMemberIds.length === 0) {\n setInitiatorProfilesById(new Map());\n\n return;\n }\n\n let cancelled = false;\n\n void (async (): Promise<void> => {\n try {\n const profiles = await resolveMembers(initiatorMemberIds);\n\n if (cancelled) {\n return;\n }\n\n setInitiatorProfilesById(\n new Map(profiles.map((profile) => [profile.memberId, profile])),\n );\n } catch {\n if (cancelled) {\n return;\n }\n\n setInitiatorProfilesById(new Map());\n }\n })();\n\n return (): void => {\n cancelled = true;\n };\n }, [rows]);\n\n const columns = useMemo(\n (): TableColumn<ApprovalInstanceRow>[] => [\n { dataIndex: 'caseTitle', key: 'caseTitle', title: '案件', width: 300 },\n {\n key: 'state',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography\n color={readInstanceStateColor(record.state)}\n component=\"span\"\n variant=\"body\"\n >\n {record.stateLabel}\n </Typography>\n ),\n title: '狀態',\n width: 120,\n },\n {\n key: 'initiatorMemberId',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {readInitiatorLabel(\n record.initiatorMemberId,\n initiatorProfilesById,\n )}\n </Typography>\n ),\n title: '發起人',\n width: 180,\n },\n {\n key: 'startedAt',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatDateTime(record.startedAt)}\n </Typography>\n ),\n title: '發起時間',\n width: 220,\n },\n {\n key: 'completedAt',\n render: (record: ApprovalInstanceRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {formatDateTime(record.completedAt)}\n </Typography>\n ),\n title: '完成時間',\n width: 220,\n },\n ],\n [initiatorProfilesById],\n );\n const tableActions = useMemo(\n (): TableActions<ApprovalInstanceRow> => ({\n render: (\n record,\n ): ReturnType<TableActions<ApprovalInstanceRow>['render']> => [\n {\n name: '查看',\n onClick: (): void => router.push(routes.caseDetail(record.id)),\n },\n ],\n variant: 'base-secondary',\n width: 88,\n }),\n [router],\n );\n\n return (\n <>\n <PageHeader>\n <ContentHeader description={description} title={title} />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.instanceFilterArea} size=\"sub\">\n <FilterLine>\n <Filter span={3}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"instanceSearchText\"\n >\n <Input\n fullWidth\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n setSearchText(event.target.value);\n setInstancePage(1);\n }}\n placeholder={searchPlaceholder}\n size=\"sub\"\n value={searchText}\n variant=\"base\"\n />\n </FormField>\n </Filter>\n <Filter span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"instanceState\"\n >\n <Select\n clearable={false}\n fullWidth\n onChange={(option): void => {\n setStateFilter(readSelectedStateFilterOption(option));\n setInstancePage(1);\n }}\n options={[...STATE_FILTER_OPTIONS]}\n placeholder=\"狀態\"\n renderValue={(value): string =>\n `狀態:${readStateFilterLabel(value)}`\n }\n size=\"sub\"\n value={stateFilter}\n />\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n {!error && !loading && rows.length === 0 ? (\n <Typography color=\"text-neutral\" variant=\"body\">\n {emptyMessage}\n </Typography>\n ) : null}\n <Table\n actions={tableActions}\n columns={columns}\n dataSource={[...rows]}\n fullWidth\n loading={loading}\n pagination={{\n current: instancePage,\n onChange: (page): void => {\n setInstancePage(page);\n },\n onChangePageSize: (pageSize): void => {\n setInstancePage(1);\n setInstancePageSize(pageSize);\n },\n pageSize: instancePageSize,\n pageSizeLabel: '每頁筆數',\n pageSizeOptions: INSTANCE_PAGE_SIZE_OPTIONS,\n renderResultSummary: (from, to, total): string =>\n `顯示 ${from}-${to} 筆,共 ${total} 筆`,\n showPageSizeOptions: true,\n total: instanceTotalCount,\n }}\n />\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nfunction readApprovalInstanceRow(\n instance: ApprovalInstanceRecord,\n): ApprovalInstanceRow {\n return {\n ...instance,\n caseTitle: readApprovalInstanceCaseTitle(instance),\n key: instance.id,\n stateLabel: readInstanceStateLabel(instance.state),\n };\n}\n\nfunction readSelectedStateFilterOption(option: unknown): StateFilterOption {\n if (!isStateFilterOption(option)) {\n return STATE_FILTER_OPTIONS[0];\n }\n return readStateFilterOption(option.state);\n}\n\nfunction readStateFilterOption(\n state: ApprovalInstanceState | null,\n): StateFilterOption {\n return (\n STATE_FILTER_OPTIONS.find((option) => option.state === state) ??\n STATE_FILTER_OPTIONS[0]\n );\n}\n\nfunction readStateFilterLabel(value: unknown): string {\n return isStateFilterOption(value) ? value.name : STATE_FILTER_OPTIONS[0].name;\n}\n\nfunction isStateFilterOption(value: unknown): value is StateFilterOption {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'id' in value &&\n 'name' in value &&\n 'state' in value\n );\n}\n\nfunction readInstanceStateLabel(state: ApprovalInstanceState): string {\n if (state === 'RUNNING') return '進行中';\n if (state === 'APPROVED') return '已通過';\n if (state === 'REJECTED') return '已拒絕';\n if (state === 'RETURNED') return '已退回';\n if (state === 'CANCELLED') return '已取消';\n if (state === 'EXPIRED') return '已逾期';\n return '草稿';\n}\n\nfunction readInstanceStateColor(\n state: ApprovalInstanceState,\n): 'text-error' | 'text-neutral' | 'text-success' {\n if (state === 'APPROVED') return 'text-success';\n if (state === 'REJECTED' || state === 'CANCELLED' || state === 'EXPIRED') {\n return 'text-error';\n }\n return 'text-neutral';\n}\n\nfunction readInitiatorLabel(\n initiatorMemberId: string | null | undefined,\n initiatorProfilesById: ReadonlyMap<string, MemberProfileRecord>,\n): string {\n const trimmedMemberId = (initiatorMemberId ?? '').trim();\n if (!trimmedMemberId) return '未知發起人';\n return initiatorProfilesById.get(trimmedMemberId)?.name ?? trimmedMemberId;\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '讀取簽核案件失敗。';\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";require('../auth-provider.css');const e=require("./router-adapter-BybHrCNP.cjs");let t=require("react"),n=require("@mezzanine-ui/react"),r=require("@rytass/bpm-core-client"),i=require("react/jsx-runtime");var a={authLoading:`bpm_authLoading_GiBnf`},o=(0,t.createContext)(null);function s({children:n,publicPaths:a=[`/login`],loginPath:s=`/login`}){let c=e.r(),[f,p]=(0,t.useState)(!0),[m,h]=(0,t.useState)(null),g=(0,t.useCallback)(async()=>{let e=await(0,r.readApiCurrentMember)();return h(e),e},[]),_=(0,t.useCallback)(async e=>{let t=await(0,r.loginApi)(e);return h(t),t},[]),v=(0,t.useCallback)(async()=>{await(0,r.logoutApi)(),h(null),c.replace(s)},[s,c]);(0,t.useEffect)(()=>{let e=!0;return p(!0),(async()=>{try{let t=await(0,r.readApiCurrentMember)();e&&h(t)}catch{e&&h(null)}finally{e&&p(!1)}})(),()=>{e=!1}},[]),(0,t.useEffect)(()=>{f||u(c.pathname,a)||m||c.replace(`${s}?next=${encodeURIComponent(d(c.pathname))}`)},[f,s,m,a,c]);let y=(0,t.useMemo)(()=>({loading:f,login:_,logout:v,member:m,refresh:g}),[f,_,v,m,g]);return f&&!u(c.pathname,a)?(0,i.jsx)(l,{label:`確認登入狀態`}):!f&&!m&&!u(c.pathname,a)?(0,i.jsx)(l,{label:`前往登入頁`}):(0,i.jsx)(o.Provider,{value:y,children:n})}function c(){let e=(0,t.useContext)(o);if(!e)throw Error(`useAuth must be used inside <AuthProvider>`);return e}function l({label:e}){return(0,i.jsx)(`main`,{className:a.authLoading,children:(0,i.jsx)(n.Typography,{color:`text-neutral`,variant:`body`,children:e})})}function u(e,t){return t.some(t=>t===e)}function d(e){return typeof window>`u`?e??`/`:`${window.location.pathname}${window.location.search}`}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return s}});
|
|
2
|
+
//# sourceMappingURL=auth-provider-D2P-qWmY.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-provider-D2P-qWmY.cjs","names":[],"sources":["../../src/lib/auth-provider.module.scss","../../src/lib/auth-provider.tsx"],"sourcesContent":[".authLoading {\n align-items: center;\n background: var(--mzn-color-bg-body);\n display: flex;\n min-height: 100vh;\n justify-content: center;\n padding: 32px;\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 { Typography } from '@mezzanine-ui/react';\nimport {\n loginApi,\n logoutApi,\n readApiCurrentMember,\n type ApiMember,\n} from '@rytass/bpm-core-client';\nimport { useRouterAdapter } from './router-adapter';\nimport styles from './auth-provider.module.scss';\n\ninterface AuthContextValue {\n readonly loading: boolean;\n readonly member: ApiMember | null;\n readonly login: (input: {\n readonly identifier: string;\n readonly password: string;\n }) => Promise<ApiMember>;\n readonly logout: () => Promise<void>;\n readonly refresh: () => Promise<ApiMember | null>;\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\nexport interface AuthProviderProps {\n readonly children: ReactNode;\n /**\n * Paths that should not redirect to `/login` when there is no session.\n * Defaults to `['/login']`. Override when your host runs the login UI\n * under a different path.\n */\n readonly publicPaths?: readonly string[];\n /**\n * Where to send unauthenticated users. Defaults to `'/login'`.\n */\n readonly loginPath?: string;\n}\n\n/**\n * BPM auth context provider. Reads / writes the host BPM API session via\n * `@rytass/bpm-core-client` (`loginApi` / `logoutApi` / `readApiCurrentMember`)\n * and uses the host-supplied {@link useRouterAdapter} to redirect\n * unauthenticated users.\n */\nexport function AuthProvider({\n children,\n publicPaths = ['/login'],\n loginPath = '/login',\n}: AuthProviderProps): ReactElement {\n const router = useRouterAdapter();\n const [loading, setLoading] = useState(true);\n const [member, setMember] = useState<ApiMember | null>(null);\n\n const refresh = useCallback(async (): Promise<ApiMember | null> => {\n const current = await readApiCurrentMember();\n setMember(current);\n return current;\n }, []);\n\n const login = useCallback(\n async (input: {\n readonly identifier: string;\n readonly password: string;\n }): Promise<ApiMember> => {\n const next = await loginApi(input);\n setMember(next);\n return next;\n },\n [],\n );\n\n const logout = useCallback(async (): Promise<void> => {\n await logoutApi();\n setMember(null);\n router.replace(loginPath);\n }, [loginPath, router]);\n\n useEffect((): (() => void) => {\n let active = true;\n setLoading(true);\n void (async () => {\n try {\n const current = await readApiCurrentMember();\n if (active) setMember(current);\n } catch {\n if (active) setMember(null);\n } finally {\n if (active) setLoading(false);\n }\n })();\n return (): void => {\n active = false;\n };\n }, []);\n\n useEffect((): void => {\n if (loading || isPublicPath(router.pathname, publicPaths) || member) return;\n router.replace(\n `${loginPath}?next=${encodeURIComponent(readCurrentPath(router.pathname))}`,\n );\n }, [loading, loginPath, member, publicPaths, router]);\n\n const value = useMemo<AuthContextValue>(\n () => ({ loading, login, logout, member, refresh }),\n [loading, login, logout, member, refresh],\n );\n\n if (loading && !isPublicPath(router.pathname, publicPaths)) {\n return <AuthLoadingState label=\"確認登入狀態\" />;\n }\n if (!loading && !member && !isPublicPath(router.pathname, publicPaths)) {\n return <AuthLoadingState label=\"前往登入頁\" />;\n }\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\n/**\n * Access the BPM auth context. Throws when used outside `<AuthProvider>`.\n */\nexport function useAuth(): AuthContextValue {\n const ctx = useContext(AuthContext);\n if (!ctx) throw new Error('useAuth must be used inside <AuthProvider>');\n return ctx;\n}\n\nfunction AuthLoadingState({\n label,\n}: {\n readonly label: string;\n}): ReactElement {\n return (\n <main className={styles.authLoading}>\n <Typography color=\"text-neutral\" variant=\"body\">\n {label}\n </Typography>\n </main>\n );\n}\n\nfunction isPublicPath(\n pathname: string | null,\n publicPaths: readonly string[],\n): boolean {\n return publicPaths.some((p) => p === pathname);\n}\n\nfunction readCurrentPath(pathname: string | null): string {\n if (typeof window === 'undefined') return pathname ?? '/';\n return `${window.location.pathname}${window.location.search}`;\n}\n"],"mappings":"sOCiCM,GAAA,EAAA,EAAA,eAAqD,IAAI,EAsB/D,SAAgB,EAAa,CAC3B,WACA,cAAc,CAAC,QAAQ,EACvB,YAAY,UACsB,CAClC,IAAM,EAAS,EAAA,EAAiB,EAC1B,CAAC,EAAS,IAAA,EAAA,EAAA,UAAuB,EAAI,EACrC,CAAC,EAAQ,IAAA,EAAA,EAAA,UAAwC,IAAI,EAErD,GAAA,EAAA,EAAA,aAAsB,SAAuC,CACjE,IAAM,EAAU,MAAA,EAAA,EAAA,sBAA2B,EAE3C,OADA,EAAU,CAAO,EACV,CACT,EAAG,CAAC,CAAC,EAEC,GAAA,EAAA,EAAA,aACJ,KAAO,IAGmB,CACxB,IAAM,EAAO,MAAA,EAAA,EAAA,UAAe,CAAK,EAEjC,OADA,EAAU,CAAI,EACP,CACT,EACA,CAAC,CACH,EAEM,GAAA,EAAA,EAAA,aAAqB,SAA2B,CACpD,MAAA,EAAA,EAAA,WAAgB,EAChB,EAAU,IAAI,EACd,EAAO,QAAQ,CAAS,CAC1B,EAAG,CAAC,EAAW,CAAM,CAAC,GAEtB,EAAA,EAAA,eAA8B,CAC5B,IAAI,EAAS,GAYb,OAXA,EAAW,EAAI,GACT,SAAY,CAChB,GAAI,CACF,IAAM,EAAU,MAAA,EAAA,EAAA,sBAA2B,EACvC,GAAQ,EAAU,CAAO,CAC/B,MAAQ,CACF,GAAQ,EAAU,IAAI,CAC5B,QAAU,CACJ,GAAQ,EAAW,EAAK,CAC9B,CACF,GAAG,MACgB,CACjB,EAAS,EACX,CACF,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,eAAsB,CAChB,GAAW,EAAa,EAAO,SAAU,CAAW,GAAK,GAC7D,EAAO,QACL,GAAG,EAAU,QAAQ,mBAAmB,EAAgB,EAAO,QAAQ,CAAC,GAC1E,CACF,EAAG,CAAC,EAAS,EAAW,EAAQ,EAAa,CAAM,CAAC,EAEpD,IAAM,GAAA,EAAA,EAAA,cACG,CAAE,UAAS,QAAO,SAAQ,SAAQ,SAAQ,GACjD,CAAC,EAAS,EAAO,EAAQ,EAAQ,CAAO,CAC1C,EAQA,OANI,GAAW,CAAC,EAAa,EAAO,SAAU,CAAW,GAChD,EAAA,EAAA,KAAC,EAAD,CAAkB,MAAM,QAAU,CAAA,EAEvC,CAAC,GAAW,CAAC,GAAU,CAAC,EAAa,EAAO,SAAU,CAAW,GAC5D,EAAA,EAAA,KAAC,EAAD,CAAkB,MAAM,OAAS,CAAA,GAEnC,EAAA,EAAA,KAAC,EAAY,SAAb,CAA6B,QAAQ,UAA+B,CAAA,CAC7E,CAKA,SAAgB,GAA4B,CAC1C,IAAM,GAAA,EAAA,EAAA,YAAiB,CAAW,EAClC,GAAI,CAAC,EAAK,MAAU,MAAM,4CAA4C,EACtE,OAAO,CACT,CAEA,SAAS,EAAiB,CACxB,SAGe,CACf,OACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAW,EAAO,sBACtB,EAAA,EAAA,KAAC,EAAA,WAAD,CAAY,MAAM,eAAe,QAAQ,gBACtC,CACS,CAAA,CACR,CAAA,CAEV,CAEA,SAAS,EACP,EACA,EACS,CACT,OAAO,EAAY,KAAM,GAAM,IAAM,CAAQ,CAC/C,CAEA,SAAS,EAAgB,EAAiC,CAExD,OADI,OAAO,OAAW,IAAoB,GAAY,IAC/C,GAAG,OAAO,SAAS,WAAW,OAAO,SAAS,QACvD"}
|