@rytass/bpm-core-react 0.15.0 → 0.17.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 +86 -0
- package/README.md +54 -6
- package/dist/chunks/{FormBuilderView-CLg-JYmu.js → FormBuilderView-BYL42M0d.js} +13 -33
- package/dist/chunks/FormBuilderView-BYL42M0d.js.map +1 -0
- package/dist/chunks/FormBuilderView-aBcA-_tt.cjs +3 -0
- package/dist/chunks/FormBuilderView-aBcA-_tt.cjs.map +1 -0
- package/dist/chunks/FormRendererView-DRFi_IH7.cjs +4 -0
- package/dist/chunks/FormRendererView-DRFi_IH7.cjs.map +1 -0
- package/dist/chunks/{FormRendererView-DU9LLrOF.js → FormRendererView-cZGOOjU5.js} +319 -332
- package/dist/chunks/FormRendererView-cZGOOjU5.js.map +1 -0
- package/dist/chunks/{admin-pickers-DLlG_1du.js → admin-pickers-DxhzECkD.js} +52 -51
- package/dist/chunks/admin-pickers-DxhzECkD.js.map +1 -0
- package/dist/chunks/admin-pickers-qe3j--gX.cjs +2 -0
- package/dist/chunks/admin-pickers-qe3j--gX.cjs.map +1 -0
- package/dist/chunks/approval-instance-list-page-CPXMP3PL.js.map +1 -1
- package/dist/chunks/approval-instance-list-page-ax0pm8bt.cjs.map +1 -1
- package/dist/chunks/auth-provider-4BeCw7cI.cjs.map +1 -1
- package/dist/chunks/auth-provider-B5oPmvk2.js.map +1 -1
- package/dist/chunks/bpm-form-field-Bc6k4ZEO.cjs.map +1 -1
- package/dist/chunks/bpm-form-field-Cao0rMol.js.map +1 -1
- package/dist/chunks/{categories-DGlxuC6X.js → categories-1gKxeEqw.js} +4 -10
- package/dist/chunks/categories-1gKxeEqw.js.map +1 -0
- package/dist/chunks/{categories-Yid05WAX.cjs → categories-DjENouRm.cjs} +2 -2
- package/dist/chunks/categories-DjENouRm.cjs.map +1 -0
- package/dist/chunks/{compose-BtQPBd1Q.js → compose-Crklayzy.js} +4 -4
- package/dist/chunks/compose-Crklayzy.js.map +1 -0
- package/dist/chunks/{compose-DZhTQOt4.cjs → compose-DZ7RKvUp.cjs} +2 -2
- package/dist/chunks/compose-DZ7RKvUp.cjs.map +1 -0
- package/dist/chunks/dashboard-page-BWQBSGDq.cjs +2 -0
- package/dist/chunks/dashboard-page-BWQBSGDq.cjs.map +1 -0
- package/dist/chunks/{dashboard-page-CeCLV1tp.js → dashboard-page-DdBvV6YI.js} +15 -16
- package/dist/chunks/dashboard-page-DdBvV6YI.js.map +1 -0
- package/dist/chunks/delegations-Ckf03icM.cjs +2 -0
- package/dist/chunks/delegations-Ckf03icM.cjs.map +1 -0
- package/dist/chunks/{delegations-DRupxJ4_.js → delegations-MxF3uJGW.js} +205 -215
- package/dist/chunks/delegations-MxF3uJGW.js.map +1 -0
- package/dist/chunks/delegations-V9HIe3_P.cjs +2 -0
- package/dist/chunks/delegations-V9HIe3_P.cjs.map +1 -0
- package/dist/chunks/{delegations-B8gLSt0i.js → delegations-xUt1tj8d.js} +168 -173
- package/dist/chunks/delegations-xUt1tj8d.js.map +1 -0
- package/dist/chunks/{designer-CrbBRPUU.js → designer-D9eFma1W.js} +1180 -1134
- package/dist/chunks/designer-D9eFma1W.js.map +1 -0
- package/dist/chunks/designer-sJGTGYOf.cjs +65 -0
- package/dist/chunks/designer-sJGTGYOf.cjs.map +1 -0
- package/dist/chunks/detail-DRCTnuKm.cjs +2 -0
- package/dist/chunks/detail-DRCTnuKm.cjs.map +1 -0
- package/dist/chunks/{detail-_c21WQla.js → detail-DScLf42P.js} +849 -836
- package/dist/chunks/detail-DScLf42P.js.map +1 -0
- package/dist/chunks/format-date-time-CB-LxzqT.js.map +1 -1
- package/dist/chunks/format-date-time-D-cx5M8z.cjs.map +1 -1
- package/dist/chunks/format-member-label-DudVI55w.js +13 -0
- package/dist/chunks/format-member-label-DudVI55w.js.map +1 -0
- package/dist/chunks/format-member-label-t7ANTF62.cjs +2 -0
- package/dist/chunks/format-member-label-t7ANTF62.cjs.map +1 -0
- package/dist/chunks/login-AtVxZBsC.cjs.map +1 -1
- package/dist/chunks/login-C6qlTC6F.js.map +1 -1
- package/dist/chunks/notifications-Bc4mgSds.cjs.map +1 -1
- package/dist/chunks/notifications-DJ5wzCye.js.map +1 -1
- package/dist/chunks/{orgs-BpqVu5ka.js → orgs-CY0i1fie.js} +787 -766
- package/dist/chunks/orgs-CY0i1fie.js.map +1 -0
- package/dist/chunks/orgs-Dr0FFoZo.cjs +2 -0
- package/dist/chunks/orgs-Dr0FFoZo.cjs.map +1 -0
- package/dist/chunks/providers-DHtDZFNA.cjs.map +1 -1
- package/dist/chunks/providers-J5eyByQj.js.map +1 -1
- package/dist/chunks/router-adapter--gYs13E8.cjs.map +1 -1
- package/dist/chunks/router-adapter-DftlFTOd.js.map +1 -1
- package/dist/chunks/routes-config-C1j_vFKo.cjs.map +1 -1
- package/dist/chunks/routes-config-yZbESPlw.js.map +1 -1
- package/dist/chunks/task-node-label-BF6UllxP.cjs.map +1 -1
- package/dist/chunks/task-node-label-CWZgOBj0.js.map +1 -1
- package/dist/chunks/use-bpm-member-D57LKe_u.js.map +1 -1
- package/dist/chunks/use-bpm-member-DpNAQtNC.cjs.map +1 -1
- package/dist/chunks/users-BCyePbwB.js +422 -0
- package/dist/chunks/users-BCyePbwB.js.map +1 -0
- package/dist/chunks/users-DVBJluFR.cjs +2 -0
- package/dist/chunks/users-DVBJluFR.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/format-member-label.d.ts +12 -0
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.js.map +1 -1
- package/dist/next/workflow-chat-route.cjs.map +1 -1
- package/dist/next/workflow-chat-route.js.map +1 -1
- package/dist/pages/admin/delegations/index.cjs +1 -1
- package/dist/pages/admin/delegations/index.cjs.map +1 -1
- package/dist/pages/admin/delegations/index.js +1 -1
- package/dist/pages/admin/delegations/index.js.map +1 -1
- package/dist/pages/admin/orgs/index.cjs +1 -1
- package/dist/pages/admin/orgs/index.cjs.map +1 -1
- package/dist/pages/admin/orgs/index.js +1 -1
- package/dist/pages/admin/orgs/index.js.map +1 -1
- package/dist/pages/admin/users/index.cjs +1 -1
- package/dist/pages/admin/users/index.cjs.map +1 -1
- package/dist/pages/admin/users/index.js +1 -1
- package/dist/pages/admin/users/index.js.map +1 -1
- package/dist/pages/admin/webhook-endpoints/index.cjs.map +1 -1
- package/dist/pages/admin/webhook-endpoints/index.js.map +1 -1
- package/dist/pages/cc/index.cjs.map +1 -1
- package/dist/pages/cc/index.js.map +1 -1
- package/dist/pages/dashboard/index.cjs.map +1 -1
- package/dist/pages/dashboard/index.js.map +1 -1
- package/dist/pages/delegations/index.cjs +1 -1
- package/dist/pages/delegations/index.cjs.map +1 -1
- package/dist/pages/delegations/index.js +1 -1
- package/dist/pages/delegations/index.js.map +1 -1
- package/dist/pages/inbox/index.cjs.map +1 -1
- package/dist/pages/inbox/index.js.map +1 -1
- package/dist/pages/instances/detail/index.cjs +1 -1
- package/dist/pages/instances/detail/index.cjs.map +1 -1
- package/dist/pages/instances/detail/index.js +1 -1
- package/dist/pages/instances/detail/index.js.map +1 -1
- package/dist/pages/instances/new/index.cjs.map +1 -1
- package/dist/pages/instances/new/index.js.map +1 -1
- package/dist/pages/login/index.cjs.map +1 -1
- package/dist/pages/login/index.js.map +1 -1
- package/dist/pages/root/index.cjs.map +1 -1
- package/dist/pages/root/index.js.map +1 -1
- package/dist/pages/search/index.cjs.map +1 -1
- package/dist/pages/search/index.js.map +1 -1
- package/dist/pages/sent/index.cjs.map +1 -1
- package/dist/pages/sent/index.js.map +1 -1
- package/dist/pages/settings/notifications/index.cjs.map +1 -1
- package/dist/pages/settings/notifications/index.js.map +1 -1
- package/dist/pages/templates/categories/index.cjs +1 -1
- package/dist/pages/templates/categories/index.cjs.map +1 -1
- package/dist/pages/templates/categories/index.js +1 -1
- package/dist/pages/templates/categories/index.js.map +1 -1
- package/dist/pages/templates/compose/index.cjs +1 -1
- package/dist/pages/templates/compose/index.cjs.map +1 -1
- package/dist/pages/templates/compose/index.js +1 -1
- package/dist/pages/templates/compose/index.js.map +1 -1
- 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 +1 -1
- package/dist/pages/templates/designer/index.js.map +1 -1
- package/dist/pages/templates/index.cjs.map +1 -1
- package/dist/pages/templates/index.js.map +1 -1
- package/dist/pages/templates/versions/index.cjs.map +1 -1
- package/dist/pages/templates/versions/index.js.map +1 -1
- package/dist/views/admin/delegations/index.cjs +1 -1
- package/dist/views/admin/delegations/index.js +1 -2
- package/dist/views/admin/index.cjs +1 -1
- package/dist/views/admin/index.js +3 -3
- package/dist/views/admin/orgs/index.cjs +1 -1
- package/dist/views/admin/orgs/index.js +1 -2
- package/dist/views/admin/users/MemberTaskReassignModal.d.ts +13 -0
- package/dist/views/admin/users/index.cjs +1 -1
- package/dist/views/admin/users/index.js +1 -2
- package/dist/views/admin/webhook-endpoints/index.cjs.map +1 -1
- package/dist/views/admin/webhook-endpoints/index.js.map +1 -1
- package/dist/views/cc/index.cjs.map +1 -1
- package/dist/views/cc/index.js.map +1 -1
- package/dist/views/dashboard/index.cjs +1 -1
- package/dist/views/dashboard/index.cjs.map +1 -1
- package/dist/views/dashboard/index.js +1 -1
- package/dist/views/dashboard/index.js.map +1 -1
- package/dist/views/delegations/index.cjs +1 -1
- package/dist/views/delegations/index.js +1 -2
- package/dist/views/forms/builder/index.cjs +1 -1
- package/dist/views/forms/builder/index.js +1 -2
- package/dist/views/forms/renderer/index.cjs +1 -1
- package/dist/views/forms/renderer/index.js +1 -2
- package/dist/views/inbox/index.cjs +1 -1
- package/dist/views/inbox/index.cjs.map +1 -1
- package/dist/views/inbox/index.js +1 -5
- package/dist/views/inbox/index.js.map +1 -1
- package/dist/views/instances/detail/index.cjs +1 -1
- package/dist/views/instances/detail/index.js +1 -2
- package/dist/views/instances/index.js +1 -2
- package/dist/views/instances/new/index.cjs +1 -1
- package/dist/views/instances/new/index.cjs.map +1 -1
- package/dist/views/instances/new/index.js +4 -11
- package/dist/views/instances/new/index.js.map +1 -1
- package/dist/views/login/index.js +1 -2
- package/dist/views/search/index.cjs.map +1 -1
- package/dist/views/search/index.js.map +1 -1
- package/dist/views/sent/index.cjs.map +1 -1
- package/dist/views/sent/index.js.map +1 -1
- package/dist/views/settings/index.js +1 -2
- package/dist/views/settings/notifications/index.js +1 -2
- package/dist/views/templates/categories/index.cjs +1 -1
- package/dist/views/templates/categories/index.js +1 -2
- package/dist/views/templates/compose/index.cjs +1 -1
- package/dist/views/templates/compose/index.js +1 -2
- package/dist/views/templates/designer/index.cjs +1 -1
- package/dist/views/templates/designer/index.js +1 -2
- package/dist/views/templates/index.cjs +1 -1
- package/dist/views/templates/index.cjs.map +1 -1
- package/dist/views/templates/index.js +1 -1
- package/dist/views/templates/index.js.map +1 -1
- package/dist/views/templates/versions/index.cjs.map +1 -1
- package/dist/views/templates/versions/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunks/FormBuilderView-6bCbIkz6.cjs +0 -3
- package/dist/chunks/FormBuilderView-6bCbIkz6.cjs.map +0 -1
- package/dist/chunks/FormBuilderView-CLg-JYmu.js.map +0 -1
- package/dist/chunks/FormRendererView-DU9LLrOF.js.map +0 -1
- package/dist/chunks/FormRendererView-gctFIqwC.cjs +0 -4
- package/dist/chunks/FormRendererView-gctFIqwC.cjs.map +0 -1
- package/dist/chunks/admin-pickers-Btvij1at.cjs +0 -2
- package/dist/chunks/admin-pickers-Btvij1at.cjs.map +0 -1
- package/dist/chunks/admin-pickers-DLlG_1du.js.map +0 -1
- package/dist/chunks/categories-DGlxuC6X.js.map +0 -1
- package/dist/chunks/categories-Yid05WAX.cjs.map +0 -1
- package/dist/chunks/compose-BtQPBd1Q.js.map +0 -1
- package/dist/chunks/compose-DZhTQOt4.cjs.map +0 -1
- package/dist/chunks/dashboard-page-CeCLV1tp.js.map +0 -1
- package/dist/chunks/dashboard-page-DooFJsxM.cjs +0 -2
- package/dist/chunks/dashboard-page-DooFJsxM.cjs.map +0 -1
- package/dist/chunks/delegations-8LsxcQM4.cjs +0 -2
- package/dist/chunks/delegations-8LsxcQM4.cjs.map +0 -1
- package/dist/chunks/delegations-B8gLSt0i.js.map +0 -1
- package/dist/chunks/delegations-DRupxJ4_.js.map +0 -1
- package/dist/chunks/delegations-DlYwMiAW.cjs +0 -2
- package/dist/chunks/delegations-DlYwMiAW.cjs.map +0 -1
- package/dist/chunks/designer-CrbBRPUU.js.map +0 -1
- package/dist/chunks/designer-D3_lWmfa.cjs +0 -65
- package/dist/chunks/designer-D3_lWmfa.cjs.map +0 -1
- package/dist/chunks/detail-CVXb5Vy8.cjs +0 -2
- package/dist/chunks/detail-CVXb5Vy8.cjs.map +0 -1
- package/dist/chunks/detail-_c21WQla.js.map +0 -1
- package/dist/chunks/orgs-BpqVu5ka.js.map +0 -1
- package/dist/chunks/orgs-D0-5BaHT.cjs +0 -2
- package/dist/chunks/orgs-D0-5BaHT.cjs.map +0 -1
- package/dist/chunks/users-C9LA-Rd7.cjs +0 -2
- package/dist/chunks/users-C9LA-Rd7.cjs.map +0 -1
- package/dist/chunks/users-FWrb3ehW.js +0 -214
- package/dist/chunks/users-FWrb3ehW.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FormRendererView-gctFIqwC.cjs","names":[],"sources":["../../src/views/forms/renderer/form-data-source-field.ts","../../src/views/forms/renderer/form-field-link.ts","../../src/lib/member-profile-resolution.ts","../../src/views/forms/renderer/member-field-options.ts","../../src/views/forms/renderer/member-field.tsx","../../src/views/forms/renderer/FormRendererView.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldOption,\n FormFieldValue,\n FormUiSchema,\n isFormDataSourceFieldDefinition,\n} from '@rytass/bpm-core-shared/form';\nimport {\n FormDataSourceErrorCode,\n FormDataSourceFieldStatus,\n FormRendererValues,\n mergeFormDataSourceOptions,\n previewResolveFormFieldOptions,\n readFormDataSourceErrorCode,\n readFormDataSourceErrorMessage,\n readMissingFormDataSourceDependencies,\n readMissingFormDataSourceOptionValues,\n previewFormFieldOptions,\n readFormDataSourceSelectedValues,\n readFormDataSourceValueSignature,\n readSelectedFormDataSourceOptions,\n readFormFieldOptions,\n resolveFormFieldOptions,\n} from '@rytass/bpm-core-client/form';\n\nexport type FormRendererDataSourceContext =\n | { readonly kind: 'preview' }\n | {\n readonly instanceId?: string | null;\n readonly kind: 'runtime';\n readonly templateId?: string | null;\n };\n\nexport interface FormDataSourceFieldState {\n /**\n * False when nothing can be re-requested — a read-only or context-less field\n * has no query to retry, so the view must not offer a dead retry action.\n */\n readonly canRetry: boolean;\n readonly error: string | null;\n readonly hasValue: boolean;\n readonly hasNextPage: boolean;\n readonly invalidValues: readonly string[];\n readonly onReachBottom: () => void;\n readonly onSearch: (searchText: string) => void;\n readonly options: readonly FormFieldOption[];\n readonly retry: () => void;\n readonly status: FormDataSourceFieldStatus;\n}\n\n/**\n * What makes a table cell different from a top-level field: it is addressed by\n * a schema path, snapshotted under an instance path, and its `ROW_FIELD`\n * bindings read from its own row rather than the form (ADR 16 §3.3, §3.5).\n */\nexport interface UseFormDataSourceFieldRow {\n /** The row as it was loaded, so a returned case can tell what changed. */\n readonly initialValues?: FormRendererValues;\n readonly values: FormRendererValues;\n}\n\nexport interface UseFormDataSourceFieldInput {\n readonly context?: FormRendererDataSourceContext;\n readonly field: FormFieldDefinition;\n /** Schema path used to address the field; defaults to its own key. */\n readonly fieldPath?: string;\n readonly formData: FormRendererValues;\n readonly initialFormData?: FormRendererValues;\n readonly initialValue?: FormFieldValue | undefined;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n /** Set only for a table cell. */\n readonly row?: UseFormDataSourceFieldRow;\n readonly schema: FormDefinitionSchema;\n /** Snapshot map key; defaults to the field key. */\n readonly snapshotKey?: string;\n readonly uiSchema: FormUiSchema;\n}\n\ninterface FormDataSourceResolution {\n readonly signature: string;\n readonly unresolvedValues: readonly string[];\n}\n\nconst EMPTY_FIELD_STATE: FormDataSourceFieldState = {\n canRetry: false,\n error: null,\n hasValue: false,\n hasNextPage: false,\n invalidValues: [],\n onReachBottom: (): void => undefined,\n onSearch: (): void => undefined,\n options: [],\n retry: (): void => undefined,\n status: 'IDLE',\n};\n\nconst NO_INVALID_VALUES: readonly string[] = [];\n\n/**\n * A dropdown only fires `onReachBottom` once its content overflows. A source\n * whose first page is smaller than the menu is therefore unreachable past page\n * one — `demo.cost-centers` serves 3 at a time, which fits exactly, so nothing\n * ever scrolls and the remaining options cannot be selected at all. Keep\n * pulling pages until there are enough rows to scroll, or the source runs out.\n */\nconst MIN_SCROLLABLE_OPTION_COUNT = 10;\n\n/**\n * Failures the filler can clear on their own by typing differently. They are\n * reported as an inline hint and never turn the field unavailable, because a\n * short search string says nothing about the validity of an existing value.\n */\nconst RECOVERABLE_ERROR_CODES: readonly FormDataSourceErrorCode[] = [\n 'FORM_DATA_SOURCE_SEARCH_NOT_SUPPORTED',\n 'FORM_DATA_SOURCE_SEARCH_TOO_SHORT',\n];\n\nexport function useFormDataSourceField(\n input: UseFormDataSourceFieldInput,\n): FormDataSourceFieldState {\n const dynamicField = isFormDataSourceFieldDefinition(input.field)\n ? input.field\n : null;\n const queryFieldKey = input.fieldPath ?? dynamicField?.fieldKey ?? '';\n const snapshot = dynamicField\n ? input.optionSnapshots?.[input.snapshotKey ?? dynamicField.fieldKey]\n : undefined;\n // The field object is rebuilt on every parent render, so depending on its\n // identity re-ran the load effect three extra times per change — each request\n // aborting the last. Depend on what actually decides the query instead.\n const dynamicFieldSignature = dynamicField\n ? JSON.stringify({\n dataSource: dynamicField.dataSource,\n fieldKey: dynamicField.fieldKey,\n type: dynamicField.type,\n })\n : '';\n const snapshotSignature = JSON.stringify(snapshot ?? null);\n const snapshotOptions = useMemo(\n (): readonly FormFieldOption[] => snapshot?.options ?? [],\n [snapshotSignature],\n );\n const formDataSignature = JSON.stringify(input.formData);\n const initialFormDataSignature = JSON.stringify(input.initialFormData ?? {});\n const schemaSignature = JSON.stringify(input.schema);\n const uiSchemaSignature = JSON.stringify(input.uiSchema);\n const contextKind = input.context?.kind;\n const contextInstanceId =\n input.context?.kind === 'runtime' ? input.context.instanceId : undefined;\n const contextTemplateId =\n input.context?.kind === 'runtime' ? input.context.templateId : undefined;\n const rowValuesSignature = JSON.stringify(input.row?.values ?? null);\n const initialRowValuesSignature = JSON.stringify(\n input.row?.initialValues ?? null,\n );\n // A cell's value lives on its row, never on the form data — a top-level field\n // may even share the column's key.\n const currentValue = dynamicField\n ? input.row\n ? input.row.values[dynamicField.fieldKey]\n : input.formData[dynamicField.fieldKey]\n : undefined;\n const currentValueSignature = readFormDataSourceValueSignature(currentValue);\n const selectedValues = useMemo(\n (): readonly string[] => readFormDataSourceSelectedValues(currentValue),\n [currentValueSignature],\n );\n const hasValue = selectedValues.length > 0;\n const dataSourceRefreshSignature = dynamicField\n ? JSON.stringify({\n bindings: dynamicField.dataSource.bindings.map((binding) =>\n binding.from.kind === 'FIELD'\n ? {\n fieldKey: binding.from.fieldKey,\n value: input.formData[binding.from.fieldKey],\n }\n : binding.from.kind === 'CONSTANT'\n ? { value: binding.from.value }\n : {\n columnKey: binding.from.columnKey,\n value: input.row?.values[binding.from.columnKey],\n },\n ),\n value:\n typeof input.initialFormData !== 'undefined' ||\n typeof input.initialValue !== 'undefined'\n ? currentValue\n : undefined,\n })\n : '';\n const effectiveContext = useMemo(\n (): FormRendererDataSourceContext | undefined => {\n if (contextKind === 'preview') {\n return { kind: 'preview' };\n }\n\n if (contextKind === 'runtime' && !input.readonly) {\n return {\n instanceId: contextInstanceId,\n kind: 'runtime',\n templateId: contextTemplateId,\n };\n }\n\n return undefined;\n },\n [\n contextInstanceId,\n contextKind,\n contextTemplateId,\n input.readonly,\n ],\n );\n const valueChanged = dynamicField\n ? (typeof input.initialFormData !== 'undefined' ||\n typeof input.initialValue !== 'undefined') &&\n readFormDataSourceValueSignature(input.initialValue) !==\n currentValueSignature\n : false;\n const bindingsChanged = dynamicField\n ? typeof input.initialFormData !== 'undefined' &&\n dynamicField.dataSource.bindings.some((binding) => {\n if (binding.from.kind === 'FIELD') {\n return (\n readFormDataSourceValueSignature(\n input.initialFormData?.[binding.from.fieldKey],\n ) !==\n readFormDataSourceValueSignature(\n input.formData[binding.from.fieldKey],\n )\n );\n }\n\n if (binding.from.kind === 'ROW_FIELD' && input.row?.initialValues) {\n return (\n readFormDataSourceValueSignature(\n input.row.initialValues[binding.from.columnKey],\n ) !==\n readFormDataSourceValueSignature(\n input.row.values[binding.from.columnKey],\n )\n );\n }\n\n return false;\n })\n : false;\n const isStale = Boolean(\n (snapshot || hasValue) && (valueChanged || bindingsChanged),\n );\n // A stale field with a value is only trustworthy once the host has resolved\n // it: display options may still carry the value through the snapshot merge.\n const needsResolve = Boolean(dynamicField && effectiveContext && isStale && hasValue);\n const quiescentStatus = readQuiescentFormDataSourceStatus({\n hasSnapshot: Boolean(snapshot),\n hasValue,\n isStale,\n readonly: input.readonly,\n });\n // AutoComplete only queries on search, so every other control is the one that\n // loads by itself as soon as it has a context.\n const autoQueries = Boolean(\n dynamicField && effectiveContext && dynamicField.type !== 'autocomplete',\n );\n // Optimistic, and only until the first answer arrives: a binding pointing at\n // an empty field is usually blocking, and showing the control as usable for\n // one round trip would flash an enabled, empty list. AutoComplete is excluded\n // because nothing would ever correct the guess — it cannot be searched while\n // disabled, so an optional binding would lock it for good.\n const waitsForDependencies =\n autoQueries &&\n dynamicField !== null &&\n readMissingFormDataSourceDependencies(\n dynamicField,\n input.formData,\n input.row?.values,\n ).length > 0;\n const pendingQueryStatus = readPendingQueryStatus(isStale, waitsForDependencies);\n const [remoteOptions, setRemoteOptions] = useState<readonly FormFieldOption[]>(\n [],\n );\n const [searchText, setSearchText] = useState('');\n const [nextCursor, setNextCursor] = useState<string | null>(null);\n const [resolution, setResolution] = useState<FormDataSourceResolution | null>(\n null,\n );\n const [status, setStatus] = useState<FormDataSourceFieldStatus>(\n (): FormDataSourceFieldStatus => {\n if (!dynamicField) {\n return 'IDLE';\n }\n\n // The very first paint already reflects the query the effect is about to\n // fire, so the control never appears usable before its options exist.\n return autoQueries ? pendingQueryStatus : quiescentStatus;\n },\n );\n const [error, setError] = useState<string | null>(null);\n const loadedOptionCount = useRef(0);\n const requestSequence = useRef(0);\n const resolveSequence = useRef(0);\n const optionsAbort = useRef<AbortController | null>(null);\n const resolveAbort = useRef<AbortController | null>(null);\n const lastRequest = useRef<{ readonly cursor: string | null; readonly searchText: string }>({\n cursor: null,\n searchText: '',\n });\n\n const queryFormData = useMemo(\n (): Readonly<Record<string, FormFieldValue>> =>\n readDefinedValues(input.formData),\n [formDataSignature],\n );\n const queryRowValues = useMemo(\n (): Readonly<Record<string, FormFieldValue>> | undefined =>\n input.row ? readDefinedValues(input.row.values) : undefined,\n [rowValuesSignature],\n );\n const mergedOptions = useMemo(\n (): readonly FormFieldOption[] =>\n mergeFormDataSourceOptions(snapshotOptions, remoteOptions),\n [remoteOptions, snapshotOptions],\n );\n const currentResolution =\n resolution && resolution.signature === dataSourceRefreshSignature\n ? resolution\n : null;\n const resolvePending = needsResolve && !currentResolution;\n const invalidValues = readInvalidFormDataSourceValues({\n options: mergedOptions,\n resolution: currentResolution,\n resolvePending,\n value: currentValue,\n });\n\n const loadOptions = useCallback(\n (nextSearchTextToLoad: string, cursor: string | null): void => {\n if (!dynamicField || !effectiveContext) {\n return;\n }\n\n const requestId = requestSequence.current + 1;\n requestSequence.current = requestId;\n lastRequest.current = {\n cursor,\n searchText: nextSearchTextToLoad,\n };\n optionsAbort.current?.abort();\n const controller = new AbortController();\n optionsAbort.current = controller;\n setStatus(pendingQueryStatus);\n setError(null);\n\n const request =\n effectiveContext.kind === 'preview'\n ? previewFormFieldOptions({\n cursor,\n fieldKey: queryFieldKey,\n formData: queryFormData,\n rowValues: queryRowValues,\n schema: input.schema,\n searchText: nextSearchTextToLoad,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n })\n : readFormFieldOptions({\n cursor,\n fieldKey: queryFieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\n rowValues: queryRowValues,\n searchText: nextSearchTextToLoad,\n signal: controller.signal,\n templateId: effectiveContext.templateId ?? null,\n });\n\n void request\n .then((result): void => {\n if (isSupersededRequest(controller, requestSequence, requestId)) {\n return;\n }\n\n setRemoteOptions((currentOptions) => {\n const selectedOptions = readSelectedFormDataSourceOptions(\n currentValue,\n currentOptions,\n );\n\n return mergeFormDataSourceOptions(\n selectedOptions,\n cursor ? currentOptions : [],\n result.options,\n );\n });\n setNextCursor(result.nextCursor);\n setError(null);\n loadedOptionCount.current = cursor\n ? loadedOptionCount.current + result.options.length\n : result.options.length;\n\n // An empty page that still hands back a cursor would loop forever.\n if (\n result.nextCursor &&\n result.options.length > 0 &&\n loadedOptionCount.current < MIN_SCROLLABLE_OPTION_COUNT\n ) {\n loadOptionsRef.current(nextSearchTextToLoad, result.nextCursor);\n }\n\n // Only the host knows which bound parameters are required, so a\n // control is disabled on its answer instead of on a browser guess.\n setStatus(\n result.waitingForFieldKeys.length\n ? 'WAITING_FOR_DEPENDENCIES'\n : 'VALID',\n );\n })\n .catch((requestError: unknown): void => {\n if (isSupersededRequest(controller, requestSequence, requestId)) {\n return;\n }\n\n // Anything that is not a DataSource code — a transport failure, or a\n // server-side error the host deliberately reduced to a generic\n // message — must not be rendered verbatim; it is English technical\n // copy the filler cannot act on.\n const message =\n readFormDataSourceErrorMessage(requestError) ??\n '選項來源暫時無法使用。';\n const code = readFormDataSourceErrorCode(requestError);\n\n setError(message);\n setStatus(\n readFailedQueryStatus(\n Boolean(code && RECOVERABLE_ERROR_CODES.includes(code)),\n hasValue,\n ),\n );\n });\n },\n [\n currentValueSignature,\n dynamicFieldSignature,\n effectiveContext,\n hasValue,\n pendingQueryStatus,\n queryFieldKey,\n queryFormData,\n queryRowValues,\n schemaSignature,\n uiSchemaSignature,\n ],\n );\n const loadOptionsRef = useRef(loadOptions);\n loadOptionsRef.current = loadOptions;\n\n const runResolve = useCallback((): void => {\n if (!dynamicField || !effectiveContext || selectedValues.length === 0) {\n return;\n }\n\n const signature = dataSourceRefreshSignature;\n const requestId = resolveSequence.current + 1;\n resolveSequence.current = requestId;\n resolveAbort.current?.abort();\n const controller = new AbortController();\n resolveAbort.current = controller;\n setError(null);\n\n const request =\n effectiveContext.kind === 'preview'\n ? previewResolveFormFieldOptions({\n fieldKey: queryFieldKey,\n formData: queryFormData,\n rowValues: queryRowValues,\n schema: input.schema,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n values: selectedValues,\n })\n : resolveFormFieldOptions({\n fieldKey: queryFieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\n rowValues: queryRowValues,\n signal: controller.signal,\n templateId: effectiveContext.templateId ?? null,\n values: selectedValues,\n });\n\n void request\n .then((result): void => {\n if (isSupersededRequest(controller, resolveSequence, requestId)) {\n return;\n }\n\n if (result.waitingForFieldKeys.length) {\n // The host could not consult the provider, so this is no answer about\n // the value: leave the field waiting rather than call it resolved.\n setStatus('WAITING_FOR_DEPENDENCIES');\n\n return;\n }\n\n // Authoritative labels for the values that survived, merged so the old\n // snapshot label of an unresolved value stays visible for recognition.\n setRemoteOptions((currentOptions) =>\n mergeFormDataSourceOptions(currentOptions, result.options),\n );\n setResolution({\n signature,\n unresolvedValues: result.unresolvedValues,\n });\n // `invalidValues` turns this into `INVALID` when anything went missing.\n setStatus('VALID');\n })\n .catch((requestError: unknown): void => {\n if (isSupersededRequest(controller, resolveSequence, requestId)) {\n return;\n }\n\n setStatus('UNAVAILABLE');\n setError(\n readFormDataSourceErrorMessage(requestError) ??\n '選項來源暫時無法使用。',\n );\n });\n }, [\n dataSourceRefreshSignature,\n dynamicFieldSignature,\n effectiveContext,\n queryFieldKey,\n queryFormData,\n queryRowValues,\n schemaSignature,\n selectedValues,\n uiSchemaSignature,\n ]);\n const runResolveRef = useRef(runResolve);\n runResolveRef.current = runResolve;\n\n useEffect((): void => {\n requestSequence.current += 1;\n optionsAbort.current?.abort();\n setRemoteOptions((currentOptions) =>\n readSelectedFormDataSourceOptions(currentValue, currentOptions),\n );\n setNextCursor((currentCursor) =>\n currentCursor === null ? currentCursor : null,\n );\n setSearchText((currentSearchText) =>\n currentSearchText ? '' : currentSearchText,\n );\n setError((currentError) => (currentError ? null : currentError));\n\n if (!dynamicField) {\n setStatus('IDLE');\n\n return;\n }\n\n if (!autoQueries) {\n // AutoComplete queries on search only, so it stays quiescent until the\n // filler types; a context-less field has nothing to query at all.\n setStatus(quiescentStatus);\n\n return;\n }\n\n loadOptionsRef.current('', null);\n }, [\n autoQueries,\n dynamicFieldSignature,\n effectiveContext,\n dataSourceRefreshSignature,\n initialFormDataSignature,\n initialRowValuesSignature,\n input.readonly,\n pendingQueryStatus,\n quiescentStatus,\n snapshotSignature,\n ]);\n\n useEffect((): void => {\n if (!needsResolve) {\n return;\n }\n\n runResolveRef.current();\n }, [dataSourceRefreshSignature, effectiveContext, needsResolve]);\n\n useEffect((): (() => void) => {\n return (): void => {\n optionsAbort.current?.abort();\n resolveAbort.current?.abort();\n };\n }, []);\n\n const onSearch = useCallback(\n (nextSearchTextToLoad: string): void => {\n setSearchText(nextSearchTextToLoad);\n setRemoteOptions((currentOptions) =>\n readSelectedFormDataSourceOptions(currentValue, currentOptions),\n );\n setNextCursor(null);\n loadOptions(nextSearchTextToLoad, null);\n },\n [loadOptions],\n );\n const onReachBottom = useCallback((): void => {\n if (nextCursor) {\n loadOptions(searchText, nextCursor);\n }\n }, [loadOptions, nextCursor, searchText]);\n const retry = useCallback((): void => {\n if (!effectiveContext) {\n return;\n }\n\n loadOptions(lastRequest.current.searchText, lastRequest.current.cursor);\n\n if (needsResolve) {\n runResolveRef.current();\n }\n }, [effectiveContext, loadOptions, needsResolve]);\n\n if (!dynamicField) {\n return EMPTY_FIELD_STATE;\n }\n\n return {\n canRetry: Boolean(effectiveContext),\n error,\n hasValue,\n hasNextPage: Boolean(nextCursor),\n invalidValues,\n onReachBottom,\n onSearch,\n options: mergedOptions,\n retry,\n status: readFormDataSourceExposedStatus({\n invalidValues,\n resolvePending,\n status,\n }),\n };\n}\n\nexport function readFormDataSourceFieldStatusMessage(\n state: FormDataSourceFieldState,\n): string | null {\n switch (state.status) {\n case 'WAITING_FOR_DEPENDENCIES':\n return '請先填寫相依欄位。';\n case 'STALE':\n return '選項需要重新載入或驗證。';\n case 'INVALID':\n return state.invalidValues.length\n ? `無法辨識選項:${state.invalidValues.join('、')}`\n : '目前選項已失效,請重新選擇。';\n case 'UNAVAILABLE':\n return state.error ?? '選項來源暫時無法使用。';\n case 'VALID':\n // A recoverable search hint outranks the empty-result copy: it is what\n // tells the filler how to get results at all.\n return state.error ?? (state.options.length ? null : '目前沒有可用選項。');\n default:\n return state.error;\n }\n}\n\nexport function isFormDataSourceFieldSubmissionBlocked(\n state: Pick<FormDataSourceFieldState, 'hasValue' | 'status'>,\n): boolean {\n return state.hasValue && state.status !== 'VALID';\n}\n\n/**\n * The copy to show when a submission is refused because of dynamic options.\n * `LOADING` and `STALE` clear on their own once the pending query answers, so\n * saying \"finish validating\" reads as a dead end for what is really a \"wait a\n * moment\"; the caller would otherwise show the same permanent-sounding line for\n * both cases.\n *\n * Returns `null` when nothing blocks the submission.\n */\nexport function readFormDataSourceSubmissionBlockMessage(\n states: readonly Pick<FormDataSourceFieldState, 'hasValue' | 'status'>[],\n): string | null {\n const blocked = states.filter(isFormDataSourceFieldSubmissionBlocked);\n\n if (blocked.length === 0) {\n return null;\n }\n\n return blocked.every(\n (state) => state.status === 'LOADING' || state.status === 'STALE',\n )\n ? '選項驗證中,請稍候再送出。'\n : '請先完成動態選項驗證。';\n}\n\n/**\n * The values the field holds that no option accounts for.\n *\n * A completed resolve is the authority — the loaded pages are not, because the\n * option snapshot is merged into them for display and would vouch for a value\n * the source has already dropped.\n */\nfunction readInvalidFormDataSourceValues(input: {\n readonly options: readonly FormFieldOption[];\n readonly resolution: FormDataSourceResolution | null;\n readonly resolvePending: boolean;\n readonly value: FormFieldValue | undefined;\n}): readonly string[] {\n if (input.resolution) {\n return input.resolution.unresolvedValues;\n }\n\n // While the host is being asked, its answer is the only one worth showing.\n if (input.resolvePending) {\n return NO_INVALID_VALUES;\n }\n\n return readMissingFormDataSourceOptionValues(input.value, input.options);\n}\n\n/**\n * Status of a field nobody is querying — read-only history, a Designer preview\n * before the first search, or a control the host gave no context for.\n */\nfunction readQuiescentFormDataSourceStatus(input: {\n readonly hasSnapshot: boolean;\n readonly hasValue: boolean;\n readonly isStale: boolean;\n readonly readonly: boolean;\n}): FormDataSourceFieldStatus {\n if (input.isStale) {\n return 'STALE';\n }\n\n if (input.hasSnapshot) {\n return 'VALID';\n }\n\n // A read-only value with no snapshot cannot be labelled from anywhere, which\n // is worth saying. An empty read-only field is simply blank — warning about a\n // source nobody is going to query would be noise.\n return input.readonly && input.hasValue ? 'UNAVAILABLE' : 'IDLE';\n}\n\n/**\n * Status held while a query is in flight.\n *\n * `waiting` is the browser's optimistic guess and only survives until the host\n * answers: `waitingForFieldKeys` from the response replaces it either way, so an\n * optional binding left empty clears within one round trip instead of locking\n * the control.\n */\nfunction readPendingQueryStatus(\n isStale: boolean,\n waiting: boolean,\n): FormDataSourceFieldStatus {\n if (waiting) {\n return 'WAITING_FOR_DEPENDENCIES';\n }\n\n return isStale ? 'STALE' : 'LOADING';\n}\n\n/**\n * Status after a rejected query. A recoverable failure leaves the field as\n * usable as it was — only a broken source makes it unavailable.\n */\nfunction readFailedQueryStatus(\n recoverable: boolean,\n hasValue: boolean,\n): FormDataSourceFieldStatus {\n if (!recoverable) {\n return 'UNAVAILABLE';\n }\n\n return hasValue ? 'VALID' : 'IDLE';\n}\n\nfunction readFormDataSourceExposedStatus(input: {\n readonly invalidValues: readonly string[];\n readonly resolvePending: boolean;\n readonly status: FormDataSourceFieldStatus;\n}): FormDataSourceFieldStatus {\n if (\n input.status === 'LOADING' ||\n input.status === 'UNAVAILABLE' ||\n input.status === 'WAITING_FOR_DEPENDENCIES'\n ) {\n return input.status;\n }\n\n if (input.invalidValues.length) {\n return 'INVALID';\n }\n\n return input.resolvePending ? 'STALE' : input.status;\n}\n\n/**\n * True when a response no longer belongs to the field: its request was aborted\n * by a newer one, or a newer one started before it settled. Such a response\n * must not touch state — an aborted request is not a failure to report.\n */\nfunction isSupersededRequest(\n controller: AbortController,\n sequence: { readonly current: number },\n requestId: number,\n): boolean {\n return controller.signal.aborted || sequence.current !== requestId;\n}\n\nfunction readDefinedValues(\n values: FormRendererValues,\n): Readonly<Record<string, FormFieldValue>> {\n return Object.entries(values).reduce<\n Readonly<Record<string, FormFieldValue>>\n >((defined, [key, value]) => {\n if (typeof value === 'undefined') {\n return defined;\n }\n\n return { ...defined, [key]: value };\n }, {});\n}\n","export interface FormFieldLink {\n /** Absolute links open in a new tab; site-relative ones stay in this tab. */\n readonly external: boolean;\n readonly href: string;\n}\n\nconst UNICODE_WHITESPACE_PATTERN = /\\s/u;\n\n// Whitespace and control characters are refused anywhere in the value: URL\n// parsers silently drop tabs and newlines, so \"/\\t/evil.example\" would turn\n// into the protocol-relative \"//evil.example\" after our `//` check passed.\nfunction hasUnsafeCharacter(value: string): boolean {\n return [...value].some((character): boolean => {\n const codePoint = character.codePointAt(0) ?? 0;\n\n return (\n // C0 controls and the space, DEL, C1 controls and the no-break space.\n codePoint <= 0x20 ||\n (codePoint >= 0x7f && codePoint <= 0xa0) ||\n UNICODE_WHITESPACE_PATTERN.test(character)\n );\n });\n}\n\nconst ABSOLUTE_HTTP_PATTERN = /^https?:\\/\\//iu;\n\n/**\n * Decides whether a `displayAs: 'link'` text value may become an anchor.\n * Returns `null` for everything that must stay plain text.\n *\n * Allowed: a site-relative path (`/` first, never `//`) or an absolute\n * `http(s)://` URL with a host and no embedded credentials. Everything else —\n * `javascript:`, `data:`, protocol-relative `//host`, backslash tricks\n * (`/\\host` is `//host` to a browser), padded or multi-line values, non-strings\n * — is refused rather than repaired.\n */\nexport function resolveFormFieldLink(value: unknown): FormFieldLink | null {\n if (\n typeof value !== 'string' ||\n !value ||\n value.includes('\\\\') ||\n hasUnsafeCharacter(value)\n ) {\n return null;\n }\n\n if (value.startsWith('/')) {\n return !value.startsWith('//') && isSafeSiteRelativePath(value)\n ? { external: false, href: value }\n : null;\n }\n\n return ABSOLUTE_HTTP_PATTERN.test(value) && isSafeAbsoluteHttpUrl(value)\n ? { external: true, href: value }\n : null;\n}\n\nconst PLACEHOLDER_BASE = 'http://placeholder.invalid';\n\n/**\n * Dot segments collapse before navigation: `/.//evil.example` resolves to the\n * path `//evil.example`, which a naive `startsWith('//')` check misses. Resolve\n * the value against a throw-away origin and require that it stays on that\n * origin and does not come out as a protocol-relative path.\n */\nfunction isSafeSiteRelativePath(value: string): boolean {\n try {\n const resolved = new URL(value, PLACEHOLDER_BASE);\n\n return (\n resolved.origin === PLACEHOLDER_BASE && !resolved.pathname.startsWith('//')\n );\n } catch {\n return false;\n }\n}\n\nfunction isSafeAbsoluteHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n\n return (\n (url.protocol === 'http:' || url.protocol === 'https:') &&\n Boolean(url.hostname) &&\n !url.username &&\n !url.password\n );\n } catch {\n return false;\n }\n}\n","import type { MemberProfileRecord } from '@rytass/bpm-core-client/template';\n\n/** Most ids asked for on their own after a failed batch, per round. */\nexport const MAX_PER_ID_REQUESTS = 20;\n\n/** Most per-id requests in flight at once. */\nexport const PER_ID_CONCURRENCY = 4;\n\nexport type MemberProfileResolution = Readonly<{\n /** Ids whose lookup failed even when asked for on their own. */\n failedIds: readonly string[];\n profiles: readonly MemberProfileRecord[];\n /**\n * Ids left unasked because a failed batch held more than the per-id budget.\n * They were not tried, so a later round may ask for them again.\n */\n skippedIds: readonly string[];\n}>;\n\nexport type MemberProfileResolutionLimits = Readonly<{\n concurrency?: number;\n maxPerIdRequests?: number;\n}>;\n\n/**\n * Looks up member profiles in one batch, and degrades per id when the batch\n * request fails.\n *\n * The host fails a whole lookup when it contains an id it cannot handle (a\n * foreign id stored in a template, say). Treating that as \"nobody resolved\"\n * labels every approver of the template 「未知會員」 for the sake of one bad\n * id, so a failed batch is retried one id at a time and only the ids that fail\n * on their own are reported back in `failedIds`.\n *\n * The retry is bounded: at most `maxPerIdRequests` ids are asked for on their\n * own (the rest come back in `skippedIds` without a request), and at most\n * `concurrency` of those requests run at once, so one bad id in a large\n * template cannot turn into a burst of requests.\n *\n * A single-id batch is not retried: it already is the per-id request.\n */\nexport async function resolveMemberProfilesPerIdOnFailure(\n memberIds: readonly string[],\n resolveMembers: (\n memberIds: readonly string[],\n ) => Promise<readonly MemberProfileRecord[]>,\n limits: MemberProfileResolutionLimits = {},\n): Promise<MemberProfileResolution> {\n const maxPerIdRequests = limits.maxPerIdRequests ?? MAX_PER_ID_REQUESTS;\n const concurrency = Math.max(1, limits.concurrency ?? PER_ID_CONCURRENCY);\n\n try {\n return {\n failedIds: [],\n profiles: await resolveMembers(memberIds),\n skippedIds: [],\n };\n } catch {\n if (memberIds.length <= 1) {\n return { failedIds: memberIds, profiles: [], skippedIds: [] };\n }\n }\n\n const askedIds = memberIds.slice(0, maxPerIdRequests);\n const settled = await settleWithConcurrency(\n askedIds,\n concurrency,\n (memberId) => resolveMembers([memberId]),\n );\n\n return {\n failedIds: askedIds.filter(\n (_, index) => settled[index].status === 'rejected',\n ),\n profiles: settled.flatMap((result) =>\n result.status === 'fulfilled' ? result.value : [],\n ),\n skippedIds: memberIds.slice(maxPerIdRequests),\n };\n}\n\n/**\n * The ids still worth asking the directory for: those that already failed on\n * their own in this session are left out, so they stay 「未知會員」 instead of\n * being re-requested every time the option list changes.\n *\n * A failed id stays 「未知會員」 for the rest of the session, even when the\n * failure was transient (a network blip, say), unless a later member search\n * returns it and replaces the placeholder. Ids skipped over the per-id cap\n * were never asked and must not be added to `failedMemberIds`.\n */\nexport function excludeFailedMemberIds(\n memberIds: readonly string[],\n failedMemberIds: ReadonlySet<string>,\n): readonly string[] {\n return memberIds.filter((memberId) => !failedMemberIds.has(memberId));\n}\n\nasync function settleWithConcurrency<TItem, TResult>(\n items: readonly TItem[],\n concurrency: number,\n run: (item: TItem) => Promise<TResult>,\n): Promise<readonly PromiseSettledResult<TResult>[]> {\n const results: PromiseSettledResult<TResult>[] = [];\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < items.length) {\n const index = nextIndex;\n\n nextIndex += 1;\n results[index] = await run(items[index]).then(\n (value): PromiseSettledResult<TResult> => ({\n status: 'fulfilled',\n value,\n }),\n (reason: unknown): PromiseSettledResult<TResult> => ({\n reason,\n status: 'rejected',\n }),\n );\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, items.length) }, worker),\n );\n\n return results;\n}\n","import type { FormFieldValue } from '@rytass/bpm-core-shared/form';\nimport { readFormMemberIds } from '@rytass/bpm-core-shared/form';\n\n/**\n * One pickable member, shaped for Mezzanine `AutoComplete` (`id` + `name`).\n * `unresolved` marks the placeholder written when a stored id has no profile,\n * so a real profile arriving later replaces it instead of the placeholder\n * sticking (the designer does the same for its approver list).\n */\nexport type MemberFieldOption = Readonly<{\n id: string;\n name: string;\n unresolved?: boolean;\n}>;\n\nexport const UNKNOWN_MEMBER_NAME = '未知會員';\n\nexport function readMemberFieldOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberFieldOption {\n return {\n id: member.memberId,\n name: member.email ? `${member.name} (${member.email})` : member.name,\n };\n}\n\nexport function readUnresolvedMemberFieldOption(\n memberId: string,\n): MemberFieldOption {\n return { id: memberId, name: UNKNOWN_MEMBER_NAME, unresolved: true };\n}\n\n/**\n * Adds `nextOptions` to `currentOptions`. A resolved option replaces an\n * unresolved placeholder with the same id; otherwise the first one seen wins.\n * Returns `currentOptions` itself when nothing changed, so state setters can\n * bail out of a re-render.\n */\nexport function mergeMemberFieldOptions(\n currentOptions: readonly MemberFieldOption[],\n nextOptions: readonly MemberFieldOption[],\n): readonly MemberFieldOption[] {\n const resolvedById = new Map(\n nextOptions\n .filter((option) => !option.unresolved)\n .map((option) => [option.id, option] as const),\n );\n const currentIds = new Set(currentOptions.map((option) => option.id));\n const addedOptions = nextOptions.filter(\n (option, index) =>\n !currentIds.has(option.id) &&\n nextOptions.findIndex((candidate) => candidate.id === option.id) ===\n index,\n );\n const upgradedOptions = currentOptions.map(\n (option) =>\n (option.unresolved ? resolvedById.get(option.id) : undefined) ?? option,\n );\n const changed =\n addedOptions.length > 0 ||\n upgradedOptions.some((option, index) => option !== currentOptions[index]);\n\n return changed ? [...upgradedOptions, ...addedOptions] : currentOptions;\n}\n\n/** Ids in the stored value that no known option covers yet. */\nexport function readUnresolvedMemberIds(\n value: FormFieldValue | undefined,\n knownOptions: readonly MemberFieldOption[],\n): readonly string[] {\n const knownIds = new Set(\n knownOptions\n .filter((option) => !option.unresolved)\n .map((option) => option.id),\n );\n\n return readFormMemberIds(value).filter((memberId) => !knownIds.has(memberId));\n}\n\n/**\n * The stored value as options, in stored order. An id with no known profile\n * still shows up (as 「未知會員」) so the filler can see and remove it.\n */\nexport function readSelectedMemberFieldOptions(\n value: FormFieldValue | undefined,\n knownOptions: readonly MemberFieldOption[],\n): readonly MemberFieldOption[] {\n return readFormMemberIds(value).map(\n (memberId) =>\n knownOptions.find((option) => option.id === memberId) ??\n readUnresolvedMemberFieldOption(memberId),\n );\n}\n\n/**\n * What a picker change writes back. Nothing picked is `undefined`, the same as\n * every other optional field, so the renderer's own initial value (`[]` for a\n * multiple member field) is restored by `buildFormRendererValues`.\n */\nexport function readMemberFieldValue(\n selected: readonly MemberFieldOption[],\n multiple: boolean,\n): FormFieldValue | undefined {\n if (selected.length === 0) {\n return undefined;\n }\n\n return multiple ? selected.map((option) => option.id) : selected[0]?.id;\n}\n","'use client';\n\nimport { ReactElement, useEffect, useRef, useState } from 'react';\nimport { AutoComplete } from '@mezzanine-ui/react';\nimport { resolveMembers, searchMembers } from '@rytass/bpm-core-client';\nimport type { FormFieldValue } from '@rytass/bpm-core-shared/form';\nimport {\n excludeFailedMemberIds,\n resolveMemberProfilesPerIdOnFailure,\n} from '../../../lib/member-profile-resolution';\nimport {\n mergeMemberFieldOptions,\n readMemberFieldOption,\n readMemberFieldValue,\n readSelectedMemberFieldOptions,\n readUnresolvedMemberFieldOption,\n readUnresolvedMemberIds,\n type MemberFieldOption,\n} from './member-field-options';\n\nexport interface MemberFieldControlProps {\n readonly multiple: boolean;\n readonly name: string;\n readonly onChange: (value: FormFieldValue | undefined) => void;\n readonly placeholder?: string;\n readonly readonly: boolean;\n readonly size?: 'main' | 'sub';\n readonly value: FormFieldValue | undefined;\n}\n\n/**\n * 人員選擇 picker. Searching and name lookup reuse the directory queries the\n * admin member picker and the workflow designer already call\n * (`searchMembers` / `resolveMembers`); nothing new is added server side.\n *\n * Stored values are member ids. The names shown here are resolved on mount and\n * whenever a stored id has no profile yet, so a read-only view of a submitted\n * form (instance detail) shows names, not ids.\n */\nexport function MemberFieldControl({\n multiple,\n name,\n onChange,\n placeholder,\n readonly,\n size,\n value,\n}: MemberFieldControlProps): ReactElement {\n const [knownOptions, setKnownOptions] = useState<\n readonly MemberFieldOption[]\n >([]);\n const [searchResults, setSearchResults] = useState<\n readonly MemberFieldOption[]\n >([]);\n const [loading, setLoading] = useState(false);\n const latestSearchRef = useRef(0);\n // Ids the directory could not name; they stay 「未知會員」 and are not asked\n // for again when the other ids of the value resolve and the key changes.\n // That holds for the rest of the session, a transient failure included,\n // unless a later search returns the member (the merge then replaces the\n // placeholder). Ids skipped over the per-id cap were never asked, so they are\n // not given up on.\n const givenUpIdsRef = useRef<Set<string>>(new Set());\n const unresolvedKey = readUnresolvedMemberIds(value, knownOptions).join('\\n');\n\n useEffect((): (() => void) => {\n const memberIds = excludeFailedMemberIds(\n unresolvedKey ? unresolvedKey.split('\\n') : [],\n givenUpIdsRef.current,\n );\n let cancelled = false;\n\n if (memberIds.length === 0) {\n return (): void => undefined;\n }\n\n // One id the directory cannot handle fails the whole batch, so a failed\n // batch is retried per id: the other members keep their names.\n void resolveMemberProfilesPerIdOnFailure(memberIds, resolveMembers).then(\n ({ profiles, skippedIds }): void => {\n if (cancelled) {\n return;\n }\n\n const resolved = profiles.map(readMemberFieldOption);\n // Ids the directory did not return stay visible as placeholders.\n const placeholders = memberIds\n .filter(\n (memberId) => !resolved.some((option) => option.id === memberId),\n )\n .map(readUnresolvedMemberFieldOption);\n\n // Only the ids that were asked for and still came back without a\n // profile are given up on, so a failed lookup is not retried in a loop.\n // Ids skipped over the per-id cap were never asked: they keep a\n // placeholder but stay eligible for the next round, as the designer\n // does.\n placeholders\n .filter((placeholder) => !skippedIds.includes(placeholder.id))\n .forEach((placeholder) => givenUpIdsRef.current.add(placeholder.id));\n\n setKnownOptions((current) =>\n mergeMemberFieldOptions(current, [...resolved, ...placeholders]),\n );\n },\n );\n\n return (): void => {\n cancelled = true;\n };\n }, [unresolvedKey]);\n\n async function handleSearch(searchText: string): Promise<void> {\n const requestId = latestSearchRef.current + 1;\n\n latestSearchRef.current = requestId;\n setLoading(true);\n\n try {\n const options = (await searchMembers(searchText)).map(\n readMemberFieldOption,\n );\n\n if (latestSearchRef.current === requestId) {\n setSearchResults(options);\n setKnownOptions((current) => mergeMemberFieldOptions(current, options));\n }\n } catch {\n if (latestSearchRef.current === requestId) {\n setSearchResults([]);\n }\n } finally {\n if (latestSearchRef.current === requestId) {\n setLoading(false);\n }\n }\n }\n\n const selected = readSelectedMemberFieldOptions(value, knownOptions);\n // Selected members stay in the option list so a picked member can be\n // un-ticked from the dropdown, not only through its tag.\n const options = [...mergeMemberFieldOptions(selected, searchResults)];\n const commonProps = {\n asyncData: true,\n clearSearchText: true,\n disabled: readonly,\n disabledOptionsFilter: true,\n emptyText: '沒有符合的成員',\n globalPortal: true,\n inputProps: {\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n },\n loading,\n loadingText: '搜尋成員中...',\n onSearch: handleSearch,\n onVisibilityChange: (open: boolean): void => {\n if (open) {\n setSearchResults([]);\n void handleSearch('');\n }\n },\n options,\n placeholder: placeholder ?? '搜尋姓名或信箱',\n readOnly: readonly,\n searchDebounceTime: 300,\n size,\n } as const;\n\n return multiple ? (\n <AutoComplete\n {...commonProps}\n mode=\"multiple\"\n onChange={(nextOptions): void =>\n onChange(readMemberFieldValue(nextOptions, true))\n }\n value={[...selected]}\n />\n ) : (\n <AutoComplete\n {...commonProps}\n mode=\"single\"\n onChange={(nextOption): void =>\n onChange(readMemberFieldValue(nextOption ? [nextOption] : [], false))\n }\n value={selected[0] ?? null}\n />\n );\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n AutoComplete,\n Button,\n CheckboxGroup,\n DatePicker,\n DateTimePicker,\n Input,\n RadioGroup,\n Select,\n Table,\n Textarea,\n Toggle,\n Typography,\n Upload,\n} from '@mezzanine-ui/react';\nimport type { UploadFile } from '@mezzanine-ui/react/Upload';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport { PlusIcon, TrashIcon } from '@mezzanine-ui/icons';\nimport {\n FormDataSourceOptionFieldDefinition,\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldValue,\n FormTableRowValue,\n FormUiSchema,\n TableColumnDefinition,\n TableFieldDefinition,\n isFormDataSourceFieldDefinition,\n isFormStaticOptionFieldDefinition,\n isFormTableCellValue,\n isTableFieldDefinition,\n readFormFieldSelectionMode,\n} from '@rytass/bpm-core-shared/form';\nimport {\n buildFormRendererValues,\n clampOptionalNumber,\n createFormTableRow,\n formatDatePickerValue,\n formatDateTimePickerValue,\n FormRendererValues,\n isFormRendererFieldReadonly,\n isFormRendererFieldRequired,\n parseOptionalNumberInput,\n readDatePickerValue,\n readFieldOptionAsSelectOption,\n readFormTableCellPath,\n readFormTableRowBounds,\n readFormTableRows,\n readSelectedFormDataSourceOptions,\n readSelectOption,\n readVisibleFormRendererFields,\n} from '@rytass/bpm-core-client/form';\nimport { BPMFormField } from '../../../components/bpm-form-field';\nimport {\n FormDataSourceFieldState,\n FormRendererDataSourceContext,\n readFormDataSourceFieldStatusMessage,\n useFormDataSourceField,\n} from './form-data-source-field';\nimport { resolveFormFieldLink } from './form-field-link';\nimport { MemberFieldControl } from './member-field';\n\nexport interface FormRendererProps {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n readonly emptyText?: string;\n readonly errors?: Readonly<Record<string, string>>;\n readonly maxWidth?: CSSProperties['maxWidth'];\n readonly onChange?: (values: FormRendererValues) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'status'\n >,\n ) => void;\n readonly onUploadAttachment?: (\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly?: boolean;\n readonly schema: FormDefinitionSchema;\n readonly singleColumn?: boolean;\n readonly uiSchema: FormUiSchema;\n readonly value?: FormRendererValues;\n}\n\n/**\n * Controls inside a table cell take the table's own `sub` size, so a row is\n * sized by its cells rather than by whichever control kept the default.\n * `DatePicker` and `DateTimePicker` accept no size, so a date column stays at\n * the default height — a Mezzanine limitation, not a choice.\n */\ntype ControlSize = 'main' | 'sub';\n\nconst FORM_RENDERER_GRID_STYLE: CSSProperties = {\n alignItems: 'start',\n display: 'grid',\n gap: 12,\n gridTemplateColumns: 'repeat(12, minmax(0, 1fr))',\n};\n\nconst FORM_RENDERER_EMPTY_STYLE: CSSProperties = {\n gridColumn: '1 / -1',\n};\n\nconst FORM_RENDERER_FIELD_STYLE: CSSProperties = {\n alignItems: 'start',\n display: 'grid',\n gap: 8,\n minWidth: 0,\n};\n\nconst TEXTAREA_STYLE: CSSProperties = {\n minWidth: '100%',\n width: '100%',\n};\n\nfunction applyFullWidthTextareaHost(element: HTMLDivElement | null): void {\n if (!element) {\n return;\n }\n\n element.style.width = '100%';\n}\n\nexport function FormRenderer({\n dataSourceContext,\n dataSourceInitialValues,\n emptyText = '尚未建立欄位。',\n errors = {},\n maxWidth,\n onChange,\n onDataSourceStateChange,\n onUploadAttachment,\n optionSnapshots,\n readonly = false,\n schema,\n singleColumn = false,\n uiSchema,\n value,\n}: FormRendererProps): ReactElement {\n const [internalValues, setInternalValues] = useState<FormRendererValues>(() =>\n buildFormRendererValues(schema.fields, value ?? {}),\n );\n const values = useMemo(\n (): FormRendererValues =>\n buildFormRendererValues(schema.fields, value ?? internalValues),\n [internalValues, schema.fields, value],\n );\n const valuesRef = useRef<FormRendererValues>(values);\n const visibleFields = useMemo(\n (): readonly FormFieldDefinition[] =>\n readVisibleFormRendererFields(schema, uiSchema, values),\n [schema, uiSchema, values],\n );\n\n useEffect((): void => {\n valuesRef.current = values;\n }, [values]);\n\n useEffect((): void => {\n setInternalValues((currentValues) =>\n buildFormRendererValues(schema.fields, value ?? currentValues),\n );\n }, [schema.fields, value]);\n\n function updateValue(\n fieldKey: string,\n nextValue: FormFieldValue | undefined,\n ): void {\n const nextValues = {\n ...valuesRef.current,\n [fieldKey]: nextValue,\n };\n valuesRef.current = nextValues;\n\n if (!value) {\n setInternalValues(nextValues);\n }\n\n onChange?.(nextValues);\n }\n\n if (!visibleFields.length) {\n return (\n <div style={FORM_RENDERER_GRID_STYLE}>\n <Typography\n color=\"text-neutral\"\n style={FORM_RENDERER_EMPTY_STYLE}\n variant=\"body\"\n >\n {schema.fields.length > 0 ? '目前條件下沒有可填寫欄位。' : emptyText}\n </Typography>\n </div>\n );\n }\n\n return (\n <div\n style={{\n ...FORM_RENDERER_GRID_STYLE,\n // `maxWidth` is applied per field rather than to the grid, so a table\n // field can use the full content width while every other field keeps\n // the single-column reading width (docs/17 P4).\n ...(singleColumn ? { width: '100%' } : {}),\n }}\n >\n {visibleFields.map((field) => (\n <FormRendererField\n dataSourceContext={dataSourceContext}\n dataSourceInitialValues={dataSourceInitialValues}\n field={field}\n error={errors[field.fieldKey] ?? null}\n errors={errors}\n fields={schema.fields}\n key={field.fieldKey}\n onChange={updateValue}\n onDataSourceStateChange={onDataSourceStateChange}\n onUploadAttachment={onUploadAttachment}\n optionSnapshots={optionSnapshots}\n readonly={readonly}\n schema={schema}\n style={{\n ...FORM_RENDERER_FIELD_STYLE,\n gridColumn: `span ${\n singleColumn ? 12 : readFieldColumnSpan(field, uiSchema)\n }`,\n ...(singleColumn && !isTableFieldDefinition(field)\n ? { maxWidth }\n : {}),\n }}\n value={values[field.fieldKey]}\n values={values}\n uiSchema={uiSchema}\n />\n ))}\n </div>\n );\n}\n\nfunction FormRendererField({\n dataSourceContext,\n dataSourceInitialValues,\n error,\n errors,\n field,\n fields,\n onChange,\n onDataSourceStateChange,\n onUploadAttachment,\n optionSnapshots,\n readonly,\n schema,\n style,\n value,\n values,\n uiSchema,\n}: {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n readonly error: string | null;\n /**\n * The whole error map, not just this field's line: a table addresses its\n * cells by instance path, so it needs the keys of its own rows too\n * (ADR 16 §3.3).\n */\n readonly errors: Readonly<Record<string, string>>;\n readonly field: FormFieldDefinition;\n readonly fields: readonly FormFieldDefinition[];\n readonly onChange: (\n fieldKey: string,\n value: FormFieldValue | undefined,\n ) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'status'\n >,\n ) => void;\n readonly onUploadAttachment?:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n readonly schema: FormDefinitionSchema;\n readonly style: CSSProperties;\n readonly uiSchema: FormUiSchema;\n readonly value: FormFieldValue | undefined;\n readonly values: FormRendererValues;\n}): ReactElement {\n const required = isFormRendererFieldRequired(field, fields, values);\n const fieldReadonly =\n readonly || isFormRendererFieldReadonly(field, fields, values);\n const dataSourceState = useFormDataSourceField({\n context: dataSourceContext,\n field,\n formData: values,\n initialFormData: dataSourceInitialValues,\n initialValue: dataSourceInitialValues?.[field.fieldKey],\n optionSnapshots,\n readonly: fieldReadonly,\n schema,\n uiSchema,\n });\n const dataSourceStatusMessage = readFormDataSourceFieldStatusMessage(\n dataSourceState,\n );\n const dataSourceStateSignature = JSON.stringify({\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n status: dataSourceState.status,\n });\n\n useEffect((): void => {\n if (!isFormDataSourceFieldDefinition(field)) {\n return;\n }\n\n onDataSourceStateChange?.(field.fieldKey, {\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n status: dataSourceState.status,\n });\n }, [\n dataSourceStateSignature,\n field.fieldKey,\n onDataSourceStateChange,\n ]);\n\n return (\n <div data-form-field-key={field.fieldKey} style={style}>\n <BPMFormField\n label={field.label}\n name={field.fieldKey}\n required={required}\n >\n {isTableFieldDefinition(field) ? (\n <FormTableField\n dataSourceContext={dataSourceContext}\n dataSourceInitialValues={dataSourceInitialValues}\n errors={errors}\n field={field}\n onChange={onChange}\n onDataSourceStateChange={onDataSourceStateChange}\n optionSnapshots={optionSnapshots}\n readonly={fieldReadonly}\n schema={schema}\n uiSchema={uiSchema}\n value={value}\n values={values}\n />\n ) : (\n renderControl(\n field,\n value,\n fieldReadonly,\n onChange,\n onUploadAttachment,\n dataSourceContext,\n dataSourceState,\n )\n )}\n </BPMFormField>\n {error ? (\n <Typography color=\"text-error\" variant=\"caption\">\n {error}\n </Typography>\n ) : null}\n {dataSourceStatusMessage ? (\n <div style={DATA_SOURCE_FEEDBACK_STYLE}>\n <Typography\n color={\n dataSourceState.status === 'INVALID' ||\n dataSourceState.status === 'UNAVAILABLE'\n ? 'text-error'\n : 'text-neutral'\n }\n variant=\"caption\"\n >\n {dataSourceStatusMessage}\n </Typography>\n {dataSourceState.status === 'UNAVAILABLE' && dataSourceState.canRetry ? (\n <Button\n onClick={dataSourceState.retry}\n size=\"sub\"\n type=\"button\"\n variant=\"base-ghost\"\n >\n 重試\n </Button>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\nconst EXTERNAL_LINK_CUE = '(另開新分頁)';\nconst EXTERNAL_LINK_TITLE = '另開新分頁';\n\nconst LINK_STYLE: CSSProperties = {\n color: 'inherit',\n textDecoration: 'underline',\n wordBreak: 'break-all',\n};\n\nconst DATA_SOURCE_FEEDBACK_STYLE: CSSProperties = {\n alignItems: 'center',\n display: 'flex',\n gap: 8,\n};\n\nconst TABLE_FIELD_STYLE: CSSProperties = {\n display: 'grid',\n gap: 8,\n minWidth: 0,\n width: '100%',\n};\n\nconst TABLE_FIELD_ACTIONS_STYLE: CSSProperties = {\n display: 'flex',\n justifyContent: 'flex-start',\n};\n\nconst TABLE_SCROLLER_STYLE: CSSProperties = {\n maxWidth: '100%',\n overflowX: 'auto',\n};\n\n// Mezzanine `Table` fills whatever box it is given, so an `overflow-x: auto`\n// wrapper on its own never scrolls: the columns just keep shrinking. Keeping\n// the table out of the single-column reading width is what stops a control\n// from overflowing its own cell; this floor is the defence for tables wide\n// enough that even the full width would squeeze them, and it is what makes the\n// wrapper actually scroll when that happens.\nconst TABLE_COLUMN_MIN_WIDTH = 160;\nconst TABLE_ACTIONS_COLUMN_WIDTH = 56;\n\nconst TABLE_CELL_STYLE: CSSProperties = {\n display: 'grid',\n gap: 4,\n minWidth: 0,\n};\n\ntype FormTableRowRecord = Readonly<\n Record<string, unknown> & {\n index: number;\n key: string;\n }\n>;\n\n/**\n * Rows carry no identity in `formData` — array order is display order\n * (ADR 16 §3.2) — so the React key and, from P3, the per-cell DataSource state\n * key come from an ephemeral id minted here and never written to the value.\n */\nfunction FormTableField({\n dataSourceContext,\n dataSourceInitialValues,\n errors,\n field,\n onChange,\n onDataSourceStateChange,\n optionSnapshots,\n readonly,\n schema,\n uiSchema,\n value,\n values,\n}: {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n readonly errors: Readonly<Record<string, string>>;\n readonly field: TableFieldDefinition;\n readonly onChange: (\n fieldKey: string,\n value: FormFieldValue | undefined,\n ) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'status'\n >,\n ) => void;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n readonly value: FormFieldValue | undefined;\n readonly values: FormRendererValues;\n}): ReactElement {\n const rows = readFormTableRows(value);\n const bounds = readFormTableRowBounds(field);\n const rowIdSequenceRef = useRef(0);\n const [rowIds, setRowIds] = useState<readonly string[]>(() =>\n rows.map((): string => createRowId(rowIdSequenceRef)),\n );\n\n // The value can also change from outside (a parent reset, a returned case\n // loading its saved rows). Re-mint ids only then, so ordinary edits keep the\n // per-row identity stable.\n useEffect((): void => {\n if (rowIds.length === rows.length) {\n return;\n }\n\n setRowIds(rows.map((): string => createRowId(rowIdSequenceRef)));\n }, [rowIds.length, rows.length]);\n\n function commitRows(nextRows: readonly FormTableRowValue[]): void {\n onChange(field.fieldKey, nextRows);\n }\n\n function handleAddRow(): void {\n setRowIds([...rowIds, createRowId(rowIdSequenceRef)]);\n commitRows([...rows, createFormTableRow(field.columns)]);\n }\n\n function handleRemoveRow(rowIndex: number): void {\n setRowIds(rowIds.filter((_, index) => index !== rowIndex));\n commitRows(rows.filter((_, index) => index !== rowIndex));\n }\n\n function handleCellChange(\n rowIndex: number,\n columnKey: string,\n cellValue: FormFieldValue | undefined,\n ): void {\n commitRows(\n rows.map((row, index) =>\n index === rowIndex\n ? readNextTableRow(row, columnKey, cellValue)\n : row,\n ),\n );\n }\n\n const dataSource: readonly FormTableRowRecord[] = rows.map((row, index) => ({\n ...row,\n index,\n key: rowIds[index] ?? `row-${index}`,\n }));\n const columns: TableColumn<FormTableRowRecord>[] = field.columns.map(\n (column) => ({\n key: column.fieldKey,\n render: (row): ReactElement => (\n <FormTableCell\n column={column}\n dataSourceContext={dataSourceContext}\n error={\n errors[\n readFormTableCellPath(field.fieldKey, row.index, column.fieldKey)\n ] ?? null\n }\n fieldPath={`${field.fieldKey}.${column.fieldKey}`}\n formData={values}\n initialFormData={dataSourceInitialValues}\n initialRowValues={readInitialTableRow(\n dataSourceInitialValues?.[field.fieldKey],\n row.index,\n )}\n // Keyed by the ephemeral row id, so a cell's DataSource state follows\n // its row rather than its position (ADR 16 §3.9).\n key={`${rowIds[row.index] ?? row.index}-${column.fieldKey}`}\n onChange={(cellValue): void =>\n handleCellChange(row.index, column.fieldKey, cellValue)\n }\n onDataSourceStateChange={onDataSourceStateChange}\n optionSnapshots={optionSnapshots}\n path={readFormTableCellPath(\n field.fieldKey,\n row.index,\n column.fieldKey,\n )}\n readonly={readonly}\n rowValues={rows[row.index] ?? {}}\n schema={schema}\n uiSchema={uiSchema}\n />\n ),\n title: column.label || column.fieldKey,\n }),\n );\n const actions: TableActions<FormTableRowRecord> | undefined = readonly\n ? undefined\n : {\n render: (row): ReturnType<TableActions<FormTableRowRecord>['render']> => [\n {\n disabled: (): boolean => rows.length <= bounds.minRows,\n icon: TrashIcon,\n iconType: 'icon-only',\n name: '刪除此列',\n onClick: (): void => handleRemoveRow(row.index),\n variant: 'destructive-ghost',\n },\n ],\n width: 56,\n };\n\n return (\n <div style={TABLE_FIELD_STYLE}>\n {/*\n Mezzanine `Table`'s `scroll` is vertical only, so a wide table is kept\n inside its own horizontal scroller rather than pushing the page sideways.\n The wrapper adds no styling of its own to the table (ADR 16 §3.9 assumed\n `scroll` covered this; see docs/17).\n */}\n <div style={TABLE_SCROLLER_STYLE}>\n <div\n style={{\n minWidth:\n field.columns.length * TABLE_COLUMN_MIN_WIDTH +\n (actions ? TABLE_ACTIONS_COLUMN_WIDTH : 0),\n }}\n >\n <Table\n {...(actions ? { actions } : {})}\n columns={columns}\n dataSource={[...dataSource]}\n // Cells carry form controls rather than text, so the row height\n // comes from the roomy container token, not the compact default.\n rowHeightPreset=\"roomy\"\n showHeader\n size=\"sub\"\n />\n </div>\n </div>\n {readonly ? null : (\n <div style={TABLE_FIELD_ACTIONS_STYLE}>\n <Button\n disabled={rows.length >= bounds.maxRows}\n icon={PlusIcon}\n iconType=\"leading\"\n onClick={handleAddRow}\n size=\"sub\"\n type=\"button\"\n variant=\"base-secondary\"\n >\n {field.addRowLabel || '新增一列'}\n </Button>\n </div>\n )}\n </div>\n );\n}\n\nfunction FormTableCell({\n column,\n dataSourceContext,\n error,\n fieldPath,\n formData,\n initialFormData,\n initialRowValues,\n onChange,\n onDataSourceStateChange,\n optionSnapshots,\n path,\n readonly,\n rowValues,\n schema,\n uiSchema,\n}: {\n readonly column: TableColumnDefinition;\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly error: string | null;\n readonly fieldPath: string;\n readonly formData: FormRendererValues;\n readonly initialFormData?: FormRendererValues;\n readonly initialRowValues?: FormTableRowValue;\n readonly onChange: (value: FormFieldValue | undefined) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'status'\n >,\n ) => void;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly path: string;\n readonly readonly: boolean;\n readonly rowValues: FormTableRowValue;\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n}): ReactElement {\n const value = readTableCellValue(rowValues, column.fieldKey);\n const dataSourceState = useFormDataSourceField({\n context: dataSourceContext,\n field: column,\n fieldPath,\n formData,\n initialFormData,\n initialValue: initialRowValues\n ? readTableCellValue(initialRowValues, column.fieldKey)\n : undefined,\n optionSnapshots,\n readonly,\n row: {\n initialValues: initialRowValues,\n values: rowValues,\n },\n schema,\n snapshotKey: path,\n uiSchema,\n });\n const statusMessage = readFormDataSourceFieldStatusMessage(dataSourceState);\n const stateSignature = JSON.stringify({\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n status: dataSourceState.status,\n });\n\n useEffect((): (() => void) | void => {\n if (!isFormDataSourceFieldDefinition(column)) {\n return;\n }\n\n onDataSourceStateChange?.(path, {\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n status: dataSourceState.status,\n });\n\n // A deleted row must stop blocking the submit it can no longer be fixed in.\n return (): void => {\n onDataSourceStateChange?.(path, {\n hasValue: false,\n invalidValues: [],\n status: 'IDLE',\n });\n };\n }, [onDataSourceStateChange, path, stateSignature]);\n\n return (\n <div data-form-field-key={path} style={TABLE_CELL_STYLE}>\n {renderControl(\n column,\n value,\n readonly,\n (_columnKey, nextValue): void => onChange(nextValue),\n undefined,\n dataSourceContext,\n dataSourceState,\n 'sub',\n )}\n {error ? (\n <Typography color=\"text-error\" variant=\"caption\">\n {error}\n </Typography>\n ) : null}\n {statusMessage ? (\n <div style={DATA_SOURCE_FEEDBACK_STYLE}>\n <Typography\n color={\n dataSourceState.status === 'INVALID' ||\n dataSourceState.status === 'UNAVAILABLE'\n ? 'text-error'\n : 'text-neutral'\n }\n variant=\"caption\"\n >\n {statusMessage}\n </Typography>\n {dataSourceState.status === 'UNAVAILABLE' &&\n dataSourceState.canRetry ? (\n <Button\n onClick={dataSourceState.retry}\n size=\"sub\"\n type=\"button\"\n variant=\"base-ghost\"\n >\n 重試\n </Button>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * The row as it was loaded, used to tell a returned case's stored values from\n * edits. Absent when the table itself is new.\n */\nfunction readInitialTableRow(\n value: FormFieldValue | undefined,\n rowIndex: number,\n): FormTableRowValue | undefined {\n return readFormTableRows(value)[rowIndex];\n}\n\nfunction createRowId(sequenceRef: { current: number }): string {\n sequenceRef.current += 1;\n\n return `table-row-${sequenceRef.current}`;\n}\n\n/**\n * Clearing a cell removes the key instead of storing `undefined`: the row is\n * serialised to JSON, where `undefined` would vanish anyway, and an absent key\n * is what an untouched cell looks like.\n */\nfunction readNextTableRow(\n row: FormTableRowValue,\n columnKey: string,\n value: FormFieldValue | undefined,\n): FormTableRowValue {\n if (typeof value === 'undefined' || !isFormTableCellValue(value)) {\n return Object.fromEntries(\n Object.entries(row).filter(([key]) => key !== columnKey),\n );\n }\n\n return { ...row, [columnKey]: value };\n}\n\nfunction readTableCellValue(\n row: FormTableRowValue | undefined,\n columnKey: string,\n): FormFieldValue | undefined {\n if (!row || !Object.prototype.hasOwnProperty.call(row, columnKey)) {\n return undefined;\n }\n\n return row[columnKey];\n}\n\nfunction renderControl(\n field: FormFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n onUploadAttachment:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined,\n dataSourceContext: FormRendererDataSourceContext | undefined,\n dataSourceState: FormDataSourceFieldState,\n size: ControlSize = 'main',\n): ReactElement {\n if (isFormDataSourceFieldDefinition(field)) {\n return renderDynamicOptionControl(\n field,\n value,\n readonly,\n onChange,\n dataSourceContext,\n dataSourceState,\n size,\n );\n }\n\n return renderStaticControl(\n field,\n value,\n readonly,\n onChange,\n onUploadAttachment,\n size,\n );\n}\n\n/**\n * Every control that does not depend on a DataSource. Split out of\n * {@link renderControl} so a table cell can reuse the same controls without\n * carrying a field-level DataSource state it has no use for (ADR 16 §3.9).\n */\nfunction renderStaticControl(\n field: FormFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n onUploadAttachment:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined,\n size: ControlSize = 'main',\n): ReactElement {\n const link =\n readonly &&\n (field.type === 'text' || field.type === 'textarea') &&\n field.displayAs === 'link'\n ? resolveFormFieldLink(value)\n : null;\n\n // Only a safe href becomes an anchor; anything else falls through to the\n // ordinary read-only control, i.e. plain text.\n if (link) {\n return (\n // Typography types its props as a paragraph's, so it cannot take `href`;\n // it supplies the brand colour and the anchor inherits it.\n <Typography color=\"text-brand\" component=\"span\" variant=\"body\">\n <a\n href={link.href}\n style={LINK_STYLE}\n {...(link.external\n ? {\n // The cue lives in attributes, not in a child node, so\n // selecting or copying the link yields just the URL.\n 'aria-label': `${link.href}${EXTERNAL_LINK_CUE}`,\n rel: 'noopener noreferrer',\n target: '_blank',\n title: EXTERNAL_LINK_TITLE,\n }\n : {})}\n >\n {link.href}\n </a>\n </Typography>\n );\n }\n\n if (field.type === 'textarea') {\n return (\n <Textarea\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void =>\n onChange(field.fieldKey, event.target.value)\n }\n placeholder={field.placeholder ?? '請輸入多行文字'}\n readOnly={readonly}\n ref={applyFullWidthTextareaHost}\n rows={3}\n style={TEXTAREA_STYLE}\n value={readStringValue(value)}\n />\n );\n }\n\n if (field.type === 'boolean') {\n return (\n <Toggle\n checked={value === true}\n disabled={readonly}\n label=\"啟用\"\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n onChange(field.fieldKey, event.target.checked)\n }\n size={size}\n />\n );\n }\n\n if (isFormStaticOptionFieldDefinition(field) && field.type === 'checkbox') {\n const options = field.options.map(readFieldOptionAsSelectOption);\n\n return (\n <Select\n mode=\"multiple\"\n onChange={(options): void =>\n onChange(\n field.fieldKey,\n options?.length\n ? options.map((option) => option.id)\n : undefined,\n )\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇一或多個選項'}\n readOnly={readonly}\n size={size}\n value={options.filter((option) =>\n readStringArrayValue(value).includes(option.id),\n )}\n />\n );\n }\n\n if (isFormStaticOptionFieldDefinition(field)) {\n const options = field.options.map(readFieldOptionAsSelectOption);\n\n return (\n <Select\n onChange={(option): void =>\n onChange(field.fieldKey, option?.id ?? undefined)\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\n size={size}\n value={readSelectOption(options, readStringValue(value))}\n />\n );\n }\n\n if (field.type === 'member') {\n return (\n <MemberFieldControl\n multiple={field.multiple === true}\n name={field.fieldKey}\n onChange={(nextValue): void => onChange(field.fieldKey, nextValue)}\n placeholder={field.placeholder}\n readonly={readonly}\n size={size}\n value={value}\n />\n );\n }\n\n if (field.type === 'file_upload') {\n return (\n <Upload\n accept={field.acceptedMimeTypes?.join(',')}\n disabled={readonly}\n maxFiles={field.maxFiles}\n mode=\"button-list\"\n {...((field.maxFiles ?? 1) > 1 ? { multiple: true } : {})}\n onChange={(files): void =>\n onChange(\n field.fieldKey,\n files.length ? files.map((file) => file.id) : undefined,\n )\n }\n onUpload={async (files): Promise<UploadFile[]> =>\n Promise.all(\n files.map(async (file): Promise<UploadFile> => {\n const uploadedFile = onUploadAttachment\n ? await onUploadAttachment(field, file)\n : { id: `${file.name}-${file.lastModified}` };\n\n return {\n file,\n id: uploadedFile.id,\n progress: 100,\n status: 'done',\n };\n }),\n )\n }\n size=\"sub\"\n />\n );\n }\n\n if (field.type === 'date') {\n return (\n <DatePicker\n format=\"YYYY-MM-DD\"\n fullWidth\n onChange={(nextValue): void =>\n onChange(field.fieldKey, formatDatePickerValue(nextValue))\n }\n placeholder={field.placeholder ?? readInputPlaceholder(field.type)}\n readOnly={readonly}\n value={readDatePickerValue(value)}\n />\n );\n }\n\n if (field.type === 'datetime') {\n return (\n <DateTimePicker\n formatDate=\"YYYY-MM-DD\"\n formatTime=\"HH:mm\"\n fullWidth\n hideSecond\n onChange={(nextValue): void =>\n onChange(field.fieldKey, formatDateTimePickerValue(nextValue))\n }\n placeholderLeft=\"選擇日期\"\n placeholderRight=\"選擇時間\"\n readOnly={readonly}\n value={readDatePickerValue(value)}\n />\n );\n }\n\n if (field.type === 'number' || field.type === 'money') {\n return (\n <Input\n fullWidth\n max={field.maximum}\n min={field.minimum}\n size={size}\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n onChange(\n field.fieldKey,\n clampOptionalNumber(parseOptionalNumberInput(event.target.value), {\n max: field.maximum,\n min: field.minimum,\n }),\n )\n }\n placeholder={field.placeholder ?? readInputPlaceholder(field.type)}\n {...(readonly ? { readonly: true as const } : {})}\n showSpinner\n value={readNumberInputValue(value)}\n variant=\"measure\"\n />\n );\n }\n\n return (\n <Input\n fullWidth\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n onChange(field.fieldKey, event.target.value)\n }\n placeholder={field.placeholder ?? readInputPlaceholder(field.type)}\n {...(readonly ? { readonly: true as const } : {})}\n size={size}\n value={readStringValue(value)}\n variant=\"base\"\n />\n );\n}\n\nfunction renderDynamicOptionControl(\n field: FormDataSourceOptionFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n dataSourceContext: FormRendererDataSourceContext | undefined,\n dataSourceState: FormDataSourceFieldState,\n size: ControlSize = 'main',\n): ReactElement {\n const options = dataSourceState.options.map(readFieldOptionAsSelectOption);\n const selectedOptions = readSelectedFormDataSourceOptions(\n value,\n dataSourceState.options,\n ).map(readFieldOptionAsSelectOption);\n const loading = dataSourceState.status === 'LOADING';\n const waitingForDependencies =\n dataSourceState.status === 'WAITING_FOR_DEPENDENCIES';\n const interactiveSearch = Boolean(dataSourceContext) && !readonly;\n const optionControlDisabled = readonly || waitingForDependencies;\n // AutoComplete alone has no pre-query gate — it only learns a dependency is\n // missing from the answer to a search the filler already typed. Disabling it\n // at that point strands that text in a locked input with no way to clear it,\n // so it stays editable and reports the wait through the status line instead.\n const autoCompleteDisabled = readonly;\n\n if (field.type === 'autocomplete') {\n if (readFormFieldSelectionMode(field) === 'multiple') {\n return (\n <AutoComplete\n asyncData={interactiveSearch}\n clearSearchText\n disabled={autoCompleteDisabled}\n disabledOptionsFilter={interactiveSearch}\n globalPortal\n loading={loading}\n mode=\"multiple\"\n onChange={(nextOptions): void =>\n onChange(\n field.fieldKey,\n nextOptions.length\n ? nextOptions.map((option) => option.id)\n : undefined,\n )\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\n size={size}\n value={selectedOptions}\n />\n );\n }\n\n return (\n <AutoComplete\n asyncData={interactiveSearch}\n clearSearchText\n disabled={autoCompleteDisabled}\n disabledOptionsFilter={interactiveSearch}\n globalPortal\n loading={loading}\n mode=\"single\"\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\n size={size}\n value={selectedOptions[0] ?? null}\n />\n );\n }\n\n if (field.type === 'radio') {\n return (\n <RadioGroup\n disabled={optionControlDisabled}\n name={field.fieldKey}\n onChange={(event): void =>\n onChange(field.fieldKey, event.target.value || undefined)\n }\n options={options}\n value={readStringValue(value)}\n />\n );\n }\n\n if (field.type === 'checkbox') {\n return (\n <CheckboxGroup\n disabled={optionControlDisabled}\n name={field.fieldKey}\n onChange={(event): void =>\n onChange(\n field.fieldKey,\n event.target.values.length ? event.target.values : undefined,\n )\n }\n options={options.map((option) => ({\n label: option.name,\n value: option.id,\n }))}\n value={[...readStringArrayValue(value)]}\n />\n );\n }\n\n if (readFormFieldSelectionMode(field) === 'multiple') {\n return (\n <Select\n disabled={optionControlDisabled}\n loading={loading}\n mode=\"multiple\"\n onChange={(nextOptions): void =>\n onChange(\n field.fieldKey,\n nextOptions.length\n ? nextOptions.map((option) => option.id)\n : undefined,\n )\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇一或多個選項'}\n readOnly={readonly}\n size={size}\n value={selectedOptions}\n />\n );\n }\n\n return (\n <Select\n disabled={optionControlDisabled}\n loading={loading}\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\n size={size}\n value={selectedOptions[0] ?? null}\n />\n );\n}\n\nfunction readFieldColumnSpan(\n field: FormFieldDefinition,\n uiSchema: FormUiSchema,\n): number {\n const width =\n uiSchema.layout.find((item) => item.fieldKey === field.fieldKey)?.width ??\n 'FULL';\n const spans: Readonly<Record<typeof width, number>> = {\n FULL: 12,\n HALF: 6,\n THIRD: 4,\n };\n\n return spans[width];\n}\n\nfunction readStringValue(value: FormFieldValue | undefined): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction readStringArrayValue(\n value: FormFieldValue | undefined,\n): readonly string[] {\n // A table value is also an array, so the element check is what separates a\n // multi-select value from table rows here.\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n ? value\n : [];\n}\n\nfunction readNumberInputValue(value: FormFieldValue | undefined): string {\n return typeof value === 'number' ? String(value) : '';\n}\n\nfunction readInputPlaceholder(type: FormFieldDefinition['type']): string {\n const placeholders: Readonly<Record<FormFieldDefinition['type'], string>> = {\n boolean: '',\n autocomplete: '請輸入或選擇',\n checkbox: '請選擇一或多個選項',\n date: '請選擇日期',\n datetime: '請選擇日期與時間',\n file_upload: '請上傳附件',\n member: '搜尋姓名或信箱',\n money: '請輸入金額',\n number: '請輸入數字',\n radio: '請選擇一個選項',\n select: '請選擇一個選項',\n table: '',\n text: '請輸入文字',\n textarea: '請輸入多行文字',\n };\n\n return placeholders[type];\n}\n\nexport { FormRenderer as FormRendererView };\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use client";require('../admin-pickers.css');let e=require("react"),t=require("@mezzanine-ui/react"),n=require("@rytass/bpm-core-client"),r=require("react/jsx-runtime");var i={pickerHost:`bpm_pickerHost_Sw6gz`};function a({disabled:a=!1,name:o,onChange:s,placeholder:l=`搜尋姓名或信箱`,size:u,value:f}){let[p,g]=(0,e.useState)(!1),[_,v]=(0,e.useState)(f?[f]:[]);async function y(e){g(!0);try{v((await(0,n.searchMembers)(e)).map(c))}finally{g(!1)}}return(0,r.jsx)(`div`,{className:i.pickerHost,children:(0,r.jsx)(t.AutoComplete,{asyncData:!0,disabled:a,disabledOptionsFilter:!0,emptyText:`沒有符合的會員`,inputProps:{autoCapitalize:`none`,autoCorrect:`off`,name:o,spellCheck:!1},loading:p,loadingText:`搜尋會員中...`,mode:`single`,onChange:e=>s(d(e)),onSearch:y,onSearchTextChange:e=>s(m(e,_)),onVisibilityChange:e=>{e&&y(``)},options:[...h(f?[f]:[],_)],placeholder:l,searchDebounceTime:300,size:u,value:f})})}function o({disabled:n=!1,onChange:a,orgUnits:o,placeholder:s=`選擇組織`,size:c,value:u}){let d=(0,e.useMemo)(()=>o.map(l),[o]);return(0,r.jsx)(`div`,{className:i.pickerHost,children:(0,r.jsx)(t.Select,{clearable:!0,disabled:n,fullWidth:!0,onChange:e=>a(f(e)),options:[...d],placeholder:s,size:c,value:u})})}function s({disabled:n=!1,onChange:a,placeholder:o=`選擇職位`,positions:s,size:c,value:l}){let d=(0,e.useMemo)(()=>s.map(u),[s]);return(0,r.jsx)(`div`,{className:i.pickerHost,children:(0,r.jsx)(t.Select,{clearable:!0,disabled:n,fullWidth:!0,onChange:e=>a(p(e)),options:[...d],placeholder:o,size:c,value:l})})}function c(e){return{email:e.email,id:e.memberId,name:`${e.name} · ${e.email}`}}function l(e){return{id:e.id,name:`${e.name} · ${e.code}`}}function u(e){return{id:e.id,name:`${e.name} · ${e.code}`}}function d(e){if(!g(e))return null;let t=e.email,n=e.id,r=e.name;return typeof t==`string`&&typeof n==`string`&&typeof r==`string`?{email:t,id:n,name:r}:null}function f(e){if(!g(e))return null;let t=e.id,n=e.name;return typeof t==`string`&&typeof n==`string`?{id:t,name:n}:null}function p(e){if(!g(e))return null;let t=e.id,n=e.name;return typeof t==`string`&&typeof n==`string`?{id:t,name:n}:null}function m(e,t){let n=e.trim().toLocaleLowerCase();if(!n)return null;let r=t.filter(e=>e.id.toLocaleLowerCase()===n||e.email.toLocaleLowerCase()===n||e.name.toLocaleLowerCase()===n);return r.length===1?r[0]:null}function h(e,t){return[...new Map([...e,...t].map(e=>[e.id,e])).values()]}function g(e){return typeof e==`object`&&!!e}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return a}});
|
|
2
|
-
//# sourceMappingURL=admin-pickers-Btvij1at.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"admin-pickers-Btvij1at.cjs","names":[],"sources":["../../src/components/admin-pickers.module.scss","../../src/components/admin-pickers.tsx"],"sourcesContent":[".pickerHost {\n width: 100%;\n min-width: 0;\n\n :global(.mzn-auto-complete),\n :global(.mzn-input),\n :global(.mzn-input-container),\n :global(.mzn-select),\n :global(.mzn-select-trigger),\n :global(.mzn-text-field) {\n width: 100%;\n min-width: 0 !important;\n }\n}\n","'use client';\n\nimport { useMemo, useState, type ReactElement } from 'react';\nimport { AutoComplete, Select } from '@mezzanine-ui/react';\nimport { searchMembers } from '@rytass/bpm-core-client';\nimport type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\nimport styles from './admin-pickers.module.scss';\n\nexport type MemberOption = Readonly<{\n email: string;\n id: string;\n name: string;\n}>;\n\nexport type OrgUnitOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport type PositionOption = Readonly<{\n id: string;\n name: string;\n}>;\n\ninterface MemberPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: MemberOption | null) => void;\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: MemberOption | null;\n}\n\ninterface OrgUnitPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: OrgUnitOption | null) => void;\n readonly orgUnits: readonly OrgUnitRecord[];\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: OrgUnitOption | null;\n}\n\ninterface PositionPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: PositionOption | null) => void;\n readonly placeholder?: string;\n readonly positions: readonly PositionRecord[];\n readonly size?: 'main' | 'sub';\n readonly value: PositionOption | null;\n}\n\n/**\n * AutoComplete-backed picker that searches the BPM member directory via\n * `searchMembers()`. Emits a {@link MemberOption} `{ id, name, email }`\n * on selection (or `null` when cleared).\n */\nexport function MemberPicker({\n disabled = false,\n name,\n onChange,\n placeholder = '搜尋姓名或信箱',\n size,\n value,\n}: MemberPickerProps): ReactElement {\n const [loading, setLoading] = useState(false);\n const [options, setOptions] = useState<readonly MemberOption[]>(\n value ? [value] : [],\n );\n\n async function handleSearch(searchText: string): Promise<void> {\n setLoading(true);\n try {\n setOptions((await searchMembers(searchText)).map(readMemberOption));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className={styles.pickerHost}>\n <AutoComplete\n asyncData\n disabled={disabled}\n disabledOptionsFilter\n emptyText=\"沒有符合的會員\"\n inputProps={{\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n }}\n loading={loading}\n loadingText=\"搜尋會員中...\"\n mode=\"single\"\n onChange={(option): void => onChange(readMemberOptionFromValue(option))}\n onSearch={handleSearch}\n onSearchTextChange={(searchText): void =>\n onChange(readUniqueOption(searchText, options))\n }\n onVisibilityChange={(open): void => {\n if (open) {\n void handleSearch('');\n }\n }}\n options={[...mergeMemberOptions(value ? [value] : [], options)]}\n placeholder={placeholder}\n searchDebounceTime={300}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of org units. Caller is\n * responsible for fetching the org units (typically via\n * `readOrganizationDashboard` from `@rytass/bpm-core-client/organization`).\n */\nexport function OrgUnitPicker({\n disabled = false,\n onChange,\n orgUnits,\n placeholder = '選擇組織',\n size,\n value,\n}: OrgUnitPickerProps): ReactElement {\n const options = useMemo(\n (): readonly OrgUnitOption[] => orgUnits.map(readOrgUnitOption),\n [orgUnits],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n fullWidth\n onChange={(option): void => onChange(readOrgUnitOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of positions.\n */\nexport function PositionPicker({\n disabled = false,\n onChange,\n placeholder = '選擇職位',\n positions,\n size,\n value,\n}: PositionPickerProps): ReactElement {\n const options = useMemo(\n (): readonly PositionOption[] => positions.map(readPositionOption),\n [positions],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n fullWidth\n onChange={(option): void => onChange(readPositionOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\nexport function readMemberOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberOption {\n return {\n email: member.email,\n id: member.memberId,\n name: `${member.name} · ${member.email}`,\n };\n}\n\nexport function readOrgUnitOption(orgUnit: OrgUnitRecord): OrgUnitOption {\n return { id: orgUnit.id, name: `${orgUnit.name} · ${orgUnit.code}` };\n}\n\nexport function readPositionOption(position: PositionRecord): PositionOption {\n return { id: position.id, name: `${position.name} · ${position.code}` };\n}\n\nfunction readMemberOptionFromValue(value: unknown): MemberOption | null {\n if (!isRecord(value)) return null;\n const email = value.email;\n const id = value.id;\n const name = value.name;\n return typeof email === 'string' &&\n typeof id === 'string' &&\n typeof name === 'string'\n ? { email, id, name }\n : null;\n}\n\nfunction readOrgUnitOptionFromValue(value: unknown): OrgUnitOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readPositionOptionFromValue(value: unknown): PositionOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readUniqueOption(\n searchText: string,\n options: readonly MemberOption[],\n): MemberOption | null {\n const normalized = searchText.trim().toLocaleLowerCase();\n if (!normalized) return null;\n const matched = options.filter(\n (option) =>\n option.id.toLocaleLowerCase() === normalized ||\n option.email.toLocaleLowerCase() === normalized ||\n option.name.toLocaleLowerCase() === normalized,\n );\n return matched.length === 1 ? matched[0] : null;\n}\n\nfunction mergeMemberOptions(\n selected: readonly MemberOption[],\n options: readonly MemberOption[],\n): readonly MemberOption[] {\n const byId = new Map(\n [...selected, ...options].map(\n (option): readonly [string, MemberOption] => [option.id, option],\n ),\n );\n return [...byId.values()];\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"mLC6DA,SAAgB,EAAa,CAC3B,WAAW,GACX,OACA,WACA,cAAc,UACd,OACA,SACkC,CAClC,GAAM,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAS,EAAK,EACtC,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAC5B,EAAQ,CAAC,CAAK,EAAI,CAAC,CACrB,EAEA,eAAe,EAAa,EAAmC,CAC7D,EAAW,EAAI,EACf,GAAI,CACF,GAAY,MAAA,EAAM,EAAA,cAAA,CAAc,CAAU,EAAA,CAAG,IAAI,CAAgB,CAAC,CACpE,QAAU,CACR,EAAW,EAAK,CAClB,CACF,CAEA,OACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAO,WACrB,UAAA,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CACE,UAAA,GACU,WACV,sBAAA,GACA,UAAU,UACV,WAAY,CACV,eAAgB,OAChB,YAAa,MACb,OACA,WAAY,EACd,EACS,UACT,YAAY,WACZ,KAAK,SACL,SAAW,GAAiB,EAAS,EAA0B,CAAM,CAAC,EACtE,SAAU,EACV,mBAAqB,GACnB,EAAS,EAAiB,EAAY,CAAO,CAAC,EAEhD,mBAAqB,GAAe,CAC9B,GACF,EAAkB,EAAE,CAExB,EACA,QAAS,CAAC,GAAG,EAAmB,EAAQ,CAAC,CAAK,EAAI,CAAC,EAAG,CAAO,CAAC,EACjD,cACb,mBAAoB,IACd,OACC,OACR,CAAA,CACE,CAAA,CAET,CAOA,SAAgB,EAAc,CAC5B,WAAW,GACX,WACA,WACA,cAAc,OACd,OACA,SACmC,CACnC,IAAM,GAAA,EAAU,EAAA,QAAA,KACkB,EAAS,IAAI,CAAiB,EAC9D,CAAC,CAAQ,CACX,EACA,OACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAO,WACrB,UAAA,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CACE,UAAA,GACU,WACV,UAAA,GACA,SAAW,GAAiB,EAAS,EAA2B,CAAM,CAAC,EACvE,QAAS,CAAC,GAAG,CAAO,EACP,cACP,OACC,OACR,CAAA,CACE,CAAA,CAET,CAKA,SAAgB,EAAe,CAC7B,WAAW,GACX,WACA,cAAc,OACd,YACA,OACA,SACoC,CACpC,IAAM,GAAA,EAAU,EAAA,QAAA,KACmB,EAAU,IAAI,CAAkB,EACjE,CAAC,CAAS,CACZ,EACA,OACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAO,WACrB,UAAA,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CACE,UAAA,GACU,WACV,UAAA,GACA,SAAW,GAAiB,EAAS,EAA4B,CAAM,CAAC,EACxE,QAAS,CAAC,GAAG,CAAO,EACP,cACP,OACC,OACR,CAAA,CACE,CAAA,CAET,CAEA,SAAgB,EAAiB,EAIhB,CACf,MAAO,CACL,MAAO,EAAO,MACd,GAAI,EAAO,SACX,KAAM,GAAG,EAAO,KAAK,KAAK,EAAO,OACnC,CACF,CAEA,SAAgB,EAAkB,EAAuC,CACvE,MAAO,CAAE,GAAI,EAAQ,GAAI,KAAM,GAAG,EAAQ,KAAK,KAAK,EAAQ,MAAO,CACrE,CAEA,SAAgB,EAAmB,EAA0C,CAC3E,MAAO,CAAE,GAAI,EAAS,GAAI,KAAM,GAAG,EAAS,KAAK,KAAK,EAAS,MAAO,CACxE,CAEA,SAAS,EAA0B,EAAqC,CACtE,GAAI,CAAC,EAAS,CAAK,EAAG,OAAO,KAC7B,IAAM,EAAQ,EAAM,MACd,EAAK,EAAM,GACX,EAAO,EAAM,KACnB,OAAO,OAAO,GAAU,UACtB,OAAO,GAAO,UACd,OAAO,GAAS,SACd,CAAE,QAAO,KAAI,MAAK,EAClB,IACN,CAEA,SAAS,EAA2B,EAAsC,CACxE,GAAI,CAAC,EAAS,CAAK,EAAG,OAAO,KAC7B,IAAM,EAAK,EAAM,GACX,EAAO,EAAM,KACnB,OAAO,OAAO,GAAO,UAAY,OAAO,GAAS,SAC7C,CAAE,KAAI,MAAK,EACX,IACN,CAEA,SAAS,EAA4B,EAAuC,CAC1E,GAAI,CAAC,EAAS,CAAK,EAAG,OAAO,KAC7B,IAAM,EAAK,EAAM,GACX,EAAO,EAAM,KACnB,OAAO,OAAO,GAAO,UAAY,OAAO,GAAS,SAC7C,CAAE,KAAI,MAAK,EACX,IACN,CAEA,SAAS,EACP,EACA,EACqB,CACrB,IAAM,EAAa,EAAW,KAAK,CAAC,CAAC,kBAAkB,EACvD,GAAI,CAAC,EAAY,OAAO,KACxB,IAAM,EAAU,EAAQ,OACrB,GACC,EAAO,GAAG,kBAAkB,IAAM,GAClC,EAAO,MAAM,kBAAkB,IAAM,GACrC,EAAO,KAAK,kBAAkB,IAAM,CACxC,EACA,OAAO,EAAQ,SAAW,EAAI,EAAQ,GAAK,IAC7C,CAEA,SAAS,EACP,EACA,EACyB,CAMzB,MAAO,CAAC,GAAG,IALM,IACf,CAAC,GAAG,EAAU,GAAG,CAAO,CAAC,CAAC,IACvB,GAA4C,CAAC,EAAO,GAAI,CAAM,CACjE,CAES,CAAA,CAAK,OAAO,CAAC,CAC1B,CAEA,SAAS,EAAS,EAA4D,CAC5E,OAAO,OAAO,GAAU,YAAY,CACtC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"admin-pickers-DLlG_1du.js","names":[],"sources":["../../src/components/admin-pickers.module.scss","../../src/components/admin-pickers.tsx"],"sourcesContent":[".pickerHost {\n width: 100%;\n min-width: 0;\n\n :global(.mzn-auto-complete),\n :global(.mzn-input),\n :global(.mzn-input-container),\n :global(.mzn-select),\n :global(.mzn-select-trigger),\n :global(.mzn-text-field) {\n width: 100%;\n min-width: 0 !important;\n }\n}\n","'use client';\n\nimport { useMemo, useState, type ReactElement } from 'react';\nimport { AutoComplete, Select } from '@mezzanine-ui/react';\nimport { searchMembers } from '@rytass/bpm-core-client';\nimport type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\nimport styles from './admin-pickers.module.scss';\n\nexport type MemberOption = Readonly<{\n email: string;\n id: string;\n name: string;\n}>;\n\nexport type OrgUnitOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport type PositionOption = Readonly<{\n id: string;\n name: string;\n}>;\n\ninterface MemberPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: MemberOption | null) => void;\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: MemberOption | null;\n}\n\ninterface OrgUnitPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: OrgUnitOption | null) => void;\n readonly orgUnits: readonly OrgUnitRecord[];\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: OrgUnitOption | null;\n}\n\ninterface PositionPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: PositionOption | null) => void;\n readonly placeholder?: string;\n readonly positions: readonly PositionRecord[];\n readonly size?: 'main' | 'sub';\n readonly value: PositionOption | null;\n}\n\n/**\n * AutoComplete-backed picker that searches the BPM member directory via\n * `searchMembers()`. Emits a {@link MemberOption} `{ id, name, email }`\n * on selection (or `null` when cleared).\n */\nexport function MemberPicker({\n disabled = false,\n name,\n onChange,\n placeholder = '搜尋姓名或信箱',\n size,\n value,\n}: MemberPickerProps): ReactElement {\n const [loading, setLoading] = useState(false);\n const [options, setOptions] = useState<readonly MemberOption[]>(\n value ? [value] : [],\n );\n\n async function handleSearch(searchText: string): Promise<void> {\n setLoading(true);\n try {\n setOptions((await searchMembers(searchText)).map(readMemberOption));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className={styles.pickerHost}>\n <AutoComplete\n asyncData\n disabled={disabled}\n disabledOptionsFilter\n emptyText=\"沒有符合的會員\"\n inputProps={{\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n }}\n loading={loading}\n loadingText=\"搜尋會員中...\"\n mode=\"single\"\n onChange={(option): void => onChange(readMemberOptionFromValue(option))}\n onSearch={handleSearch}\n onSearchTextChange={(searchText): void =>\n onChange(readUniqueOption(searchText, options))\n }\n onVisibilityChange={(open): void => {\n if (open) {\n void handleSearch('');\n }\n }}\n options={[...mergeMemberOptions(value ? [value] : [], options)]}\n placeholder={placeholder}\n searchDebounceTime={300}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of org units. Caller is\n * responsible for fetching the org units (typically via\n * `readOrganizationDashboard` from `@rytass/bpm-core-client/organization`).\n */\nexport function OrgUnitPicker({\n disabled = false,\n onChange,\n orgUnits,\n placeholder = '選擇組織',\n size,\n value,\n}: OrgUnitPickerProps): ReactElement {\n const options = useMemo(\n (): readonly OrgUnitOption[] => orgUnits.map(readOrgUnitOption),\n [orgUnits],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n fullWidth\n onChange={(option): void => onChange(readOrgUnitOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of positions.\n */\nexport function PositionPicker({\n disabled = false,\n onChange,\n placeholder = '選擇職位',\n positions,\n size,\n value,\n}: PositionPickerProps): ReactElement {\n const options = useMemo(\n (): readonly PositionOption[] => positions.map(readPositionOption),\n [positions],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n fullWidth\n onChange={(option): void => onChange(readPositionOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\nexport function readMemberOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberOption {\n return {\n email: member.email,\n id: member.memberId,\n name: `${member.name} · ${member.email}`,\n };\n}\n\nexport function readOrgUnitOption(orgUnit: OrgUnitRecord): OrgUnitOption {\n return { id: orgUnit.id, name: `${orgUnit.name} · ${orgUnit.code}` };\n}\n\nexport function readPositionOption(position: PositionRecord): PositionOption {\n return { id: position.id, name: `${position.name} · ${position.code}` };\n}\n\nfunction readMemberOptionFromValue(value: unknown): MemberOption | null {\n if (!isRecord(value)) return null;\n const email = value.email;\n const id = value.id;\n const name = value.name;\n return typeof email === 'string' &&\n typeof id === 'string' &&\n typeof name === 'string'\n ? { email, id, name }\n : null;\n}\n\nfunction readOrgUnitOptionFromValue(value: unknown): OrgUnitOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readPositionOptionFromValue(value: unknown): PositionOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readUniqueOption(\n searchText: string,\n options: readonly MemberOption[],\n): MemberOption | null {\n const normalized = searchText.trim().toLocaleLowerCase();\n if (!normalized) return null;\n const matched = options.filter(\n (option) =>\n option.id.toLocaleLowerCase() === normalized ||\n option.email.toLocaleLowerCase() === normalized ||\n option.name.toLocaleLowerCase() === normalized,\n );\n return matched.length === 1 ? matched[0] : null;\n}\n\nfunction mergeMemberOptions(\n selected: readonly MemberOption[],\n options: readonly MemberOption[],\n): readonly MemberOption[] {\n const byId = new Map(\n [...selected, ...options].map(\n (option): readonly [string, MemberOption] => [option.id, option],\n ),\n );\n return [...byId.values()];\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"categories-DGlxuC6X.js","names":[],"sources":["../../src/views/templates/templates.module.scss","../../src/views/templates/categories/TemplateCategoriesView.tsx"],"sourcesContent":[".templateFilterArea {\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.templateModalFields {\n display: grid;\n gap: 16px;\n}\n","'use client';\n\nimport type { ChangeEvent, Key, ReactElement } from 'react';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n Input,\n Modal,\n PageHeader,\n Section,\n SectionGroup,\n Tab,\n TabItem,\n Table,\n Textarea,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { PlusIcon } from '@mezzanine-ui/icons';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport { BPMFormField } from '../../../components/bpm-form-field';\nimport { formatDateTime } from '../../../lib/format-date-time';\nimport {\n ApprovalTemplateCategoryRecord,\n ApprovalTemplateCategoryStatus,\n createApprovalTemplateCategory,\n deleteApprovalTemplateCategory,\n listApprovalTemplateCategoriesPage,\n updateApprovalTemplateCategory,\n} from '@rytass/bpm-core-client/template';\nimport styles from '../templates.module.scss';\n\nconst CATEGORY_PAGE_SIZE_OPTIONS = [10, 20, 50];\nconst CATEGORY_STATUS_TABS: readonly {\n readonly key: CategoryStatusTabKey;\n readonly label: string;\n}[] = [\n { key: 'ALL', label: '全部' },\n { key: 'ACTIVE', label: '啟用' },\n { key: 'INACTIVE', label: '停用' },\n];\ntype CategoryStatusTabKey = 'ACTIVE' | 'ALL' | 'INACTIVE';\n\ntype CategoryRow = Readonly<\n Record<string, unknown> &\n ApprovalTemplateCategoryRecord & {\n key: string;\n updatedAtLabel: string;\n }\n>;\n\ntype CategoryModalState = Readonly<{\n record: ApprovalTemplateCategoryRecord | null;\n type: 'CREATE' | 'EDIT';\n}>;\n\ntype DeleteConfirmationState = Readonly<{\n id: string;\n name: string;\n}>;\n\n\nexport function TemplateCategoriesView(): ReactElement {\n const [categories, setCategories] = useState<\n readonly ApprovalTemplateCategoryRecord[]\n >([]);\n const [categoryPage, setCategoryPage] = useState(1);\n const [categoryPageSize, setCategoryPageSize] = useState(10);\n const [categoryStatus, setCategoryStatus] =\n useState<CategoryStatusTabKey>('ALL');\n const [categoryTotalCount, setCategoryTotalCount] = useState(0);\n const [deleteConfirmation, setDeleteConfirmation] =\n useState<DeleteConfirmationState | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [modalState, setModalState] = useState<CategoryModalState | null>(null);\n const [saving, setSaving] = useState(false);\n const [searchText, setSearchText] = useState('');\n\n const refreshCategories = useCallback(async (): Promise<void> => {\n setLoading(true);\n setError(null);\n\n try {\n const categoryPageResult = await listApprovalTemplateCategoriesPage({\n page: categoryPage,\n pageSize: categoryPageSize,\n searchText,\n status: readCategoryStatus(categoryStatus),\n });\n\n setCategories(categoryPageResult.categories);\n setCategoryTotalCount(categoryPageResult.totalCount);\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setLoading(false);\n }\n }, [categoryPage, categoryPageSize, categoryStatus, searchText]);\n\n useEffect((): void => {\n void refreshCategories();\n }, [refreshCategories]);\n\n const rows = useMemo(\n (): CategoryRow[] =>\n categories.map((category) => ({\n ...category,\n key: category.id,\n updatedAtLabel: formatDateTime(category.updatedAt),\n })),\n [categories],\n );\n\n const columns = useMemo(\n (): TableColumn<CategoryRow>[] => [\n { dataIndex: 'name', key: 'name', title: '分類名稱', width: 180 },\n {\n key: 'status',\n render: (record: CategoryRow): ReactElement => (\n <CategoryStatusBadge isActive={record.isActive} />\n ),\n title: '狀態',\n width: 120,\n },\n {\n dataIndex: 'sortOrder',\n key: 'sortOrder',\n title: '排序',\n width: 100,\n },\n {\n key: 'description',\n render: (record: CategoryRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {record.description || '無'}\n </Typography>\n ),\n title: '說明',\n width: 260,\n },\n {\n dataIndex: 'updatedAtLabel',\n key: 'updatedAtLabel',\n title: '更新時間',\n width: 220,\n },\n ],\n [],\n );\n\n async function handleSaveCategory(input: CategoryFormInput): Promise<void> {\n setSaving(true);\n setError(null);\n\n try {\n if (modalState?.type === 'EDIT' && modalState.record) {\n await updateApprovalTemplateCategory({\n ...input,\n id: modalState.record.id,\n });\n } else {\n await createApprovalTemplateCategory(input);\n }\n\n setModalState(null);\n await refreshCategories();\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n throw requestError;\n } finally {\n setSaving(false);\n }\n }\n\n async function handleToggleCategory(\n record: ApprovalTemplateCategoryRecord,\n ): Promise<void> {\n setSaving(true);\n setError(null);\n\n try {\n await updateApprovalTemplateCategory({\n description: record.description,\n id: record.id,\n isActive: !record.isActive,\n name: record.name,\n sortOrder: record.sortOrder,\n });\n await refreshCategories();\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n async function handleConfirmDelete(): Promise<void> {\n if (!deleteConfirmation) {\n return;\n }\n\n setSaving(true);\n setError(null);\n\n try {\n await deleteApprovalTemplateCategory(deleteConfirmation.id);\n setDeleteConfirmation(null);\n await refreshCategories();\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const tableActions = useMemo(\n (): TableActions<CategoryRow> => ({\n render: (record): ReturnType<TableActions<CategoryRow>['render']> => [\n {\n name: '編輯',\n onClick: (): void => setModalState({ record, type: 'EDIT' }),\n },\n {\n name: record.isActive ? '停用' : '啟用',\n onClick: (): void => void handleToggleCategory(record),\n variant: record.isActive ? 'destructive-secondary' : 'base-secondary',\n },\n {\n name: '刪除',\n onClick: (): void =>\n setDeleteConfirmation({ id: record.id, name: record.name }),\n variant: 'destructive-secondary',\n },\n ],\n variant: 'base-secondary',\n width: 192,\n }),\n [refreshCategories],\n );\n\n return (\n <>\n <>\n <PageHeader>\n <ContentHeader\n description=\"維護簽核模板分類,供模板建立、篩選與列表標示使用。\"\n title=\"簽核模板分類\"\n >\n <Button\n icon={PlusIcon}\n iconType=\"leading\"\n onClick={(): void =>\n setModalState({ record: null, type: 'CREATE' })\n }\n variant=\"base-primary\"\n >\n 建立分類\n </Button>\n </ContentHeader>\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.templateFilterArea} size=\"sub\">\n <FilterLine>\n <Filter span={3}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"categorySearchText\"\n >\n <Input\n fullWidth\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n setSearchText(event.target.value);\n setCategoryPage(1);\n }}\n placeholder=\"關鍵字:搜尋分類名稱或說明\"\n size=\"sub\"\n value={searchText}\n variant=\"base\"\n />\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n tab={\n <Tab\n activeKey={categoryStatus}\n onChange={(activeKey): void => {\n setCategoryStatus(readCategoryStatusTabKey(activeKey));\n setCategoryPage(1);\n }}\n >\n {CATEGORY_STATUS_TABS.map((statusTab) => (\n <TabItem key={statusTab.key}>{statusTab.label}</TabItem>\n ))}\n </Tab>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n <Table\n actions={tableActions}\n columns={columns}\n dataSource={rows}\n fullWidth\n loading={loading || saving}\n pagination={{\n current: categoryPage,\n onChange: (page): void => {\n setCategoryPage(page);\n },\n onChangePageSize: (pageSize): void => {\n setCategoryPage(1);\n setCategoryPageSize(pageSize);\n },\n pageSize: categoryPageSize,\n pageSizeLabel: '每頁筆數',\n pageSizeOptions: CATEGORY_PAGE_SIZE_OPTIONS,\n renderResultSummary: (from, to, total): string =>\n `顯示 ${from}-${to} 筆,共 ${total} 筆`,\n showPageSizeOptions: true,\n total: categoryTotalCount,\n }}\n />\n </Section>\n </SectionGroup>\n </>\n\n <CategoryModal\n loading={saving}\n modal={modalState}\n onClose={(): void => setModalState(null)}\n onSubmit={handleSaveCategory}\n />\n <Modal\n cancelText=\"取消\"\n confirmButtonProps={{ variant: 'destructive-primary' }}\n confirmText=\"刪除\"\n loading={saving}\n modalStatusType=\"error\"\n modalType=\"standard\"\n onCancel={(): void => setDeleteConfirmation(null)}\n onClose={(): void => setDeleteConfirmation(null)}\n onConfirm={(): void => void handleConfirmDelete()}\n open={Boolean(deleteConfirmation)}\n showModalFooter\n showModalHeader\n size=\"regular\"\n supportingText=\"若分類已被模板使用,系統會改為停用分類並保留既有模板關聯。\"\n title=\"刪除分類\"\n >\n <Typography color=\"text-neutral\" variant=\"body\">\n 確定要刪除「{deleteConfirmation?.name ?? ''}」嗎?\n </Typography>\n </Modal>\n </>\n );\n}\n\ninterface CategoryFormInput {\n readonly description: string | null;\n readonly isActive: boolean;\n readonly name: string;\n readonly sortOrder: number;\n}\n\nfunction CategoryModal({\n loading,\n modal,\n onClose,\n onSubmit,\n}: {\n readonly loading: boolean;\n readonly modal: CategoryModalState | null;\n readonly onClose: () => void;\n readonly onSubmit: (input: CategoryFormInput) => Promise<void>;\n}): ReactElement {\n const [description, setDescription] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [name, setName] = useState('');\n const [sortOrder, setSortOrder] = useState('0');\n\n useEffect((): void => {\n if (!modal) {\n return;\n }\n\n setDescription(modal.record?.description ?? '');\n setError(null);\n setName(modal.record?.name ?? '');\n setSortOrder(String(modal.record?.sortOrder ?? 0));\n }, [modal]);\n\n async function handleConfirm(): Promise<void> {\n const trimmedName = name.trim();\n const parsedSortOrder = Number(sortOrder);\n\n if (!trimmedName) {\n setError('請輸入分類名稱');\n return;\n }\n\n if (!Number.isInteger(parsedSortOrder)) {\n setError('排序必須是整數');\n return;\n }\n\n try {\n await onSubmit({\n description: description.trim() || null,\n isActive: modal?.record?.isActive ?? true,\n name: trimmedName,\n sortOrder: parsedSortOrder,\n });\n } catch (submitError: unknown) {\n setError(readErrorMessage(submitError));\n }\n }\n\n return (\n <Modal\n cancelText=\"取消\"\n confirmButtonProps={{ disabled: !name.trim() }}\n confirmText={modal?.type === 'EDIT' ? '儲存' : '建立'}\n loading={loading}\n modalType=\"standard\"\n onCancel={onClose}\n onClose={onClose}\n onConfirm={(): void => void handleConfirm()}\n open={Boolean(modal)}\n showModalFooter\n showModalHeader\n size=\"regular\"\n title={modal?.type === 'EDIT' ? '編輯分類' : '建立分類'}\n >\n <div className={styles.templateModalFields}>\n <BPMFormField label=\"分類名稱\" name=\"categoryName\" required>\n <Input\n autoFocus\n fullWidth\n onChange={(event: ChangeEvent<HTMLInputElement>): void => {\n setName(event.target.value);\n setError(null);\n }}\n placeholder=\"例如:行政管理\"\n value={name}\n variant=\"base\"\n />\n </BPMFormField>\n <BPMFormField label=\"排序\" name=\"categorySortOrder\">\n <Input\n fullWidth\n onChange={(event: ChangeEvent<HTMLInputElement>): void => {\n setSortOrder(event.target.value);\n setError(null);\n }}\n placeholder=\"0\"\n value={sortOrder}\n variant=\"base\"\n />\n </BPMFormField>\n <BPMFormField label=\"說明\" name=\"categoryDescription\">\n <Textarea\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void => {\n setDescription(event.target.value);\n setError(null);\n }}\n placeholder=\"補充分類用途\"\n value={description}\n />\n </BPMFormField>\n </div>\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n </Modal>\n );\n}\n\nfunction CategoryStatusBadge({\n isActive,\n}: {\n readonly isActive: boolean;\n}): ReactElement {\n return isActive ? (\n <Badge size=\"sub\" text=\"啟用\" variant=\"dot-success\" />\n ) : (\n <Badge size=\"sub\" text=\"停用\" variant=\"dot-inactive\" />\n );\n}\n\nfunction readCategoryStatusTabKey(activeKey: Key): CategoryStatusTabKey {\n if (activeKey === 'ACTIVE' || activeKey === 'INACTIVE') {\n return activeKey;\n }\n\n return 'ALL';\n}\n\nfunction readCategoryStatus(\n status: CategoryStatusTabKey,\n): ApprovalTemplateCategoryStatus {\n return status;\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"categories-Yid05WAX.cjs","names":[],"sources":["../../src/views/templates/templates.module.scss","../../src/views/templates/categories/TemplateCategoriesView.tsx"],"sourcesContent":[".templateFilterArea {\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.templateModalFields {\n display: grid;\n gap: 16px;\n}\n","'use client';\n\nimport type { ChangeEvent, Key, ReactElement } from 'react';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n Input,\n Modal,\n PageHeader,\n Section,\n SectionGroup,\n Tab,\n TabItem,\n Table,\n Textarea,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { PlusIcon } from '@mezzanine-ui/icons';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport { BPMFormField } from '../../../components/bpm-form-field';\nimport { formatDateTime } from '../../../lib/format-date-time';\nimport {\n ApprovalTemplateCategoryRecord,\n ApprovalTemplateCategoryStatus,\n createApprovalTemplateCategory,\n deleteApprovalTemplateCategory,\n listApprovalTemplateCategoriesPage,\n updateApprovalTemplateCategory,\n} from '@rytass/bpm-core-client/template';\nimport styles from '../templates.module.scss';\n\nconst CATEGORY_PAGE_SIZE_OPTIONS = [10, 20, 50];\nconst CATEGORY_STATUS_TABS: readonly {\n readonly key: CategoryStatusTabKey;\n readonly label: string;\n}[] = [\n { key: 'ALL', label: '全部' },\n { key: 'ACTIVE', label: '啟用' },\n { key: 'INACTIVE', label: '停用' },\n];\ntype CategoryStatusTabKey = 'ACTIVE' | 'ALL' | 'INACTIVE';\n\ntype CategoryRow = Readonly<\n Record<string, unknown> &\n ApprovalTemplateCategoryRecord & {\n key: string;\n updatedAtLabel: string;\n }\n>;\n\ntype CategoryModalState = Readonly<{\n record: ApprovalTemplateCategoryRecord | null;\n type: 'CREATE' | 'EDIT';\n}>;\n\ntype DeleteConfirmationState = Readonly<{\n id: string;\n name: string;\n}>;\n\n\nexport function TemplateCategoriesView(): ReactElement {\n const [categories, setCategories] = useState<\n readonly ApprovalTemplateCategoryRecord[]\n >([]);\n const [categoryPage, setCategoryPage] = useState(1);\n const [categoryPageSize, setCategoryPageSize] = useState(10);\n const [categoryStatus, setCategoryStatus] =\n useState<CategoryStatusTabKey>('ALL');\n const [categoryTotalCount, setCategoryTotalCount] = useState(0);\n const [deleteConfirmation, setDeleteConfirmation] =\n useState<DeleteConfirmationState | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [modalState, setModalState] = useState<CategoryModalState | null>(null);\n const [saving, setSaving] = useState(false);\n const [searchText, setSearchText] = useState('');\n\n const refreshCategories = useCallback(async (): Promise<void> => {\n setLoading(true);\n setError(null);\n\n try {\n const categoryPageResult = await listApprovalTemplateCategoriesPage({\n page: categoryPage,\n pageSize: categoryPageSize,\n searchText,\n status: readCategoryStatus(categoryStatus),\n });\n\n setCategories(categoryPageResult.categories);\n setCategoryTotalCount(categoryPageResult.totalCount);\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setLoading(false);\n }\n }, [categoryPage, categoryPageSize, categoryStatus, searchText]);\n\n useEffect((): void => {\n void refreshCategories();\n }, [refreshCategories]);\n\n const rows = useMemo(\n (): CategoryRow[] =>\n categories.map((category) => ({\n ...category,\n key: category.id,\n updatedAtLabel: formatDateTime(category.updatedAt),\n })),\n [categories],\n );\n\n const columns = useMemo(\n (): TableColumn<CategoryRow>[] => [\n { dataIndex: 'name', key: 'name', title: '分類名稱', width: 180 },\n {\n key: 'status',\n render: (record: CategoryRow): ReactElement => (\n <CategoryStatusBadge isActive={record.isActive} />\n ),\n title: '狀態',\n width: 120,\n },\n {\n dataIndex: 'sortOrder',\n key: 'sortOrder',\n title: '排序',\n width: 100,\n },\n {\n key: 'description',\n render: (record: CategoryRow): ReactElement => (\n <Typography component=\"span\" variant=\"body\">\n {record.description || '無'}\n </Typography>\n ),\n title: '說明',\n width: 260,\n },\n {\n dataIndex: 'updatedAtLabel',\n key: 'updatedAtLabel',\n title: '更新時間',\n width: 220,\n },\n ],\n [],\n );\n\n async function handleSaveCategory(input: CategoryFormInput): Promise<void> {\n setSaving(true);\n setError(null);\n\n try {\n if (modalState?.type === 'EDIT' && modalState.record) {\n await updateApprovalTemplateCategory({\n ...input,\n id: modalState.record.id,\n });\n } else {\n await createApprovalTemplateCategory(input);\n }\n\n setModalState(null);\n await refreshCategories();\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n throw requestError;\n } finally {\n setSaving(false);\n }\n }\n\n async function handleToggleCategory(\n record: ApprovalTemplateCategoryRecord,\n ): Promise<void> {\n setSaving(true);\n setError(null);\n\n try {\n await updateApprovalTemplateCategory({\n description: record.description,\n id: record.id,\n isActive: !record.isActive,\n name: record.name,\n sortOrder: record.sortOrder,\n });\n await refreshCategories();\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n async function handleConfirmDelete(): Promise<void> {\n if (!deleteConfirmation) {\n return;\n }\n\n setSaving(true);\n setError(null);\n\n try {\n await deleteApprovalTemplateCategory(deleteConfirmation.id);\n setDeleteConfirmation(null);\n await refreshCategories();\n } catch (requestError: unknown) {\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const tableActions = useMemo(\n (): TableActions<CategoryRow> => ({\n render: (record): ReturnType<TableActions<CategoryRow>['render']> => [\n {\n name: '編輯',\n onClick: (): void => setModalState({ record, type: 'EDIT' }),\n },\n {\n name: record.isActive ? '停用' : '啟用',\n onClick: (): void => void handleToggleCategory(record),\n variant: record.isActive ? 'destructive-secondary' : 'base-secondary',\n },\n {\n name: '刪除',\n onClick: (): void =>\n setDeleteConfirmation({ id: record.id, name: record.name }),\n variant: 'destructive-secondary',\n },\n ],\n variant: 'base-secondary',\n width: 192,\n }),\n [refreshCategories],\n );\n\n return (\n <>\n <>\n <PageHeader>\n <ContentHeader\n description=\"維護簽核模板分類,供模板建立、篩選與列表標示使用。\"\n title=\"簽核模板分類\"\n >\n <Button\n icon={PlusIcon}\n iconType=\"leading\"\n onClick={(): void =>\n setModalState({ record: null, type: 'CREATE' })\n }\n variant=\"base-primary\"\n >\n 建立分類\n </Button>\n </ContentHeader>\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.templateFilterArea} size=\"sub\">\n <FilterLine>\n <Filter span={3}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"categorySearchText\"\n >\n <Input\n fullWidth\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n setSearchText(event.target.value);\n setCategoryPage(1);\n }}\n placeholder=\"關鍵字:搜尋分類名稱或說明\"\n size=\"sub\"\n value={searchText}\n variant=\"base\"\n />\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n tab={\n <Tab\n activeKey={categoryStatus}\n onChange={(activeKey): void => {\n setCategoryStatus(readCategoryStatusTabKey(activeKey));\n setCategoryPage(1);\n }}\n >\n {CATEGORY_STATUS_TABS.map((statusTab) => (\n <TabItem key={statusTab.key}>{statusTab.label}</TabItem>\n ))}\n </Tab>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n <Table\n actions={tableActions}\n columns={columns}\n dataSource={rows}\n fullWidth\n loading={loading || saving}\n pagination={{\n current: categoryPage,\n onChange: (page): void => {\n setCategoryPage(page);\n },\n onChangePageSize: (pageSize): void => {\n setCategoryPage(1);\n setCategoryPageSize(pageSize);\n },\n pageSize: categoryPageSize,\n pageSizeLabel: '每頁筆數',\n pageSizeOptions: CATEGORY_PAGE_SIZE_OPTIONS,\n renderResultSummary: (from, to, total): string =>\n `顯示 ${from}-${to} 筆,共 ${total} 筆`,\n showPageSizeOptions: true,\n total: categoryTotalCount,\n }}\n />\n </Section>\n </SectionGroup>\n </>\n\n <CategoryModal\n loading={saving}\n modal={modalState}\n onClose={(): void => setModalState(null)}\n onSubmit={handleSaveCategory}\n />\n <Modal\n cancelText=\"取消\"\n confirmButtonProps={{ variant: 'destructive-primary' }}\n confirmText=\"刪除\"\n loading={saving}\n modalStatusType=\"error\"\n modalType=\"standard\"\n onCancel={(): void => setDeleteConfirmation(null)}\n onClose={(): void => setDeleteConfirmation(null)}\n onConfirm={(): void => void handleConfirmDelete()}\n open={Boolean(deleteConfirmation)}\n showModalFooter\n showModalHeader\n size=\"regular\"\n supportingText=\"若分類已被模板使用,系統會改為停用分類並保留既有模板關聯。\"\n title=\"刪除分類\"\n >\n <Typography color=\"text-neutral\" variant=\"body\">\n 確定要刪除「{deleteConfirmation?.name ?? ''}」嗎?\n </Typography>\n </Modal>\n </>\n );\n}\n\ninterface CategoryFormInput {\n readonly description: string | null;\n readonly isActive: boolean;\n readonly name: string;\n readonly sortOrder: number;\n}\n\nfunction CategoryModal({\n loading,\n modal,\n onClose,\n onSubmit,\n}: {\n readonly loading: boolean;\n readonly modal: CategoryModalState | null;\n readonly onClose: () => void;\n readonly onSubmit: (input: CategoryFormInput) => Promise<void>;\n}): ReactElement {\n const [description, setDescription] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [name, setName] = useState('');\n const [sortOrder, setSortOrder] = useState('0');\n\n useEffect((): void => {\n if (!modal) {\n return;\n }\n\n setDescription(modal.record?.description ?? '');\n setError(null);\n setName(modal.record?.name ?? '');\n setSortOrder(String(modal.record?.sortOrder ?? 0));\n }, [modal]);\n\n async function handleConfirm(): Promise<void> {\n const trimmedName = name.trim();\n const parsedSortOrder = Number(sortOrder);\n\n if (!trimmedName) {\n setError('請輸入分類名稱');\n return;\n }\n\n if (!Number.isInteger(parsedSortOrder)) {\n setError('排序必須是整數');\n return;\n }\n\n try {\n await onSubmit({\n description: description.trim() || null,\n isActive: modal?.record?.isActive ?? true,\n name: trimmedName,\n sortOrder: parsedSortOrder,\n });\n } catch (submitError: unknown) {\n setError(readErrorMessage(submitError));\n }\n }\n\n return (\n <Modal\n cancelText=\"取消\"\n confirmButtonProps={{ disabled: !name.trim() }}\n confirmText={modal?.type === 'EDIT' ? '儲存' : '建立'}\n loading={loading}\n modalType=\"standard\"\n onCancel={onClose}\n onClose={onClose}\n onConfirm={(): void => void handleConfirm()}\n open={Boolean(modal)}\n showModalFooter\n showModalHeader\n size=\"regular\"\n title={modal?.type === 'EDIT' ? '編輯分類' : '建立分類'}\n >\n <div className={styles.templateModalFields}>\n <BPMFormField label=\"分類名稱\" name=\"categoryName\" required>\n <Input\n autoFocus\n fullWidth\n onChange={(event: ChangeEvent<HTMLInputElement>): void => {\n setName(event.target.value);\n setError(null);\n }}\n placeholder=\"例如:行政管理\"\n value={name}\n variant=\"base\"\n />\n </BPMFormField>\n <BPMFormField label=\"排序\" name=\"categorySortOrder\">\n <Input\n fullWidth\n onChange={(event: ChangeEvent<HTMLInputElement>): void => {\n setSortOrder(event.target.value);\n setError(null);\n }}\n placeholder=\"0\"\n value={sortOrder}\n variant=\"base\"\n />\n </BPMFormField>\n <BPMFormField label=\"說明\" name=\"categoryDescription\">\n <Textarea\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void => {\n setDescription(event.target.value);\n setError(null);\n }}\n placeholder=\"補充分類用途\"\n value={description}\n />\n </BPMFormField>\n </div>\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n </Modal>\n );\n}\n\nfunction CategoryStatusBadge({\n isActive,\n}: {\n readonly isActive: boolean;\n}): ReactElement {\n return isActive ? (\n <Badge size=\"sub\" text=\"啟用\" variant=\"dot-success\" />\n ) : (\n <Badge size=\"sub\" text=\"停用\" variant=\"dot-inactive\" />\n );\n}\n\nfunction readCategoryStatusTabKey(activeKey: Key): CategoryStatusTabKey {\n if (activeKey === 'ACTIVE' || activeKey === 'INACTIVE') {\n return activeKey;\n }\n\n return 'ALL';\n}\n\nfunction readCategoryStatus(\n status: CategoryStatusTabKey,\n): ApprovalTemplateCategoryStatus {\n return status;\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"compose-BtQPBd1Q.js","names":[],"sources":["../../src/views/templates/compose/steps/ComposeFormStep.tsx","../../src/views/templates/compose/steps/ComposeReviewStep.tsx","../../src/views/templates/compose/steps/ComposeWorkflowStep.tsx","../../src/views/templates/compose/use-template-compose-wizard.ts","../../src/views/templates/compose/TemplateComposeWizardView.tsx"],"sourcesContent":["'use client';\n\nimport type { ChangeEvent, ReactElement } from 'react';\nimport { useEffect, useState } from 'react';\nimport { FormField, Input, Select, Typography } from '@mezzanine-ui/react';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport type { SelectValue } from '@mezzanine-ui/react/Select';\nimport type {\n FormDefinitionSchema,\n FormUiSchema,\n} from '@rytass/bpm-core-shared/form';\nimport {\n ApprovalTemplateCategoryRecord,\n listApprovalTemplateCategoriesPage,\n} from '@rytass/bpm-core-client/template';\nimport { FormBuilderView } from '../../../forms/builder';\n\nconst UNCATEGORIZED_OPTION: SelectValue = {\n id: 'UNCATEGORIZED',\n name: '未分類',\n};\n\nconst CATEGORY_PAGE_SIZE = 100;\n\nexport interface ComposeFormStepProps {\n readonly name: string;\n readonly categoryId: string | null;\n readonly formSchema: FormDefinitionSchema;\n readonly formUiSchema: FormUiSchema;\n readonly onNameChange: (value: string) => void;\n readonly onCategoryIdChange: (value: string | null) => void;\n readonly onFormChange: (next: {\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n }) => void;\n}\n\nexport function ComposeFormStep({\n categoryId,\n formSchema,\n formUiSchema,\n name,\n onCategoryIdChange,\n onFormChange,\n onNameChange,\n}: ComposeFormStepProps): ReactElement {\n const [categoryOptions, setCategoryOptions] = useState<\n readonly ApprovalTemplateCategoryRecord[]\n >([]);\n\n useEffect((): (() => void) => {\n let active = true;\n\n void (async (): Promise<void> => {\n try {\n const result = await listApprovalTemplateCategoriesPage({\n page: 1,\n pageSize: CATEGORY_PAGE_SIZE,\n searchText: '',\n status: 'ACTIVE',\n });\n\n if (active) {\n setCategoryOptions(result.categories);\n }\n } catch {\n if (active) {\n setCategoryOptions([]);\n }\n }\n })();\n\n return (): void => {\n active = false;\n };\n }, []);\n\n const selectOptions: SelectValue[] = [\n UNCATEGORIZED_OPTION,\n ...categoryOptions.map((category) => ({\n id: category.id,\n name: category.name,\n })),\n ];\n const selectedOption =\n selectOptions.find((option) => option.id === categoryId) ??\n UNCATEGORIZED_OPTION;\n\n return (\n <>\n <div style={BASICS_GRID_STYLE}>\n <FormField\n label=\"名稱\"\n layout={FormFieldLayout.VERTICAL}\n name=\"composeName\"\n >\n <Input\n fullWidth\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n onNameChange(event.target.value)\n }\n placeholder=\"例如:請款簽核\"\n value={name}\n />\n </FormField>\n <FormField\n label=\"分類(選填)\"\n layout={FormFieldLayout.VERTICAL}\n name=\"composeCategory\"\n >\n <Select\n clearable={false}\n fullWidth\n onChange={(option): void =>\n onCategoryIdChange(\n option && option.id !== UNCATEGORIZED_OPTION.id\n ? option.id\n : null,\n )\n }\n options={selectOptions}\n placeholder=\"未分類\"\n value={selectedOption}\n />\n </FormField>\n </div>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 先設計表單欄位,下一步的流程條件分流即可直接引用這些欄位。\n </Typography>\n <FormBuilderView\n onChange={onFormChange}\n value={{ schema: formSchema, uiSchema: formUiSchema }}\n />\n </>\n );\n}\n\nconst BASICS_GRID_STYLE = {\n display: 'grid',\n gap: 16,\n gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',\n} as const;\n","'use client';\n\nimport type { CSSProperties, ReactElement } from 'react';\nimport { Empty, Table, Tag, Typography } from '@mezzanine-ui/react';\nimport type { TableColumn } from '@mezzanine-ui/core/table';\nimport type {\n FormDefinitionSchema,\n FormUiSchema,\n} from '@rytass/bpm-core-shared/form';\nimport type {\n WorkflowDefinition,\n WorkflowNode,\n} from '@rytass/bpm-core-shared/workflow';\nimport { FormRenderer } from '../../../forms/renderer/FormRendererView';\n\ntype FlowNodeRow = Readonly<\n Record<string, unknown> & {\n detail: string;\n key: string;\n label: string;\n typeLabel: string;\n }\n>;\n\ntype BranchRow = Readonly<\n Record<string, unknown> & {\n condition: string;\n key: string;\n source: string;\n target: string;\n }\n>;\n\nexport interface ComposeReviewStepProps {\n readonly name: string;\n readonly formSchema: FormDefinitionSchema;\n readonly formUiSchema: FormUiSchema;\n readonly workflowDefinition: WorkflowDefinition | null;\n readonly initiatorPolicyCel: string | null;\n readonly publishError: string | null;\n}\n\nexport function ComposeReviewStep({\n formSchema,\n formUiSchema,\n initiatorPolicyCel,\n name,\n publishError,\n workflowDefinition,\n}: ComposeReviewStepProps): ReactElement {\n const nodes = workflowDefinition?.nodes ?? [];\n const edges = workflowDefinition?.edges ?? [];\n const fieldCount = formSchema.fields.length;\n const nodeLabelById = new Map(\n nodes.map((node) => [node.id, node.data.label] as const),\n );\n const nodeRows: FlowNodeRow[] = nodes.map((node) => ({\n detail: readNodeDetail(node) ?? '—',\n key: node.id,\n label: node.data.label,\n typeLabel: readNodeTypeLabel(node),\n }));\n const branchRows: BranchRow[] = edges\n .filter((edge) => Boolean(edge.data.condition))\n .map((edge) => ({\n condition: edge.data.condition ?? '',\n key: edge.id,\n source: nodeLabelById.get(edge.source) ?? edge.source,\n target: nodeLabelById.get(edge.target) ?? edge.target,\n }));\n\n return (\n <div style={STACK_STYLE}>\n <Typography component=\"h2\" style={TITLE_STYLE} variant=\"h2\">\n {name || '(未命名)'}\n </Typography>\n\n <div style={STACK_STYLE}>\n <Typography variant=\"h3\">表單預覽</Typography>\n {fieldCount > 0 ? (\n <FormRenderer\n dataSourceContext={{ kind: 'preview' }}\n readonly\n schema={formSchema}\n uiSchema={formUiSchema}\n />\n ) : (\n <Empty title=\"尚未設計表單欄位\" />\n )}\n </div>\n\n <div style={STACK_STYLE}>\n <Typography variant=\"h3\">流程概覽</Typography>\n {nodes.length > 0 ? (\n <Table columns={NODE_COLUMNS} dataSource={nodeRows} fullWidth />\n ) : (\n <Empty title=\"尚未設計流程節點\" />\n )}\n {branchRows.length > 0 ? (\n <>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 條件分流\n </Typography>\n <Table columns={BRANCH_COLUMNS} dataSource={branchRows} fullWidth />\n </>\n ) : null}\n {initiatorPolicyCel ? (\n <Typography color=\"text-neutral\" variant=\"caption\">\n 發起權限:{initiatorPolicyCel}\n </Typography>\n ) : null}\n </div>\n\n {publishError ? (\n <Typography color=\"text-error\" variant=\"body\">\n {publishError}\n </Typography>\n ) : null}\n </div>\n );\n}\n\nconst NODE_COLUMNS: TableColumn<FlowNodeRow>[] = [\n {\n key: 'type',\n render: (record: FlowNodeRow): ReactElement => (\n <Tag label={record.typeLabel} size=\"sub\" type=\"static\" />\n ),\n title: '類型',\n width: 130,\n },\n { dataIndex: 'label', key: 'label', title: '節點' },\n { dataIndex: 'detail', key: 'detail', title: '簽核方式', width: 160 },\n];\n\nconst BRANCH_COLUMNS: TableColumn<BranchRow>[] = [\n { dataIndex: 'source', key: 'source', title: '來源', width: 180 },\n { dataIndex: 'target', key: 'target', title: '目標', width: 180 },\n { dataIndex: 'condition', key: 'condition', title: '條件' },\n];\n\nfunction readNodeTypeLabel(node: WorkflowNode): string {\n switch (node.type) {\n case 'startEvent':\n return '開始';\n case 'endEvent':\n return node.data.endState === 'REJECTED' ? '結束(駁回)' : '結束(核准)';\n case 'userTask':\n return '簽核';\n case 'serviceTask':\n return '服務';\n case 'exclusiveGateway':\n return '條件閘道';\n case 'parallelGateway':\n return '平行閘道';\n default:\n return '節點';\n }\n}\n\nfunction readNodeDetail(node: WorkflowNode): string | null {\n if (node.type !== 'userTask') {\n return null;\n }\n\n switch (node.data.approverResolver.type) {\n case 'DIRECT':\n return '指定成員';\n case 'POSITION':\n return '指定職位';\n case 'ORG_UNIT_MEMBER':\n return '單位成員';\n case 'ORG_UNIT_POSITION':\n return '單位職位';\n case 'ORG_MANAGER':\n return '主管';\n case 'ORG_MANAGER_CHAIN':\n return '主管鏈';\n case 'ORG_UNIT_MANAGER':\n return '單位主管';\n case 'DYNAMIC_FORM':\n return '依表單欄位指定';\n case 'EXPRESSION':\n return '運算式';\n default:\n return '簽核人';\n }\n}\n\nconst STACK_STYLE: CSSProperties = {\n display: 'grid',\n gap: 12,\n};\n\n// Match the breathing room between the stepper and the PageHeader, which is the\n// Section's top padding token.\nconst TITLE_STYLE: CSSProperties = {\n marginTop: 'var(--mzn-spacing-padding-vertical-spacious)',\n};\n","'use client';\n\nimport type { ReactElement } from 'react';\nimport { Typography } from '@mezzanine-ui/react';\nimport type { FormDefinitionSchema } from '@rytass/bpm-core-shared/form';\nimport type { WorkflowDefinition } from '@rytass/bpm-core-shared/workflow';\nimport { TemplateDesignerView } from '../../designer';\n\nexport interface ComposeWorkflowStepProps {\n readonly formSchema: FormDefinitionSchema;\n readonly workflowDefinition: WorkflowDefinition | null;\n readonly initiatorPolicyCel: string | null;\n readonly onWorkflowChange: (definition: WorkflowDefinition) => void;\n readonly onInitiatorPolicyChange: (cel: string | null) => void;\n /**\n * Show the AI assistant button inside the embedded designer's side panel.\n * Follows the same opt-in contract as the standalone designer page; default\n * hidden. Wired up the stack from the server page's `BPM_AI_ASSISTANT_ENABLED`.\n */\n readonly showAiAssistant?: boolean;\n /**\n * Whether the LLM backend is configured (host has an API key). When `false`\n * the AI button shows disabled as a placeholder. Default `false`.\n */\n readonly aiAssistantAvailable?: boolean;\n}\n\nexport function ComposeWorkflowStep({\n aiAssistantAvailable = false,\n formSchema,\n initiatorPolicyCel,\n onInitiatorPolicyChange,\n onWorkflowChange,\n showAiAssistant = false,\n workflowDefinition,\n}: ComposeWorkflowStepProps): ReactElement {\n return (\n <>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 設計簽核節點與條件分流;條件可直接引用上一步設計的表單欄位。\n </Typography>\n <TemplateDesignerView\n aiAssistantAvailable={aiAssistantAvailable}\n embedded\n formSchemaOverride={formSchema}\n initialInitiatorPolicyCel={initiatorPolicyCel}\n initialWorkflowDefinition={workflowDefinition ?? undefined}\n onInitiatorPolicyChange={onInitiatorPolicyChange}\n onWorkflowChange={onWorkflowChange}\n showAiAssistant={showAiAssistant}\n />\n </>\n );\n}\n","'use client';\n\nimport { useCallback, useState } from 'react';\nimport type {\n FormDefinitionSchema,\n FormUiSchema,\n} from '@rytass/bpm-core-shared/form';\nimport type { WorkflowDefinition } from '@rytass/bpm-core-shared/workflow';\nimport { readFormSchemaLintMessage } from '@rytass/bpm-core-client/form';\nimport {\n ComposeApprovalTemplateWithFormResult,\n composeApprovalTemplateWithForm,\n} from '@rytass/bpm-core-client/template';\n\nexport type ComposeWizardStep = 0 | 1 | 2;\n\nexport type ComposePublishPhase = 'error' | 'idle' | 'submitting' | 'success';\n\nexport const EMPTY_COMPOSE_FORM_SCHEMA: FormDefinitionSchema = {\n fields: [],\n schemaVersion: 1,\n};\n\nexport const EMPTY_COMPOSE_FORM_UI_SCHEMA: FormUiSchema = {\n layout: [],\n schemaVersion: 1,\n};\n\nexport interface TemplateComposeWizard {\n readonly currentStep: ComposeWizardStep;\n /**\n * Single user-facing name. Persisted to both the template and the form\n * (`templateName` / `formName`) at the `composeApprovalTemplateWithForm`\n * boundary — the DB keeps two columns, the UI keeps one field.\n */\n readonly name: string;\n readonly categoryId: string | null;\n readonly formSchema: FormDefinitionSchema;\n readonly formUiSchema: FormUiSchema;\n readonly workflowDefinition: WorkflowDefinition | null;\n readonly initiatorPolicyCel: string | null;\n readonly publishPhase: ComposePublishPhase;\n readonly publishError: string | null;\n readonly canLeaveBasics: boolean;\n readonly goToStep: (step: ComposeWizardStep) => void;\n readonly goNext: () => void;\n readonly goBack: () => void;\n readonly setName: (value: string) => void;\n readonly setCategoryId: (value: string | null) => void;\n readonly setFormValue: (next: {\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n }) => void;\n readonly setWorkflowDefinition: (definition: WorkflowDefinition) => void;\n readonly setInitiatorPolicyCel: (cel: string | null) => void;\n readonly publish: () => Promise<ComposeApprovalTemplateWithFormResult | null>;\n}\n\n/**\n * Owns the cross-step state for the unified \"form + flow\" template wizard.\n *\n * Step 0 (表單) edits `formSchema`/`formUiSchema`; that same in-memory schema\n * feeds Step 1 (流程) as the field source for condition branches, so the\n * embedded designer never needs a published form version. Step 2 (檢視發佈)\n * submits everything through the atomic `composeApprovalTemplateWithForm`\n * mutation.\n */\nexport function useTemplateComposeWizard(): TemplateComposeWizard {\n const [currentStep, setCurrentStep] = useState<ComposeWizardStep>(0);\n const [name, setName] = useState('');\n const [categoryId, setCategoryId] = useState<string | null>(null);\n const [formSchema, setFormSchema] = useState<FormDefinitionSchema>(\n EMPTY_COMPOSE_FORM_SCHEMA,\n );\n const [formUiSchema, setFormUiSchema] = useState<FormUiSchema>(\n EMPTY_COMPOSE_FORM_UI_SCHEMA,\n );\n const [workflowDefinition, setWorkflowDefinitionState] =\n useState<WorkflowDefinition | null>(null);\n const [initiatorPolicyCel, setInitiatorPolicyCelState] = useState<\n string | null\n >(null);\n const [publishPhase, setPublishPhase] = useState<ComposePublishPhase>('idle');\n const [publishError, setPublishError] = useState<string | null>(null);\n\n const canLeaveBasics = name.trim().length > 0 && formSchema.fields.length > 0;\n\n const goToStep = useCallback((step: ComposeWizardStep): void => {\n setCurrentStep(step);\n }, []);\n\n const goNext = useCallback((): void => {\n setCurrentStep((step) =>\n step < 2 ? ((step + 1) as ComposeWizardStep) : step,\n );\n }, []);\n\n const goBack = useCallback((): void => {\n setCurrentStep((step) =>\n step > 0 ? ((step - 1) as ComposeWizardStep) : step,\n );\n }, []);\n\n const setFormValue = useCallback(\n (next: {\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n }): void => {\n setFormSchema(next.schema);\n setFormUiSchema(next.uiSchema);\n },\n [],\n );\n\n const setWorkflowDefinition = useCallback(\n (definition: WorkflowDefinition): void => {\n setWorkflowDefinitionState(definition);\n },\n [],\n );\n\n const setInitiatorPolicyCel = useCallback((cel: string | null): void => {\n setInitiatorPolicyCelState(cel);\n }, []);\n\n const publish =\n useCallback(async (): Promise<ComposeApprovalTemplateWithFormResult | null> => {\n setPublishPhase('submitting');\n setPublishError(null);\n\n try {\n const result = await composeApprovalTemplateWithForm({\n category: null,\n categoryId,\n formDefinitionId: null,\n formDescription: null,\n // One UI name fans out to both persisted columns.\n formName: name,\n initiatorPolicyCel,\n notificationConfig: null,\n publish: true,\n schema: formSchema,\n slaDefaults: null,\n templateDescription: null,\n templateId: null,\n templateName: name,\n uiSchema: formUiSchema,\n workflowDefinition: workflowDefinition ?? EMPTY_WORKFLOW_DEFINITION,\n });\n\n setPublishPhase('success');\n\n return result;\n } catch (requestError: unknown) {\n setPublishError(readErrorMessage(requestError));\n setPublishPhase('error');\n\n return null;\n }\n }, [\n categoryId,\n formSchema,\n formUiSchema,\n initiatorPolicyCel,\n name,\n workflowDefinition,\n ]);\n\n return {\n canLeaveBasics,\n categoryId,\n currentStep,\n formSchema,\n formUiSchema,\n goBack,\n goNext,\n goToStep,\n initiatorPolicyCel,\n name,\n publish,\n publishError,\n publishPhase,\n setCategoryId,\n setFormValue,\n setInitiatorPolicyCel,\n setName,\n setWorkflowDefinition,\n workflowDefinition,\n };\n}\n\nconst EMPTY_WORKFLOW_DEFINITION: WorkflowDefinition = {\n edges: [],\n meta: { schemaVersion: 1 },\n nodes: [\n {\n data: { label: '開始' },\n id: 'start',\n position: { x: 80, y: 160 },\n type: 'startEvent',\n },\n {\n data: { endState: 'APPROVED', label: '完成', triggerMode: 'AND' },\n id: 'end',\n position: { x: 560, y: 160 },\n type: 'endEvent',\n },\n ],\n};\n\nfunction readErrorMessage(error: unknown): string {\n // Publish failures can carry stable `FORM_DATA_SOURCE_*` codes; map them to\n // readable copy and leave every other message untouched.\n return error instanceof Error\n ? readFormSchemaLintMessage(error.message)\n : '發生未知錯誤';\n}\n","'use client';\n\nimport type { ReactElement } from 'react';\nimport {\n Button,\n PageHeader,\n Section,\n SectionGroup,\n Step,\n Stepper,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { useRouterAdapter } from '../../../lib/router-adapter';\nimport { useBPMRoutes } from '../../../lib/routes-config';\nimport { ComposeFormStep } from './steps/ComposeFormStep';\nimport { ComposeReviewStep } from './steps/ComposeReviewStep';\nimport { ComposeWorkflowStep } from './steps/ComposeWorkflowStep';\nimport { useTemplateComposeWizard } from './use-template-compose-wizard';\n\nexport interface TemplateComposeWizardViewProps {\n /**\n * Show the workflow designer AI assistant inside Step 1 (流程設計). Opt-in,\n * default hidden — the server page maps `BPM_AI_ASSISTANT_ENABLED` to it.\n */\n readonly showAiAssistant?: boolean;\n /**\n * Whether the LLM backend is configured (host has an API key). When `false`\n * the AI button shows disabled as a placeholder. Default `false`.\n */\n readonly aiAssistantAvailable?: boolean;\n}\n\n/**\n * Unified \"form + flow\" template creation wizard. Walks the user through\n * Step 0 表單設計 → Step 1 流程設計 → Step 2 檢視並發佈, then commits both\n * sides atomically through `composeApprovalTemplateWithForm`. This is the\n * template entry point; the form builder is intentionally controlled here\n * instead of exposed as a standalone `/forms` page.\n */\nexport function TemplateComposeWizardView({\n aiAssistantAvailable = false,\n showAiAssistant = false,\n}: TemplateComposeWizardViewProps = {}): ReactElement {\n const router = useRouterAdapter();\n const routes = useBPMRoutes();\n const wizard = useTemplateComposeWizard();\n const submitting = wizard.publishPhase === 'submitting';\n\n async function handlePublish(): Promise<void> {\n const result = await wizard.publish();\n\n if (result) {\n router.push(routes.templateDesigner(result.templateId));\n }\n }\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"一次完成表單與流程設計,發佈後即可直接發起。\"\n title=\"建立模板(表單 + 流程)\"\n >\n <Button\n disabled={submitting}\n onClick={(): void => router.push(routes.templates())}\n variant=\"base-secondary\"\n >\n 返回模板列表\n </Button>\n </ContentHeader>\n </PageHeader>\n\n <SectionGroup>\n <Section>\n <Stepper currentStep={wizard.currentStep}>\n <Step description=\"設計欄位與版面\" title=\"表單設計\" />\n <Step description=\"設計簽核節點與條件\" title=\"流程設計\" />\n <Step description=\"確認後發佈\" title=\"檢視並發佈\" />\n </Stepper>\n\n {wizard.currentStep === 0 ? (\n <ComposeFormStep\n categoryId={wizard.categoryId}\n formSchema={wizard.formSchema}\n formUiSchema={wizard.formUiSchema}\n name={wizard.name}\n onCategoryIdChange={wizard.setCategoryId}\n onFormChange={wizard.setFormValue}\n onNameChange={wizard.setName}\n />\n ) : null}\n\n {wizard.currentStep === 1 ? (\n <ComposeWorkflowStep\n aiAssistantAvailable={aiAssistantAvailable}\n formSchema={wizard.formSchema}\n initiatorPolicyCel={wizard.initiatorPolicyCel}\n onInitiatorPolicyChange={wizard.setInitiatorPolicyCel}\n onWorkflowChange={wizard.setWorkflowDefinition}\n showAiAssistant={showAiAssistant}\n workflowDefinition={wizard.workflowDefinition}\n />\n ) : null}\n\n {wizard.currentStep === 2 ? (\n <ComposeReviewStep\n formSchema={wizard.formSchema}\n formUiSchema={wizard.formUiSchema}\n initiatorPolicyCel={wizard.initiatorPolicyCel}\n name={wizard.name}\n publishError={wizard.publishError}\n workflowDefinition={wizard.workflowDefinition}\n />\n ) : null}\n\n <div style={FOOTER_STYLE}>\n {wizard.currentStep === 0 && !wizard.canLeaveBasics ? (\n <Typography color=\"text-neutral\" variant=\"caption\">\n 請填寫模板名稱、表單名稱,並至少新增一個表單欄位。\n </Typography>\n ) : (\n <span />\n )}\n <div style={FOOTER_ACTIONS_STYLE}>\n <Button\n disabled={wizard.currentStep === 0 || submitting}\n onClick={wizard.goBack}\n variant=\"base-secondary\"\n >\n 上一步\n </Button>\n {wizard.currentStep < 2 ? (\n <Button\n disabled={wizard.currentStep === 0 && !wizard.canLeaveBasics}\n onClick={wizard.goNext}\n variant=\"base-primary\"\n >\n 下一步\n </Button>\n ) : (\n <Button\n disabled={submitting}\n loading={submitting}\n onClick={(): void => {\n void handlePublish();\n }}\n variant=\"base-primary\"\n >\n 發佈\n </Button>\n )}\n </div>\n </div>\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FOOTER_STYLE = {\n alignItems: 'center',\n display: 'flex',\n // Match the breathing room used elsewhere (the Section's top padding token).\n marginTop: 'var(--mzn-spacing-padding-vertical-spacious)',\n justifyContent: 'space-between',\n gap: 16,\n} as const;\n\nconst FOOTER_ACTIONS_STYLE = {\n display: 'flex',\n gap: 8,\n} as const;\n"],"mappings":"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"}
|