@rytass/bpm-core-react 0.4.0 → 0.5.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 +110 -0
- package/dist/chunks/FormBuilderView-B_KGPjlp.cjs +3 -0
- package/dist/chunks/FormBuilderView-B_KGPjlp.cjs.map +1 -0
- package/dist/chunks/FormBuilderView-D8DrQOXD.js +1090 -0
- package/dist/chunks/FormBuilderView-D8DrQOXD.js.map +1 -0
- package/dist/chunks/{approval-instance-list-page-UNIIgUZy.cjs → approval-instance-list-page-BMUKxzcz.cjs} +2 -2
- package/dist/chunks/{approval-instance-list-page-UNIIgUZy.cjs.map → approval-instance-list-page-BMUKxzcz.cjs.map} +1 -1
- package/dist/chunks/{approval-instance-list-page-BtEc8Cs3.js → approval-instance-list-page-YZcGGDD8.js} +3 -3
- package/dist/chunks/{approval-instance-list-page-BtEc8Cs3.js.map → approval-instance-list-page-YZcGGDD8.js.map} +1 -1
- package/dist/chunks/{auth-provider-D2P-qWmY.cjs → auth-provider-4BeCw7cI.cjs} +2 -2
- package/dist/chunks/{auth-provider-D2P-qWmY.cjs.map → auth-provider-4BeCw7cI.cjs.map} +1 -1
- package/dist/chunks/{auth-provider-TTO9eNZV.js → auth-provider-B5oPmvk2.js} +2 -2
- package/dist/chunks/{auth-provider-TTO9eNZV.js.map → auth-provider-B5oPmvk2.js.map} +1 -1
- package/dist/chunks/compose-PMrmi-LE.js +451 -0
- package/dist/chunks/compose-PMrmi-LE.js.map +1 -0
- package/dist/chunks/compose-ziVbRYdo.cjs +2 -0
- package/dist/chunks/compose-ziVbRYdo.cjs.map +1 -0
- package/dist/chunks/{dashboard-page-CQRBJxze.js → dashboard-page-DJ9vOPga.js} +4 -4
- package/dist/chunks/{dashboard-page-CQRBJxze.js.map → dashboard-page-DJ9vOPga.js.map} +1 -1
- package/dist/chunks/{dashboard-page-DrDChhg1.cjs → dashboard-page-DwHQY6Ki.cjs} +2 -2
- package/dist/chunks/{dashboard-page-DrDChhg1.cjs.map → dashboard-page-DwHQY6Ki.cjs.map} +1 -1
- package/dist/chunks/{delegations-CFXaJrdX.cjs → delegations-C2wLWsDQ.cjs} +2 -2
- package/dist/chunks/{delegations-CFXaJrdX.cjs.map → delegations-C2wLWsDQ.cjs.map} +1 -1
- package/dist/chunks/{delegations-DwbYkNUg.cjs → delegations-DDEk-WI6.cjs} +2 -2
- package/dist/chunks/{delegations-DwbYkNUg.cjs.map → delegations-DDEk-WI6.cjs.map} +1 -1
- package/dist/chunks/{delegations-FTLaWo1Y.js → delegations-ZNtodFaD.js} +2 -2
- package/dist/chunks/{delegations-FTLaWo1Y.js.map → delegations-ZNtodFaD.js.map} +1 -1
- package/dist/chunks/{delegations-D5pPEWsP.js → delegations-iVnRi3QE.js} +2 -2
- package/dist/chunks/{delegations-D5pPEWsP.js.map → delegations-iVnRi3QE.js.map} +1 -1
- package/dist/chunks/designer-DCn6_v4b.cjs +65 -0
- package/dist/chunks/designer-DCn6_v4b.cjs.map +1 -0
- package/dist/chunks/designer-mOMxJ0Py.js +2576 -0
- package/dist/chunks/designer-mOMxJ0Py.js.map +1 -0
- package/dist/chunks/detail-Bml-vXHX.js +1622 -0
- package/dist/chunks/detail-Bml-vXHX.js.map +1 -0
- package/dist/chunks/detail-CWeCrmtC.cjs +2 -0
- package/dist/chunks/detail-CWeCrmtC.cjs.map +1 -0
- package/dist/chunks/{login-BfmfCclF.cjs → login-9bCXyjbX.cjs} +2 -2
- package/dist/chunks/{login-BfmfCclF.cjs.map → login-9bCXyjbX.cjs.map} +1 -1
- package/dist/chunks/{login-xgI4wLHe.js → login-BKxpLibd.js} +3 -3
- package/dist/chunks/{login-xgI4wLHe.js.map → login-BKxpLibd.js.map} +1 -1
- package/dist/chunks/{notifications-a-FCxV02.cjs → notifications-BKs4--96.cjs} +2 -2
- package/dist/chunks/{notifications-a-FCxV02.cjs.map → notifications-BKs4--96.cjs.map} +1 -1
- package/dist/chunks/{notifications-BoNa1BXD.js → notifications-CSulztkU.js} +2 -2
- package/dist/chunks/{notifications-BoNa1BXD.js.map → notifications-CSulztkU.js.map} +1 -1
- package/dist/chunks/router-adapter--gYs13E8.cjs +2 -0
- package/dist/chunks/{router-adapter-BybHrCNP.cjs.map → router-adapter--gYs13E8.cjs.map} +1 -1
- package/dist/chunks/{router-adapter-BdHZXLS3.js → router-adapter-DftlFTOd.js} +2 -2
- package/dist/chunks/{router-adapter-BdHZXLS3.js.map → router-adapter-DftlFTOd.js.map} +1 -1
- package/dist/chunks/{routes-config-dxahImVe.js → routes-config-RBYQtUd0.js} +2 -3
- package/dist/chunks/routes-config-RBYQtUd0.js.map +1 -0
- package/dist/chunks/routes-config-fDVHmvXi.cjs +2 -0
- package/dist/chunks/routes-config-fDVHmvXi.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +270 -130
- package/dist/index.js.map +1 -1
- package/dist/lib/routes-config.d.ts +6 -4
- package/dist/next/BPMNextProviders.d.ts +1 -1
- package/dist/next/index.cjs +1 -1
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.js +14 -24
- package/dist/next/index.js.map +1 -1
- package/dist/next/workflow-chat-route.cjs +19 -0
- package/dist/next/workflow-chat-route.cjs.map +1 -0
- package/dist/next/workflow-chat-route.d.ts +17 -0
- package/dist/next/workflow-chat-route.js +31 -0
- package/dist/next/workflow-chat-route.js.map +1 -0
- package/dist/pages/admin/delegations/index.cjs +1 -1
- package/dist/pages/admin/delegations/index.js +1 -1
- package/dist/pages/delegations/index.cjs +1 -1
- package/dist/pages/delegations/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/compose/index.cjs +2 -0
- package/dist/pages/templates/compose/index.cjs.map +1 -0
- package/dist/pages/templates/compose/index.d.ts +13 -0
- package/dist/pages/templates/compose/index.js +14 -0
- package/dist/pages/templates/compose/index.js.map +1 -0
- package/dist/pages/templates/designer/index.cjs +1 -1
- package/dist/pages/templates/designer/index.cjs.map +1 -1
- package/dist/pages/templates/designer/index.js +7 -2
- package/dist/pages/templates/designer/index.js.map +1 -1
- package/dist/pages/templates/index.cjs +1 -1
- package/dist/pages/templates/index.cjs.map +1 -1
- package/dist/pages/templates/index.js +3 -3
- package/dist/pages/templates/index.js.map +1 -1
- 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 +1 -1
- package/dist/views/cc/index.cjs +1 -1
- package/dist/views/cc/index.js +1 -1
- package/dist/views/dashboard/index.cjs +1 -1
- package/dist/views/dashboard/index.js +1 -1
- package/dist/views/delegations/index.cjs +1 -1
- package/dist/views/delegations/index.js +1 -1
- package/dist/views/forms/builder/FormBuilderView.d.ts +13 -4
- package/dist/views/forms/builder/index.cjs +1 -1
- package/dist/views/forms/builder/index.js +1 -1
- package/dist/views/forms/builder/json-code-editor.d.ts +1 -1
- package/dist/views/inbox/index.cjs +1 -1
- package/dist/views/inbox/index.js +3 -3
- package/dist/views/instances/detail/InstanceDetailView.d.ts +11 -1
- package/dist/views/instances/detail/index.cjs +1 -1
- package/dist/views/instances/detail/index.d.ts +5 -0
- package/dist/views/instances/detail/index.js +2 -2
- package/dist/views/instances/detail/sections/InstanceAttachmentsSection.d.ts +15 -0
- package/dist/views/instances/detail/sections/InstanceFormSection.d.ts +33 -0
- package/dist/views/instances/detail/sections/InstanceHistorySection.d.ts +29 -0
- package/dist/views/instances/detail/sections/InstanceSignaturesSection.d.ts +14 -0
- package/dist/views/instances/detail/sections/InstanceTasksSection.d.ts +44 -0
- package/dist/views/instances/detail/sections/container-helpers.d.ts +8 -0
- package/dist/views/instances/detail/sections/shared.d.ts +103 -0
- package/dist/views/instances/new/index.cjs +1 -1
- package/dist/views/instances/new/index.js +3 -3
- package/dist/views/login/index.cjs +1 -1
- package/dist/views/login/index.js +1 -1
- package/dist/views/search/index.cjs +1 -1
- package/dist/views/search/index.js +1 -1
- package/dist/views/sent/index.cjs +1 -1
- package/dist/views/sent/index.js +1 -1
- package/dist/views/settings/index.cjs +1 -1
- package/dist/views/settings/index.js +1 -1
- 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 +5 -0
- package/dist/views/templates/compose/TemplateComposeWizardView.d.ts +8 -0
- package/dist/views/templates/compose/index.cjs +1 -0
- package/dist/views/templates/compose/index.d.ts +2 -0
- package/dist/views/templates/compose/index.js +2 -0
- package/dist/views/templates/compose/steps/ComposeFormStep.d.ts +15 -0
- package/dist/views/templates/compose/steps/ComposeReviewStep.d.ts +12 -0
- package/dist/views/templates/compose/steps/ComposeWorkflowStep.d.ts +11 -0
- package/dist/views/templates/compose/use-template-compose-wizard.d.ts +46 -0
- package/dist/views/templates/designer/TemplateDesignerView.d.ts +60 -2
- package/dist/views/templates/designer/chrome-workflow-chat.d.ts +12 -0
- package/dist/views/templates/designer/index.cjs +1 -51
- package/dist/views/templates/designer/index.js +2 -2272
- package/dist/views/templates/designer/use-workflow-chat.d.ts +21 -0
- package/dist/views/templates/designer/use-workflow-designer-controller.d.ts +41 -0
- package/dist/views/templates/designer/workflow-chat-drawer.d.ts +16 -0
- package/dist/views/templates/index.cjs +2 -1
- package/dist/views/templates/index.cjs.map +1 -0
- package/dist/views/templates/index.js +265 -4
- package/dist/views/templates/index.js.map +1 -0
- 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 +39 -43
- package/dist/views/templates/versions/index.js.map +1 -1
- package/package.json +22 -19
- package/dist/chunks/builder-C3E-8OJu.js +0 -1300
- package/dist/chunks/builder-C3E-8OJu.js.map +0 -1
- package/dist/chunks/builder-f-Q_0NUs.cjs +0 -3
- package/dist/chunks/builder-f-Q_0NUs.cjs.map +0 -1
- package/dist/chunks/detail-B9JkYNHc.cjs +0 -2
- package/dist/chunks/detail-B9JkYNHc.cjs.map +0 -1
- package/dist/chunks/detail-CSxI04gB.js +0 -1518
- package/dist/chunks/detail-CSxI04gB.js.map +0 -1
- package/dist/chunks/form-name-modal-C3OEvkCV.js +0 -64
- package/dist/chunks/form-name-modal-C3OEvkCV.js.map +0 -1
- package/dist/chunks/form-name-modal-uZCHbtRH.cjs +0 -2
- package/dist/chunks/form-name-modal-uZCHbtRH.cjs.map +0 -1
- package/dist/chunks/router-adapter-BybHrCNP.cjs +0 -2
- package/dist/chunks/routes-config-2aKbWq2H.cjs +0 -2
- package/dist/chunks/routes-config-2aKbWq2H.cjs.map +0 -1
- package/dist/chunks/routes-config-dxahImVe.js.map +0 -1
- package/dist/chunks/templates-CL8bPvgn.cjs +0 -2
- package/dist/chunks/templates-CL8bPvgn.cjs.map +0 -1
- package/dist/chunks/templates-DNfDOPGm.js +0 -380
- package/dist/chunks/templates-DNfDOPGm.js.map +0 -1
- package/dist/pages/forms/builder/index.cjs +0 -2
- package/dist/pages/forms/builder/index.cjs.map +0 -1
- package/dist/pages/forms/builder/index.d.ts +0 -21
- package/dist/pages/forms/builder/index.js +0 -15
- package/dist/pages/forms/builder/index.js.map +0 -1
- package/dist/pages/forms/index.cjs +0 -2
- package/dist/pages/forms/index.cjs.map +0 -1
- package/dist/pages/forms/index.d.ts +0 -17
- package/dist/pages/forms/index.js +0 -14
- package/dist/pages/forms/index.js.map +0 -1
- package/dist/views/forms/FormsView.d.ts +0 -2
- package/dist/views/forms/form-name-modal.d.ts +0 -12
- package/dist/views/forms/index.cjs +0 -2
- package/dist/views/forms/index.cjs.map +0 -1
- package/dist/views/forms/index.d.ts +0 -2
- package/dist/views/forms/index.js +0 -186
- package/dist/views/forms/index.js.map +0 -1
- package/dist/views/templates/designer/index.cjs.map +0 -1
- package/dist/views/templates/designer/index.js.map +0 -1
- package/dist/views/templates/template-name-modal.d.ts +0 -22
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications-a-FCxV02.cjs","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\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\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notifications-BKs4--96.cjs","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\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\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { n as e } from "./auth-provider-
|
|
2
|
+
import { n as e } from "./auth-provider-B5oPmvk2.js";
|
|
3
3
|
import { useEffect as t, useState as n } from "react";
|
|
4
4
|
import { Filter as r, FilterArea as i, FilterLine as a, FormField as o, PageHeader as s, RadioGroup as c, Section as l, SectionGroup as u, Typography as d } from "@mezzanine-ui/react";
|
|
5
5
|
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
@@ -190,4 +190,4 @@ function O(e) {
|
|
|
190
190
|
//#endregion
|
|
191
191
|
export { C as t };
|
|
192
192
|
|
|
193
|
-
//# sourceMappingURL=notifications-
|
|
193
|
+
//# sourceMappingURL=notifications-CSulztkU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications-BoNa1BXD.js","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\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\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notifications-CSulztkU.js","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\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\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\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";let e=require("react"),t=require("react/jsx-runtime");var n=(0,e.createContext)(null);function r({value:e,children:r}){return(0,t.jsx)(n.Provider,{value:e,children:r})}function i(){let t=(0,e.useContext)(n);if(!t)throw Error("useRouterAdapter must be used inside <RouterAdapterProvider>. In Next.js, wrap your app with <BPMNextProviders> from `@rytass/bpm-core-react/next` (it mounts <RouterAdapterProvider> internally). For non-Next hosts, build your own RouterAdapter and pass it to <RouterAdapterProvider value={...}>.");return t}function a(){return typeof window>`u`?new URLSearchParams:new URLSearchParams(window.location.search)}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return r}});
|
|
2
|
+
//# sourceMappingURL=router-adapter--gYs13E8.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-adapter
|
|
1
|
+
{"version":3,"file":"router-adapter--gYs13E8.cjs","names":[],"sources":["../../src/lib/router-adapter.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\n/**\n * Framework-agnostic router contract every BPM view consumes.\n *\n * Next.js App Router consumers wire it from `useRouter()` / `usePathname()`,\n * but the contract is intentionally generic so SPA / Remix / Tanstack Router\n * hosts can plug in the same `<RouterAdapterProvider>` with their own\n * navigation primitives.\n */\nexport interface RouterAdapter {\n /** Current pathname (e.g. \"/inbox\"). `null` during SSR before hydration. */\n readonly pathname: string | null;\n /** Navigate to `href` (push onto history). */\n push(href: string): void;\n /** Navigate to `href` and replace the current history entry. */\n replace(href: string): void;\n /** Optional: go back. Falls back to `history.back()` when omitted. */\n back?(): void;\n /**\n * Optional search params accessor. Returned `URLSearchParams` should be\n * read-only — mutating it does not navigate. Default implementation reads\n * `window.location.search` on the client.\n */\n searchParams?(): URLSearchParams;\n}\n\nconst RouterAdapterContext = createContext<RouterAdapter | null>(null);\n\nexport interface RouterAdapterProviderProps {\n readonly value: RouterAdapter;\n readonly children: ReactNode;\n}\n\n/**\n * Wraps the BPM React tree so `useRouterAdapter()` resolves to the host's\n * navigation primitives. Consumers typically put this once at the very root\n * of their layout (or inside a `'use client'` shim that reads\n * `useRouter()` + `usePathname()` from `next/navigation`).\n */\nexport function RouterAdapterProvider({\n value,\n children,\n}: RouterAdapterProviderProps): ReactElement {\n return (\n <RouterAdapterContext.Provider value={value}>\n {children}\n </RouterAdapterContext.Provider>\n );\n}\n\n/**\n * Reads the host-provided {@link RouterAdapter}. Throws when used outside a\n * `<RouterAdapterProvider>` to surface wiring mistakes early.\n */\nexport function useRouterAdapter(): RouterAdapter {\n const value = useContext(RouterAdapterContext);\n if (!value) {\n throw new Error(\n 'useRouterAdapter must be used inside <RouterAdapterProvider>. ' +\n 'In Next.js, wrap your app with <BPMNextProviders> from ' +\n '`@rytass/bpm-core-react/next` (it mounts <RouterAdapterProvider> ' +\n 'internally). For non-Next hosts, build your own RouterAdapter ' +\n 'and pass it to <RouterAdapterProvider value={...}>.',\n );\n }\n return value;\n}\n\n/**\n * Pure default search-params reader for the browser. Server-side returns an\n * empty `URLSearchParams`. Used internally when a {@link RouterAdapter}\n * does not override `searchParams()`.\n */\nexport function defaultBrowserSearchParams(): URLSearchParams {\n if (typeof window === 'undefined') return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n}\n"],"mappings":"mEAkCA,IAAM,GAAA,EAAA,EAAA,eAA2D,IAAI,EAarE,SAAgB,EAAsB,CACpC,QACA,YAC2C,CAC3C,OACE,EAAA,EAAA,KAAC,EAAqB,SAAtB,CAAsC,QACnC,UAC4B,CAAA,CAEnC,CAMA,SAAgB,GAAkC,CAChD,IAAM,GAAA,EAAA,EAAA,YAAmB,CAAoB,EAC7C,GAAI,CAAC,EACH,MAAU,MACR,ySAKF,EAEF,OAAO,CACT,CAOA,SAAgB,GAA8C,CAE5D,OADI,OAAO,OAAW,IAAoB,IAAI,gBACvC,IAAI,gBAAgB,OAAO,SAAS,MAAM,CACnD"}
|
|
@@ -11,7 +11,7 @@ function i({ value: e, children: t }) {
|
|
|
11
11
|
}
|
|
12
12
|
function a() {
|
|
13
13
|
let e = t(r);
|
|
14
|
-
if (!e) throw Error("useRouterAdapter must be used inside <RouterAdapterProvider>. In Next.js, wrap your app with <
|
|
14
|
+
if (!e) throw Error("useRouterAdapter must be used inside <RouterAdapterProvider>. In Next.js, wrap your app with <BPMNextProviders> from `@rytass/bpm-core-react/next` (it mounts <RouterAdapterProvider> internally). For non-Next hosts, build your own RouterAdapter and pass it to <RouterAdapterProvider value={...}>.");
|
|
15
15
|
return e;
|
|
16
16
|
}
|
|
17
17
|
function o() {
|
|
@@ -20,4 +20,4 @@ function o() {
|
|
|
20
20
|
//#endregion
|
|
21
21
|
export { o as n, a as r, i as t };
|
|
22
22
|
|
|
23
|
-
//# sourceMappingURL=router-adapter-
|
|
23
|
+
//# sourceMappingURL=router-adapter-DftlFTOd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-adapter-
|
|
1
|
+
{"version":3,"file":"router-adapter-DftlFTOd.js","names":[],"sources":["../../src/lib/router-adapter.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\n/**\n * Framework-agnostic router contract every BPM view consumes.\n *\n * Next.js App Router consumers wire it from `useRouter()` / `usePathname()`,\n * but the contract is intentionally generic so SPA / Remix / Tanstack Router\n * hosts can plug in the same `<RouterAdapterProvider>` with their own\n * navigation primitives.\n */\nexport interface RouterAdapter {\n /** Current pathname (e.g. \"/inbox\"). `null` during SSR before hydration. */\n readonly pathname: string | null;\n /** Navigate to `href` (push onto history). */\n push(href: string): void;\n /** Navigate to `href` and replace the current history entry. */\n replace(href: string): void;\n /** Optional: go back. Falls back to `history.back()` when omitted. */\n back?(): void;\n /**\n * Optional search params accessor. Returned `URLSearchParams` should be\n * read-only — mutating it does not navigate. Default implementation reads\n * `window.location.search` on the client.\n */\n searchParams?(): URLSearchParams;\n}\n\nconst RouterAdapterContext = createContext<RouterAdapter | null>(null);\n\nexport interface RouterAdapterProviderProps {\n readonly value: RouterAdapter;\n readonly children: ReactNode;\n}\n\n/**\n * Wraps the BPM React tree so `useRouterAdapter()` resolves to the host's\n * navigation primitives. Consumers typically put this once at the very root\n * of their layout (or inside a `'use client'` shim that reads\n * `useRouter()` + `usePathname()` from `next/navigation`).\n */\nexport function RouterAdapterProvider({\n value,\n children,\n}: RouterAdapterProviderProps): ReactElement {\n return (\n <RouterAdapterContext.Provider value={value}>\n {children}\n </RouterAdapterContext.Provider>\n );\n}\n\n/**\n * Reads the host-provided {@link RouterAdapter}. Throws when used outside a\n * `<RouterAdapterProvider>` to surface wiring mistakes early.\n */\nexport function useRouterAdapter(): RouterAdapter {\n const value = useContext(RouterAdapterContext);\n if (!value) {\n throw new Error(\n 'useRouterAdapter must be used inside <RouterAdapterProvider>. ' +\n 'In Next.js, wrap your app with <BPMNextProviders> from ' +\n '`@rytass/bpm-core-react/next` (it mounts <RouterAdapterProvider> ' +\n 'internally). For non-Next hosts, build your own RouterAdapter ' +\n 'and pass it to <RouterAdapterProvider value={...}>.',\n );\n }\n return value;\n}\n\n/**\n * Pure default search-params reader for the browser. Server-side returns an\n * empty `URLSearchParams`. Used internally when a {@link RouterAdapter}\n * does not override `searchParams()`.\n */\nexport function defaultBrowserSearchParams(): URLSearchParams {\n if (typeof window === 'undefined') return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n}\n"],"mappings":";;;;AAkCA,IAAM,IAAuB,EAAoC,IAAI;AAarE,SAAgB,EAAsB,EACpC,UACA,eAC2C;CAC3C,OACE,kBAAC,EAAqB,UAAtB;EAAsC;EACnC;CAC4B,CAAA;AAEnC;AAMA,SAAgB,IAAkC;CAChD,IAAM,IAAQ,EAAW,CAAoB;CAC7C,IAAI,CAAC,GACH,MAAU,MACR,ySAKF;CAEF,OAAO;AACT;AAOA,SAAgB,IAA8C;CAE5D,OADI,OAAO,SAAW,MAAoB,IAAI,gBAAgB,IACvD,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACnD"}
|
|
@@ -14,11 +14,10 @@ function i() {
|
|
|
14
14
|
caseDetail: (e) => `/instances/${e}`,
|
|
15
15
|
caseNew: (e) => e ? `/instances/new?templateId=${encodeURIComponent(e)}` : "/instances/new",
|
|
16
16
|
templates: () => "/templates",
|
|
17
|
+
templateCompose: () => "/templates/compose",
|
|
17
18
|
templateDesigner: (e) => `/templates/${e}/designer`,
|
|
18
19
|
templateVersions: (e) => `/templates/${e}/versions`,
|
|
19
20
|
templateCategories: () => "/templates/categories",
|
|
20
|
-
forms: () => "/forms",
|
|
21
|
-
formBuilder: (e) => `/forms/${e}/builder`,
|
|
22
21
|
notificationSettings: () => "/settings/notifications",
|
|
23
22
|
adminOrgs: () => "/admin/orgs",
|
|
24
23
|
adminUsers: () => "/admin/users",
|
|
@@ -40,4 +39,4 @@ var c = i();
|
|
|
40
39
|
//#endregion
|
|
41
40
|
export { i as n, s as r, o as t };
|
|
42
41
|
|
|
43
|
-
//# sourceMappingURL=routes-config-
|
|
42
|
+
//# sourceMappingURL=routes-config-RBYQtUd0.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-config-RBYQtUd0.js","names":[],"sources":["../../src/lib/routes-config.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\n/**\n * Framework-agnostic path mapping every BPM view uses for internal\n * cross-navigation. When a host embeds BPM under a non-root prefix\n * (e.g. `/workspace/bpm/*`) it provides its own implementation through\n * `<BPMRoutesProvider value={...}>`; otherwise the default factory\n * preserves the historical `/instances/:id`, `/templates`, etc. paths.\n *\n * Function-shape (instead of string templates) means:\n * - Optional and multi-param routes can branch on argument presence\n * (`caseNew(templateId?)`).\n * - Query-string composition stays inside the host's resolver, not the\n * view.\n * - TypeScript flags missing arguments at compile time.\n *\n * Mirror sibling: {@link RouterAdapter} owns *navigation primitives*\n * (`push`, `replace`, `pathname`); `BPMRoutes` owns *path strings*. The\n * two compose: views call `router.push(routes.caseDetail(id))`.\n */\nexport interface BPMRoutes {\n /** Workflow dashboard landing. Used by sidebar + dashboard tiles. */\n dashboard(): string;\n /** Inbox (待簽) — pending tasks assigned to the current member. */\n inbox(): string;\n /** Sent (我發起的) — instances the current member initiated. */\n sent(): string;\n /** CC (抄送給我) — instances the current member is copied on. */\n cc(): string;\n /** Cross-view search. */\n search(): string;\n /** Personal delegation rules. */\n delegations(): string;\n /**\n * Reserved for hosts that want a dedicated `/notifications` page —\n * BPM's built-in views do **not** navigate here by default (the\n * notification drawer mounted by `<BPMNextProviders>` is the primary\n * unread-notification UX). The `createDefaultBPMRoutes()` factory\n * returns `'/notifications'` so consumers can mount their own page\n * at that path if desired; no BPM-shipped `pages/notifications`\n * shim exists.\n */\n notifications(): string;\n\n /**\n * Detail page for one approval instance.\n *\n * @example default → `/instances/abc123`\n */\n caseDetail(instanceId: string): string;\n /**\n * Launch a new approval instance. When `templateId` is passed, the\n * launch form is pre-populated for that template — by default this is\n * appended as a `?templateId=` query string so the path itself stays\n * stable for routing.\n *\n * @example default `caseNew()` → `/instances/new`\n * @example default `caseNew('tpl-1')` → `/instances/new?templateId=tpl-1`\n */\n caseNew(templateId?: string): string;\n\n /** Template index. */\n templates(): string;\n /**\n * Unified \"form + flow\" creation wizard. Designs a form and an approval\n * flow together, then publishes both atomically. This is the only place\n * forms are designed — there is no separate standalone form builder page.\n */\n templateCompose(): string;\n /** Template designer (xyflow canvas). */\n templateDesigner(templateId: string): string;\n /** Template version history. */\n templateVersions(templateId: string): string;\n /** Template categories admin. */\n templateCategories(): string;\n\n /** Per-member notification preferences. */\n notificationSettings(): string;\n\n /** Admin: organization tree management. */\n adminOrgs(): string;\n /** Admin: member directory mapping. */\n adminUsers(): string;\n /** Admin: delegation rule management. */\n adminDelegations(): string;\n}\n\n/**\n * Factory for the default `BPMRoutes` value. Reproduces the exact path\n * literals BPM views used before `BPMRoutesContext` existed, so existing\n * hosts (no provider mounted) keep working unchanged.\n */\nexport function createDefaultBPMRoutes(): BPMRoutes {\n return {\n dashboard: () => '/dashboard',\n inbox: () => '/inbox',\n sent: () => '/sent',\n cc: () => '/cc',\n search: () => '/search',\n delegations: () => '/delegations',\n notifications: () => '/notifications',\n\n caseDetail: (instanceId) => `/instances/${instanceId}`,\n caseNew: (templateId) =>\n templateId\n ? `/instances/new?templateId=${encodeURIComponent(templateId)}`\n : '/instances/new',\n\n templates: () => '/templates',\n templateCompose: () => '/templates/compose',\n templateDesigner: (templateId) => `/templates/${templateId}/designer`,\n templateVersions: (templateId) => `/templates/${templateId}/versions`,\n templateCategories: () => '/templates/categories',\n\n notificationSettings: () => '/settings/notifications',\n\n adminOrgs: () => '/admin/orgs',\n adminUsers: () => '/admin/users',\n adminDelegations: () => '/admin/delegations',\n };\n}\n\nconst BPMRoutesContext = createContext<BPMRoutes | null>(null);\n\nexport interface BPMRoutesProviderProps {\n /**\n * Override the path mapping. Provide a full implementation, or spread\n * the default and override individual entries:\n *\n * ```tsx\n * <BPMRoutesProvider value={{\n * ...createDefaultBPMRoutes(),\n * caseDetail: (id) => `/workspace/cases/${id}`,\n * }}>\n * ```\n */\n readonly value?: BPMRoutes;\n readonly children: ReactNode;\n}\n\n/**\n * Wraps the BPM React tree with a {@link BPMRoutes} value. Mounting the\n * provider is **optional** — `useBPMRoutes()` falls back to\n * {@link createDefaultBPMRoutes} when no provider is present. Use it\n * only when the host needs to remap BPM's internal paths.\n */\nexport function BPMRoutesProvider({\n value,\n children,\n}: BPMRoutesProviderProps): ReactElement {\n const resolved = useMemo(() => value ?? createDefaultBPMRoutes(), [value]);\n return (\n <BPMRoutesContext.Provider value={resolved}>\n {children}\n </BPMRoutesContext.Provider>\n );\n}\n\n/**\n * Reads the host-configured {@link BPMRoutes}. Falls back to\n * {@link createDefaultBPMRoutes} when no `<BPMRoutesProvider>` ancestor\n * is mounted, so views remain self-contained.\n */\nexport function useBPMRoutes(): BPMRoutes {\n const value = useContext(BPMRoutesContext);\n return value ?? DEFAULT_ROUTES;\n}\n\nconst DEFAULT_ROUTES: BPMRoutes = createDefaultBPMRoutes();\n"],"mappings":";;;;AAoGA,SAAgB,IAAoC;CAClD,OAAO;EACL,iBAAiB;EACjB,aAAa;EACb,YAAY;EACZ,UAAU;EACV,cAAc;EACd,mBAAmB;EACnB,qBAAqB;EAErB,aAAa,MAAe,cAAc;EAC1C,UAAU,MACR,IACI,6BAA6B,mBAAmB,CAAU,MAC1D;EAEN,iBAAiB;EACjB,uBAAuB;EACvB,mBAAmB,MAAe,cAAc,EAAW;EAC3D,mBAAmB,MAAe,cAAc,EAAW;EAC3D,0BAA0B;EAE1B,4BAA4B;EAE5B,iBAAiB;EACjB,kBAAkB;EAClB,wBAAwB;CAC1B;AACF;AAEA,IAAM,IAAmB,EAAgC,IAAI;AAwB7D,SAAgB,EAAkB,EAChC,UACA,eACuC;CACvC,IAAM,IAAW,QAAc,KAAS,EAAuB,GAAG,CAAC,CAAK,CAAC;CACzE,OACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAC/B;CACwB,CAAA;AAE/B;AAOA,SAAgB,IAA0B;CAExC,OADc,EAAW,CAClB,KAAS;AAClB;AAEA,IAAM,IAA4B,EAAuB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";let e=require("react"),t=require("react/jsx-runtime");function n(){return{dashboard:()=>`/dashboard`,inbox:()=>`/inbox`,sent:()=>`/sent`,cc:()=>`/cc`,search:()=>`/search`,delegations:()=>`/delegations`,notifications:()=>`/notifications`,caseDetail:e=>`/instances/${e}`,caseNew:e=>e?`/instances/new?templateId=${encodeURIComponent(e)}`:`/instances/new`,templates:()=>`/templates`,templateCompose:()=>`/templates/compose`,templateDesigner:e=>`/templates/${e}/designer`,templateVersions:e=>`/templates/${e}/versions`,templateCategories:()=>`/templates/categories`,notificationSettings:()=>`/settings/notifications`,adminOrgs:()=>`/admin/orgs`,adminUsers:()=>`/admin/users`,adminDelegations:()=>`/admin/delegations`}}var r=(0,e.createContext)(null);function i({value:i,children:a}){let o=(0,e.useMemo)(()=>i??n(),[i]);return(0,t.jsx)(r.Provider,{value:o,children:a})}function a(){return(0,e.useContext)(r)??o}var o=n();Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return i}});
|
|
2
|
+
//# sourceMappingURL=routes-config-fDVHmvXi.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-config-fDVHmvXi.cjs","names":[],"sources":["../../src/lib/routes-config.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react';\n\n/**\n * Framework-agnostic path mapping every BPM view uses for internal\n * cross-navigation. When a host embeds BPM under a non-root prefix\n * (e.g. `/workspace/bpm/*`) it provides its own implementation through\n * `<BPMRoutesProvider value={...}>`; otherwise the default factory\n * preserves the historical `/instances/:id`, `/templates`, etc. paths.\n *\n * Function-shape (instead of string templates) means:\n * - Optional and multi-param routes can branch on argument presence\n * (`caseNew(templateId?)`).\n * - Query-string composition stays inside the host's resolver, not the\n * view.\n * - TypeScript flags missing arguments at compile time.\n *\n * Mirror sibling: {@link RouterAdapter} owns *navigation primitives*\n * (`push`, `replace`, `pathname`); `BPMRoutes` owns *path strings*. The\n * two compose: views call `router.push(routes.caseDetail(id))`.\n */\nexport interface BPMRoutes {\n /** Workflow dashboard landing. Used by sidebar + dashboard tiles. */\n dashboard(): string;\n /** Inbox (待簽) — pending tasks assigned to the current member. */\n inbox(): string;\n /** Sent (我發起的) — instances the current member initiated. */\n sent(): string;\n /** CC (抄送給我) — instances the current member is copied on. */\n cc(): string;\n /** Cross-view search. */\n search(): string;\n /** Personal delegation rules. */\n delegations(): string;\n /**\n * Reserved for hosts that want a dedicated `/notifications` page —\n * BPM's built-in views do **not** navigate here by default (the\n * notification drawer mounted by `<BPMNextProviders>` is the primary\n * unread-notification UX). The `createDefaultBPMRoutes()` factory\n * returns `'/notifications'` so consumers can mount their own page\n * at that path if desired; no BPM-shipped `pages/notifications`\n * shim exists.\n */\n notifications(): string;\n\n /**\n * Detail page for one approval instance.\n *\n * @example default → `/instances/abc123`\n */\n caseDetail(instanceId: string): string;\n /**\n * Launch a new approval instance. When `templateId` is passed, the\n * launch form is pre-populated for that template — by default this is\n * appended as a `?templateId=` query string so the path itself stays\n * stable for routing.\n *\n * @example default `caseNew()` → `/instances/new`\n * @example default `caseNew('tpl-1')` → `/instances/new?templateId=tpl-1`\n */\n caseNew(templateId?: string): string;\n\n /** Template index. */\n templates(): string;\n /**\n * Unified \"form + flow\" creation wizard. Designs a form and an approval\n * flow together, then publishes both atomically. This is the only place\n * forms are designed — there is no separate standalone form builder page.\n */\n templateCompose(): string;\n /** Template designer (xyflow canvas). */\n templateDesigner(templateId: string): string;\n /** Template version history. */\n templateVersions(templateId: string): string;\n /** Template categories admin. */\n templateCategories(): string;\n\n /** Per-member notification preferences. */\n notificationSettings(): string;\n\n /** Admin: organization tree management. */\n adminOrgs(): string;\n /** Admin: member directory mapping. */\n adminUsers(): string;\n /** Admin: delegation rule management. */\n adminDelegations(): string;\n}\n\n/**\n * Factory for the default `BPMRoutes` value. Reproduces the exact path\n * literals BPM views used before `BPMRoutesContext` existed, so existing\n * hosts (no provider mounted) keep working unchanged.\n */\nexport function createDefaultBPMRoutes(): BPMRoutes {\n return {\n dashboard: () => '/dashboard',\n inbox: () => '/inbox',\n sent: () => '/sent',\n cc: () => '/cc',\n search: () => '/search',\n delegations: () => '/delegations',\n notifications: () => '/notifications',\n\n caseDetail: (instanceId) => `/instances/${instanceId}`,\n caseNew: (templateId) =>\n templateId\n ? `/instances/new?templateId=${encodeURIComponent(templateId)}`\n : '/instances/new',\n\n templates: () => '/templates',\n templateCompose: () => '/templates/compose',\n templateDesigner: (templateId) => `/templates/${templateId}/designer`,\n templateVersions: (templateId) => `/templates/${templateId}/versions`,\n templateCategories: () => '/templates/categories',\n\n notificationSettings: () => '/settings/notifications',\n\n adminOrgs: () => '/admin/orgs',\n adminUsers: () => '/admin/users',\n adminDelegations: () => '/admin/delegations',\n };\n}\n\nconst BPMRoutesContext = createContext<BPMRoutes | null>(null);\n\nexport interface BPMRoutesProviderProps {\n /**\n * Override the path mapping. Provide a full implementation, or spread\n * the default and override individual entries:\n *\n * ```tsx\n * <BPMRoutesProvider value={{\n * ...createDefaultBPMRoutes(),\n * caseDetail: (id) => `/workspace/cases/${id}`,\n * }}>\n * ```\n */\n readonly value?: BPMRoutes;\n readonly children: ReactNode;\n}\n\n/**\n * Wraps the BPM React tree with a {@link BPMRoutes} value. Mounting the\n * provider is **optional** — `useBPMRoutes()` falls back to\n * {@link createDefaultBPMRoutes} when no provider is present. Use it\n * only when the host needs to remap BPM's internal paths.\n */\nexport function BPMRoutesProvider({\n value,\n children,\n}: BPMRoutesProviderProps): ReactElement {\n const resolved = useMemo(() => value ?? createDefaultBPMRoutes(), [value]);\n return (\n <BPMRoutesContext.Provider value={resolved}>\n {children}\n </BPMRoutesContext.Provider>\n );\n}\n\n/**\n * Reads the host-configured {@link BPMRoutes}. Falls back to\n * {@link createDefaultBPMRoutes} when no `<BPMRoutesProvider>` ancestor\n * is mounted, so views remain self-contained.\n */\nexport function useBPMRoutes(): BPMRoutes {\n const value = useContext(BPMRoutesContext);\n return value ?? DEFAULT_ROUTES;\n}\n\nconst DEFAULT_ROUTES: BPMRoutes = createDefaultBPMRoutes();\n"],"mappings":"mEAoGA,SAAgB,GAAoC,CAClD,MAAO,CACL,cAAiB,aACjB,UAAa,SACb,SAAY,QACZ,OAAU,MACV,WAAc,UACd,gBAAmB,eACnB,kBAAqB,iBAErB,WAAa,GAAe,cAAc,IAC1C,QAAU,GACR,EACI,6BAA6B,mBAAmB,CAAU,IAC1D,iBAEN,cAAiB,aACjB,oBAAuB,qBACvB,iBAAmB,GAAe,cAAc,EAAW,WAC3D,iBAAmB,GAAe,cAAc,EAAW,WAC3D,uBAA0B,wBAE1B,yBAA4B,0BAE5B,cAAiB,cACjB,eAAkB,eAClB,qBAAwB,oBAC1B,CACF,CAEA,IAAM,GAAA,EAAA,EAAA,eAAmD,IAAI,EAwB7D,SAAgB,EAAkB,CAChC,QACA,YACuC,CACvC,IAAM,GAAA,EAAA,EAAA,aAAyB,GAAS,EAAuB,EAAG,CAAC,CAAK,CAAC,EACzE,OACE,EAAA,EAAA,KAAC,EAAiB,SAAlB,CAA2B,MAAO,EAC/B,UACwB,CAAA,CAE/B,CAOA,SAAgB,GAA0B,CAExC,OAAA,EAAA,EAAA,YADyB,CAClB,GAAS,CAClB,CAEA,IAAM,EAA4B,EAAuB"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});require('./index.css');const e=require("./chunks/chunk-CMqjfN_6.cjs"),t=require("./chunks/router-adapter
|
|
1
|
+
"use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});require('./index.css');const e=require("./chunks/chunk-CMqjfN_6.cjs"),t=require("./chunks/router-adapter--gYs13E8.cjs"),n=require("./chunks/auth-provider-4BeCw7cI.cjs"),r=require("./chunks/format-date-time-XxBzF0F5.cjs"),i=require("./chunks/routes-config-fDVHmvXi.cjs"),a=require("./chunks/admin-pickers-Btvij1at.cjs"),o=require("./chunks/approval-instance-list-page-BMUKxzcz.cjs"),s=require("./chunks/bpm-form-field-Bc6k4ZEO.cjs"),c=require("./chunks/dashboard-page-DwHQY6Ki.cjs");let l=require("react"),u=require("@mezzanine-ui/react"),d=require("react/jsx-runtime"),f=require("@rytass/bpm-core-client/workflow"),p=require("@mezzanine-ui/react/moment"),m=require("@mezzanine-ui/react/Drawer");m=e.t(m,1);let h=require("@mezzanine-ui/react/Modal");h=e.t(h,1);let g=require("@mezzanine-ui/react/NotificationCenter");g=e.t(g,1);let _=require("@mezzanine-ui/react/Textarea");_=e.t(_,1);let v=require("@mezzanine-ui/icons");var y=(0,l.createContext)(null);function b({children:e}){let[t,n]=(0,l.useState)(!1),r=(0,l.useCallback)(()=>{n(!0)},[]),i=(0,l.useCallback)(()=>{n(!1)},[]),a=(0,l.useCallback)(()=>{n(e=>!e)},[]),o=(0,l.useMemo)(()=>({close:i,isOpen:t,open:r,toggle:a}),[i,t,r,a]);return(0,d.jsx)(y.Provider,{value:o,children:e})}function x(){return(0,l.useContext)(y)||{close:()=>void 0,isOpen:!1,open:()=>void 0,toggle:()=>void 0}}var S=(0,l.createContext)(null);function C({children:e}){let{member:t}=n.n(),r=t?.memberId??null,[i,a]=(0,l.useState)(0),o=(0,l.useCallback)(async()=>{if(!r)return a(0),0;let e=await(0,f.readUnreadNotificationCount)(r);return a(e),e},[r]);(0,l.useEffect)(()=>{let e=!0;return(async()=>{try{let t=await o();e&&a(t)}catch{e&&a(0)}})(),()=>{e=!1}},[o]);let s=(0,l.useMemo)(()=>({refreshUnreadCount:o,unreadCount:i}),[o,i]);return(0,d.jsx)(S.Provider,{value:s,children:e})}function w(){return(0,l.useContext)(S)||{refreshUnreadCount:async()=>0,unreadCount:0}}var T=[`today`,`yesterday`,`past7Days`,`earlier`],ee={earlier:`更早`,past7Days:`過去七天`,today:`今天`,yesterday:`昨天`},E=50;function te(e){return[...e.actionable?[{id:`approve`,name:`同意`},{id:`reject`,name:`拒絕`}]:[],...e.instanceId?[{id:`view`,name:`查看案件`}]:[],...e.status===`READ`?[]:[{id:`read`,name:`標為已讀`}]]}var D={APPROVED:`簽核任務已同意`,REJECTED:`簽核任務已拒絕`,RETURNED:`簽核任務已退回`,SUPERSEDED:`簽核任務已結束`,TRANSFERRED:`簽核任務已轉派`};function ne(e){return e.resolution?D[e.resolution]:e.title}function O(e){return e.resolution===`APPROVED`?`success`:e.resolution===`REJECTED`?`error`:e.resolution?`info`:A(e.type)}function k(){let e=t.r(),r=i.r(),{member:a}=n.n(),{close:o,isOpen:s}=x(),{refreshUnreadCount:c}=w(),u=a?.memberId??null,[p,v]=(0,l.useState)([]),[y,b]=(0,l.useState)(0),[S,C]=(0,l.useState)(1),[D,k]=(0,l.useState)(!1),[A,M]=(0,l.useState)(!1),[N,P]=(0,l.useState)(null),[F,I]=(0,l.useState)(null),[L,ie]=(0,l.useState)(`all`),[R,z]=(0,l.useState)(null),[B,V]=(0,l.useState)(``),[H,U]=(0,l.useState)(!1),W=B.trim(),G=(0,l.useCallback)(async(e,t)=>{if(u){k(!0),P(null);try{let n=await(0,f.listNotifications)({includeRead:!0,page:e,pageSize:E,recipientMemberId:u});v(e=>t?[...e,...n.notifications]:n.notifications),b(n.totalCount),C(e),await c()}catch(e){P(j(e))}finally{k(!1)}}},[u,c]);(0,l.useEffect)(()=>{!s||!u||(P(null),I(null),G(1,!1))},[s,u,G]);let ae=(0,l.useCallback)(e=>{let t=e.target.value;(t===`all`||t===`read`||t===`unread`)&&ie(t)},[]),oe=(0,l.useCallback)(async()=>{if(!(!u||A)){M(!0),P(null);try{await(0,f.markAllNotificationsRead)({recipientMemberId:u}),await G(1,!1)}catch(e){P(j(e))}finally{M(!1)}}},[A,u,G]),se=(0,l.useCallback)(()=>{D||G(S+1,!0)},[D,G,S]),K=(0,l.useCallback)(async e=>{if(u)try{await(0,f.markNotificationRead)({id:e,readerMemberId:u}),await G(1,!1)}catch(e){P(j(e))}},[u,G]),q=(0,l.useCallback)(async t=>{if(!(!t.instanceId||!u))try{t.status!==`READ`&&(await(0,f.markNotificationRead)({id:t.id,readerMemberId:u}),await c()),o(),e.push(r.caseDetail(t.instanceId))}catch(e){P(j(e))}},[o,u,c,e,r]),J=(0,l.useCallback)(async e=>{if(!(!e.taskId||!u||H)){U(!0),P(null),I(null);try{await(0,f.decideTask)({action:`APPROVED`,comment:null,decidedByMemberId:u,taskId:e.taskId}),I(`已同意「${e.title}」。`),await G(1,!1)}catch(e){P(j(e))}finally{U(!1)}}},[u,H,G]),Y=(0,l.useCallback)(e=>{z(e),V(``)},[]),X=(0,l.useCallback)(()=>{z(null),V(``)},[]),ce=(0,l.useCallback)(async()=>{let e=R;if(!(!e?.taskId||!u||!W||H)){U(!0),P(null),I(null);try{await(0,f.decideTask)({action:`REJECTED`,comment:W,decidedByMemberId:u,taskId:e.taskId}),z(null),V(``),I(`已拒絕「${e.title}」。`),await G(1,!1)}catch(e){P(j(e))}finally{U(!1)}}},[u,H,G,R,W]),le=(0,l.useCallback)((e,t)=>{let n=t.id;n===`approve`?J(e):n===`reject`?Y(e):n===`view`?q(e):n===`read`&&K(e.id)},[J,K,q,Y]),ue=(0,l.useCallback)((e,t)=>{t instanceof Element&&t.closest(`button`)||q(e)},[q]),de=(0,l.useCallback)((e,t)=>{t.key!==`Enter`&&t.key!==` `||t.target instanceof Element&&t.target.closest(`button`)||(t.preventDefault(),q(e))},[q]),Z=(0,l.useMemo)(()=>p.filter(e=>L===`all`?!0:L===`read`?e.status===`READ`:e.status!==`READ`),[L,p]),Q=(0,l.useMemo)(()=>{let e=new Date,t=T.reduce((e,t)=>(e[t]=[],e),{earlier:[],past7Days:[],today:[],yesterday:[]});return Z.forEach(n=>{t[re(n.createdAt,e)].push(n)}),T.map(e=>[e,t[e]]).filter(([,e])=>e.length>0)},[Z]),$=p.length<y;return u?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(m.default,{bottomGhostActionDisabled:A||D,bottomGhostActionLoading:A,bottomGhostActionText:`全部標為已讀`,bottomOnGhostActionClick:()=>{oe()},bottomOnPrimaryActionClick:()=>{se()},bottomPrimaryActionDisabled:!$||D,bottomPrimaryActionLoading:D&&$,bottomPrimaryActionText:$?`載入更多`:`已顯示全部`,contentKey:`${L}:${p.length}`,filterAreaAllRadioLabel:`全部`,filterAreaOnRadioChange:ae,filterAreaReadRadioLabel:`已讀`,filterAreaShow:!0,filterAreaUnreadRadioLabel:`未讀`,filterAreaValue:L,headerTitle:`通知中心`,isBottomDisplay:!0,isHeaderDisplay:!0,onClose:o,open:s,size:`medium`,children:(0,d.jsxs)(`div`,{role:`list`,children:[N?(0,d.jsx)(`p`,{role:`alert`,style:{color:`var(--mzn-color-text-error, #d92d20)`,padding:`12px 16px`},children:N}):null,F?(0,d.jsx)(`p`,{role:`status`,style:{color:`var(--mzn-color-text-success, #079455)`,padding:`12px 16px`},children:F}):null,Q.length===0?(0,d.jsx)(`p`,{style:{color:`var(--mzn-color-text-secondary, #6b7280)`,padding:`24px 16px`,textAlign:`center`},children:D?`載入中…`:`目前沒有通知`}):null,Q.map(([e,t])=>(0,d.jsx)(l.Fragment,{children:t.map((t,n)=>{let r=t.instanceId!==null;return(0,d.jsx)(`div`,{onClick:r?e=>{ue(t,e.target)}:void 0,onKeyDown:r?e=>{de(t,e)}:void 0,role:r?`button`:void 0,style:r?{cursor:`pointer`}:void 0,tabIndex:r?0:void 0,children:(0,d.jsx)(g.default,{description:t.body,onBadgeSelect:e=>{le(t,e)},options:[...te(t)],prependTips:n===0?ee[e]:void 0,reference:t.id,severity:O(t),showBadge:t.status!==`READ`,timeStamp:t.createdAt,title:ne(t),type:`drawer`})},t.id)})},e))]})}),(0,d.jsx)(h.default,{cancelText:`取消`,confirmButtonProps:{disabled:!W,variant:`destructive-primary`},confirmText:`送出拒絕`,loading:H,modalStatusType:`error`,modalType:`standard`,onCancel:X,onClose:X,onConfirm:()=>{ce()},open:R!==null,showModalFooter:!0,showModalHeader:!0,size:`regular`,supportingText:`拒絕案件時必須留下原因,供發起人與後續追蹤查看。`,title:`拒絕原因`,children:(0,d.jsx)(_.default,{autoFocus:!0,onChange:e=>{V(e.target.value)},placeholder:`請輸入拒絕原因`,rows:4,value:B})})]}):null}function A(e){return e===`SLA_OVERDUE`?`error`:e===`SLA_WARNING`?`warning`:e===`INSTANCE_COMPLETED`?`success`:`info`}function re(e,t){let n=new Date(e);if(Number.isNaN(n.getTime()))return`earlier`;let r=new Date(t.getFullYear(),t.getMonth(),t.getDate()),i=new Date(n.getFullYear(),n.getMonth(),n.getDate());if(i.getTime()===r.getTime())return`today`;let a=new Date(r);return a.setDate(a.getDate()-1),i.getTime()===a.getTime()?`yesterday`:(t.getTime()-n.getTime())/(1e3*60*60*24)<=7?`past7Days`:`earlier`}function j(e){return e instanceof Error?e.message:`發生未知錯誤`}function M({children:e,locale:t=p.CalendarLocale.ZH_TW,publicPaths:r,loginPath:i}){return(0,d.jsx)(p.CalendarConfigProviderMoment,{locale:t,children:(0,d.jsx)(n.t,{publicPaths:r,loginPath:i,children:(0,d.jsx)(C,{children:(0,d.jsxs)(b,{children:[e,(0,d.jsx)(k,{})]})})})})}function N(){let{logout:e}=n.n();return e}function P(){let{member:e}=n.n();return e}var F={root:`bpm_root_jl5S-`,badge:`bpm_badge_SIKJk`};function I({label:e=`通知中心`}={}){let{open:t}=x(),{unreadCount:n}=w(),r=n>0?`${e},${n} 則未讀`:e;return(0,d.jsxs)(`span`,{className:F.root,children:[(0,d.jsx)(u.NavigationIconButton,{"aria-label":r,icon:v.NotificationUnreadIcon,onClick:()=>{t()},title:e,type:`button`}),n>0?(0,d.jsx)(`span`,{className:F.badge,children:n>99?`99+`:n}):null]})}exports.ApprovalInstanceListPage=o.t,exports.AuthProvider=n.t,exports.BPMFormField=s.t,exports.BPMNotificationBellButton=I,exports.BPMRoutesProvider=i.t,exports.DashboardPage=c.t,exports.MemberPicker=a.t,exports.NotificationDrawer=k,exports.NotificationDrawerProvider=b,exports.NotificationUnreadProvider=C,exports.OrgUnitPicker=a.n,exports.PositionPicker=a.r,exports.Providers=M,exports.RouterAdapterProvider=t.t,exports.createDefaultBPMRoutes=i.n,exports.defaultBrowserSearchParams=t.n,exports.formatDateTime=r.t,exports.readMemberOption=a.i,exports.readOrgUnitOption=a.a,exports.readPositionOption=a.o,exports.useAuth=n.n,exports.useBPMLogout=N,exports.useBPMMember=P,exports.useBPMRoutes=i.r,exports.useNotificationDrawer=x,exports.useNotificationUnread=w,exports.useRouterAdapter=t.r;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","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":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","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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"}
|