From b5c518fd4b21b0b6c32c92de975596aef0b9a025 Mon Sep 17 00:00:00 2001 From: "bai.zixv" Date: Fri, 17 May 2024 20:50:25 +0800 Subject: [PATCH] feat: init support mobile approvals (#1002) Co-authored-by: wjh Co-authored-by: sealday Reviewed-on: https://git.daoyoucloud.com/daoyoucloud/tachybase/pulls/1002 Co-authored-by: bai.zixv Co-committed-by: bai.zixv --- .../@hera/plugin-approval-mobile/package.json | 5 +- .../approval/ApprovalBlockInitializer.tsx | 17 +- .../src/client/approval/InitiationsBlock.tsx | 65 ------ .../component/ApprovalProcess.view.tsx | 117 +++++++++++ .../approval/component/TabApprovalItem.tsx | 70 ------- .../src/client/approval/constants.ts | 108 ++++++++++ .../approval/context/ApprovalExecution.tsx | 8 + .../src/client/approval/context/FormBlock.tsx | 76 +++++++ .../approval/context/SchemaComponent.tsx | 11 ++ .../approval/context/WithActionEnabled.tsx | 3 + .../src/client/approval/index.ts | 14 +- .../approval/initiations/InitiationsBlock.tsx | 65 ++++++ .../initiations/UserInitiationsBlock.tsx | 35 ++++ .../initiations/component/ApprovalItem.tsx | 90 +++++++++ .../component/ApprovalReachDataType.tsx | 31 +++ .../initiations/component/ApprovalStatus.tsx | 43 ++++ .../component/ApprovalTemplateType.tsx | 37 ++++ .../ViewActionUserInitiationsContent.tsx | 175 ++++++++++++++++ .../config/LauncherActionConfig.tsx | 28 +++ .../config/LauncherActionConfigComponent.tsx | 31 +++ .../initiations/hook/useDestroyAction.tsx | 27 +++ .../initiations/hook/useFormBlockProps.tsx | 32 +++ .../approval/initiations/hook/useSubmit.tsx | 42 ++++ .../initiations/hook/useWithdrawAction.tsx | 31 +++ .../initiations/provider/ActionBar.tsx | 23 +++ .../provider/ApplyActionStatus.tsx | 28 +++ .../initiations/provider/WithdrawAction.tsx | 18 ++ .../src/client/approval/locale.tsx | 35 ++-- .../src/client/approval/style/style.css | 21 ++ .../approval/{ => todos}/TodosBlock.tsx | 0 .../component/TabApplicantType.tsx | 0 .../todos/component/TabApprovalItem.tsx | 100 ++++++++++ .../{ => todos}/component/TabApprovalType.tsx | 0 .../component/TabBatchProcessingType.tsx | 0 .../component/TabReachDataType.tsx | 0 .../{ => todos}/component/TabReadingType.tsx | 0 .../{ => todos}/component/TabTemplateType.tsx | 0 .../component/ViewActionTodosContent.tsx | 186 ++++++++++++++++++ .../hook/useApprovalDetailBlockProps.tsx | 14 ++ .../todos/hook/useApprovalFormBlockProps.tsx | 6 + .../approval/todos/hook/useDestroyAction.tsx | 27 +++ .../approval/todos/hook/useFormBlockProps.tsx | 30 +++ .../client/approval/todos/hook/useSubmit.tsx | 37 ++++ .../approval/todos/interface/interface.ts | 146 ++++++++++++++ .../todos/provider/ActionBarProvider.tsx | 29 +++ .../todos/provider/ApplyActionStatus.tsx | 27 +++ .../todos/provider/ApprovalAction.tsx | 23 +++ .../todos/provider/ApprovalFormBlock.tsx | 85 ++++++++ .../todos/provider/WithdrawAction.tsx | 19 ++ .../src/client/index.tsx | 11 +- .../src/locale/en-US.json | 65 ++++++ .../src/locale/zh-CN.json | 80 ++++++++ .../plugin-approval/src/server/actions.ts | 5 +- .../@hera/plugin-homepage/package.json | 1 + .../@hera/plugin-homepage/src/client/Home.tsx | 2 +- .../src/client/core/index.tsx | 2 + .../plugin-mobile-client/src/client/index.tsx | 2 + pnpm-lock.yaml | 16 +- 58 files changed, 2040 insertions(+), 159 deletions(-) delete mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/InitiationsBlock.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/ApprovalProcess.view.tsx delete mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApprovalItem.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/constants.ts create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/ApprovalExecution.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/FormBlock.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/SchemaComponent.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/WithActionEnabled.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/InitiationsBlock.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/UserInitiationsBlock.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalItem.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalReachDataType.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalStatus.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalTemplateType.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ViewActionUserInitiationsContent.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfig.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfigComponent.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useDestroyAction.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useFormBlockProps.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useSubmit.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useWithdrawAction.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ActionBar.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ApplyActionStatus.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/WithdrawAction.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/style/style.css rename packages/plugins/@hera/plugin-approval-mobile/src/client/approval/{ => todos}/TodosBlock.tsx (100%) rename packages/plugins/@hera/plugin-approval-mobile/src/client/approval/{ => todos}/component/TabApplicantType.tsx (100%) create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApprovalItem.tsx rename packages/plugins/@hera/plugin-approval-mobile/src/client/approval/{ => todos}/component/TabApprovalType.tsx (100%) rename packages/plugins/@hera/plugin-approval-mobile/src/client/approval/{ => todos}/component/TabBatchProcessingType.tsx (100%) rename packages/plugins/@hera/plugin-approval-mobile/src/client/approval/{ => todos}/component/TabReachDataType.tsx (100%) rename packages/plugins/@hera/plugin-approval-mobile/src/client/approval/{ => todos}/component/TabReadingType.tsx (100%) rename packages/plugins/@hera/plugin-approval-mobile/src/client/approval/{ => todos}/component/TabTemplateType.tsx (100%) create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/ViewActionTodosContent.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalDetailBlockProps.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalFormBlockProps.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useDestroyAction.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useFormBlockProps.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useSubmit.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/interface/interface.ts create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ActionBarProvider.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApplyActionStatus.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalAction.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalFormBlock.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/WithdrawAction.tsx create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/locale/en-US.json create mode 100644 packages/plugins/@hera/plugin-approval-mobile/src/locale/zh-CN.json diff --git a/packages/plugins/@hera/plugin-approval-mobile/package.json b/packages/plugins/@hera/plugin-approval-mobile/package.json index c442cbde3..44c8e08f5 100644 --- a/packages/plugins/@hera/plugin-approval-mobile/package.json +++ b/packages/plugins/@hera/plugin-approval-mobile/package.json @@ -6,15 +6,18 @@ "@ant-design/icons": "5.x", "@tachybase/schema": "workspace:*", "@types/lodash": "^4.17.0", + "ahooks": "^3.7.2", "antd": "5.16.1", "antd-mobile": "^5.35.0", "antd-mobile-icons": "^0.3.0", "classnames": "^2.3.1", "lodash": "4.17.21", - "react-i18next": "^11.15.1" + "react-i18next": "^11.15.1", + "react-router-dom": "6.x" }, "peerDependencies": { "@tachybase/client": "workspace:*", + "@tachybase/plugin-workflow": "workspace:*", "@tachybase/server": "workspace:*", "@tachybase/test": "workspace:*", "@tachybase/utils": "workspace:*" diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/ApprovalBlockInitializer.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/ApprovalBlockInitializer.tsx index 299339d4d..edd82a3f0 100644 --- a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/ApprovalBlockInitializer.tsx +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/ApprovalBlockInitializer.tsx @@ -37,10 +37,23 @@ export const ApprovalBlockInitializer = () => { export const ApprovalInitializerItem = [ { - type: 'item', + type: 'itemGroup', name: 'initiations', title: '发起', - itemComponent: 'InitiationsBlock', + children: [ + { + type: 'item', + name: 'initiationsApproval', + title: '发起申请', + itemComponent: 'InitiationsBlock', + }, + { + type: 'item', + name: 'currApproval', + title: '我发起的', + itemComponent: 'UserInitiationsBlock', + }, + ], }, { type: 'item', diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/InitiationsBlock.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/InitiationsBlock.tsx deleted file mode 100644 index 4aedd73f8..000000000 --- a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/InitiationsBlock.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { BlockItem, css } from '@tachybase/client'; -import React from 'react'; -import { AutoCenter, Card, SearchBar, Selector, Space } from 'antd-mobile'; -import { TeamFill } from 'antd-mobile-icons'; - -export const InitiationsBlock = () => { - return ( - -
- - - -
- -
- 11111 -
- ), - }, - { - value: '2', - label: ( -
-
- -
- 11111 -
- ), - }, - { - value: '3', - label: ( -
-
- -
- 11111 -
- ), - }, - ]} - /> - - -
- ); -}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/ApprovalProcess.view.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/ApprovalProcess.view.tsx new file mode 100644 index 000000000..da14d636b --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/ApprovalProcess.view.tsx @@ -0,0 +1,117 @@ +import { createStyles, useCurrentUserContext } from '@tachybase/client'; +import { EXECUTION_STATUS } from '@tachybase/plugin-workflow/client'; +import _ from 'lodash'; +import React, { useMemo } from 'react'; +import { APPROVAL_ACTION_STATUS, APPROVAL_STATUS } from '../constants'; +import { lang, usePluginTranslation } from '../locale'; +import { useContextApprovalExecution } from '../context/ApprovalExecution'; +import { ContextWithActionEnabled } from '../context/WithActionEnabled'; + +// 审批(发起/待办)区块-查看-审批处理 +export const ApprovalProcess = (props) => { + const { t } = usePluginTranslation(); + const { approval: approvalContext } = useContextApprovalExecution(); + const { styles } = getStyles(); + const { data } = useCurrentUserContext(); + + const results = useMemo(() => getResults({ approval: approvalContext, currentUser: data }), [approvalContext, data]); + + // const columns = useMemo(() => getAntdTableColumns({ t, styles }), [t, styles]); + + return ( + + {/* + {results.map((item) => ( + + ))} + */} + + ); +}; + +const getStyles = createStyles(({ css, token }) => ({ + layout: css` + display: flex; + `, + columnDetail: css` + .ant-description-textarea { + margin-bottom: 0.5em; + } + time { + display: block; + color: ${token.colorTextTertiary}; + } + `, +})); + +function getResults({ approval, currentUser }) { + const { workflow, approvalExecutions, records } = approval; + approvalExecutions.sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt)); + const approvalExecution = approvalExecutions.reduce( + (newObj, curr) => + Object.assign(newObj, { + [curr.id]: Object.assign(curr, { + records: [ + { + groupCount: 1, + node: { + title: lang('Apply'), + }, + user: { + ...approval.createdBy, + id: approval.createdById, + }, + status: curr.status ? APPROVAL_ACTION_STATUS.SUBMITTED : approval.status, + updatedAt: curr.createdAt, + execution: { ...curr }, + }, + ], + }), + }), + {}, + ); + + records + .sort((prevRecord, nextRecord) => { + const prev = new Date(prevRecord.job?.createdAt); + const next = new Date(nextRecord.job?.createdAt); + return prev < next ? -1 : prev > next ? 1 : prevRecord.id - nextRecord.id; + }) + .forEach((record) => { + const approvalExecutionId = approvalExecution[record.approvalExecutionId]; + const omitApprovalExecutionId = _.omit(approvalExecutionId, ['records']); + (record.workflow = workflow), + (record.execution = { ...omitApprovalExecutionId }), + approvalExecutionId.records.push(record), + approvalExecutionId.jobs || (approvalExecutionId.jobs = {}), + approvalExecutionId.jobs[record.jobId] + ? (approvalExecutionId.jobs[record.jobId].first.groupCount += 1) + : ((approvalExecutionId.jobs[record.jobId] = { first: record }), + (record.groupCount = 1), + (record.statusCount = { [APPROVAL_ACTION_STATUS.APPROVED]: 0, [APPROVAL_ACTION_STATUS.REJECTED]: 0 })), + [APPROVAL_ACTION_STATUS.APPROVED, APPROVAL_ACTION_STATUS.REJECTED].includes(record.status) && + (approvalExecutionId.jobs[record.jobId].first.statusCount[record.status] += 1); + }), + approval.createdById === (currentUser == null ? void 0 : currentUser.data.id) && + approvalExecutions.forEach((approvalExecution) => { + approvalExecution.status === EXECUTION_STATUS.CANCELED && + approvalExecution.records.length === 1 && + ((approvalExecution.records[0].groupCount = 2), + approvalExecution.records.push({ + user: { nickname: approval.createdBy.nickname }, + status: APPROVAL_STATUS.WITHDRAWN, + updatedAt: approvalExecution.updatedAt, + })); + }); + const aELength = approvalExecutions.length; + return approvalExecutions.filter( + (approvalExecution, index) => + (aELength - 1 === index && + (!approvalExecution.status || approvalExecution.status === EXECUTION_STATUS.CANCELED)) || + approvalExecution.records.length > 1, + ); +} + +const getStepsResult = (result) => { + const stepData = result.map((value) => {}); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApprovalItem.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApprovalItem.tsx deleted file mode 100644 index 8d5c2cabe..000000000 --- a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApprovalItem.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { connect, useFieldSchema } from '@tachybase/schema'; -import { Badge, Empty, List, Space, Tag } from 'antd-mobile'; -import React from 'react'; - -export const TabApprovalItem = () => { - const fieldSchema = useFieldSchema(); - const props = fieldSchema['x-component-props']; - const blockData = props.approvalKey === 'duplicate' ? [] : data; - - return ( -
- {blockData.length ? ( - - {blockData.map((item, index) => { - return ( - - - - {item.title} - - 审批中 - - - - xxxx:{item.context} - xxxx:{item.price} - xxxx:{item.context} - - ); - })} - - ) : ( - - )} -
- ); -}; - -const data = [ - { - title: 'xx-xxxxx的申请', - context: 'xxxxxxxxx', - price: 'xxxxxxx', - type: '1', - status: '1', - date: '2024-01-08', - read: false, - applicantId: 9, - }, - { - title: 'xx-xxxxx的申请', - context: 'xxxxxxxxx', - price: 'xxxxxxx', - type: '2', - status: '2', - date: '2024-01-09', - read: true, - applicantId: 10, - }, - { - title: 'xx-xxxxx的申请', - context: 'xxxxxxxxx', - price: 'xxxxxxx', - type: '3', - status: '3', - date: '2024-01-010', - read: false, - applicantId: 11, - }, -]; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/constants.ts b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/constants.ts new file mode 100644 index 000000000..4272da751 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/constants.ts @@ -0,0 +1,108 @@ +import { JOB_STATUS } from '@tachybase/plugin-workflow/client'; +import { NAMESPACE, lang, tval } from './locale'; + +/**显示状态 */ +export const APPROVAL_STATUS = { + /**已分配 */ + ASSIGNED: null, + /** 待处理*/ + PENDING: 0, + /**已退回 */ + RETURNED: 1, + /**已通过 */ + APPROVED: 2, + /**已拒绝 */ + REJECTED: -1, + /**取消 */ + CANCELED: -2, + /**撤回 */ + WITHDRAWN: -3, +}; +export const approvalStatusOptions = [ + { value: APPROVAL_STATUS.ASSIGNED, label: `Assigned`, color: 'blue' }, + { value: APPROVAL_STATUS.PENDING, label: `Pending`, color: 'gold' }, + { value: APPROVAL_STATUS.RETURNED, label: `Returned`, color: 'purple' }, + { value: APPROVAL_STATUS.APPROVED, label: `Approved`, color: 'green' }, + { value: APPROVAL_STATUS.REJECTED, label: `Rejected`, color: 'red' }, + { value: APPROVAL_STATUS.WITHDRAWN, label: `Withdrawn` }, +]; + +/**行为状态 */ +export const APPROVAL_ACTION_STATUS = { + /** 0:草稿 */ + DRAFT: 0, + /** 1:已退回 */ + RETURNED: 1, + /** 2:提交 */ + SUBMITTED: 2, + /** 3:处理中 */ + PROCESSING: 3, + /** 4:已完结 */ + APPROVED: 4, + /** -1:拒收 */ + REJECTED: -1, +}; + +export const ApprovalStatusEnums = [ + { value: APPROVAL_ACTION_STATUS.DRAFT, label: `Draft`, editable: true }, + { + value: APPROVAL_ACTION_STATUS.RETURNED, + label: `Returned`, + color: 'purple', + editable: true, + }, + { value: APPROVAL_ACTION_STATUS.SUBMITTED, label: `Submitted`, color: 'cyan' }, + { value: APPROVAL_ACTION_STATUS.PROCESSING, label: `Processing`, color: 'gold' }, + { value: APPROVAL_ACTION_STATUS.APPROVED, label: `Approved`, color: 'green' }, + { value: APPROVAL_ACTION_STATUS.REJECTED, label: `Rejected`, color: 'red' }, +]; + +export const ApprovalPriorityType = [ + { value: '1', label: '一般', color: 'cyan' }, + { value: '2', label: '紧急', color: 'gold' }, + { value: '3', label: '非常紧急', color: 'red' }, +]; + +export const ApprovalStatusEnumDict = ApprovalStatusEnums.reduce((e, t) => Object.assign(e, { [t.value]: t }), {}); +export const JobStatusEnums = { + [JOB_STATUS.PENDING]: { color: 'gold', label: `Pending` }, + [JOB_STATUS.RESOLVED]: { color: 'green', label: `Approved` }, + [JOB_STATUS.REJECTED]: { color: 'red', label: `Rejected` }, + [JOB_STATUS.RETRY_NEEDED]: { color: 'red', label: `Returned` }, +}; +export const VoteCategory = { SINGLE: Symbol('single'), ALL: Symbol('all'), VOTE: Symbol('vote') }; +export const VoteCategoryEnums = [ + { value: VoteCategory.SINGLE, label: `Or"` }, + { value: VoteCategory.ALL, label: `And"` }, + { + value: VoteCategory.VOTE, + label: (v: number) => `${lang('Voting')} ( > ${(v * 100).toFixed(0)}%)`, + }, +].reduce((obj, vote) => Object.assign(obj, { [vote.value]: vote }), {}); +export function voteOption(value: number) { + switch (true) { + case value === 1: + return VoteCategory.ALL; + case 0 < value && value < 1: + return VoteCategory.VOTE; + default: + return VoteCategory.SINGLE; + } +} +export function flatSchemaArray(sourceData, filter, needRecursion = false) { + const flatArray = []; + if (!sourceData) { + return flatArray; + } + + if (filter(sourceData) && (!needRecursion || !sourceData.properties)) { + flatArray.push(sourceData); + } else { + sourceData.properties && + Object.keys(sourceData.properties).forEach((key) => { + flatArray.push(...flatSchemaArray(sourceData.properties[key], filter)); + }); + } + + return flatArray; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/ApprovalExecution.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/ApprovalExecution.tsx new file mode 100644 index 000000000..cd4584a7d --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/ApprovalExecution.tsx @@ -0,0 +1,8 @@ +import { createContext, useContext } from 'react'; +import { ApprovalExecution } from '../todos/interface/interface'; + +export const ContextApprovalExecution = createContext>({}); + +export function useContextApprovalExecution() { + return useContext(ContextApprovalExecution); +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/FormBlock.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/FormBlock.tsx new file mode 100644 index 000000000..ced89861b --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/FormBlock.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { + BlockRequestContext_deprecated, + CollectionProvider_deprecated, + FormActiveFieldsProvider, + FormBlockContext, + FormProvider, + FormV2, + RecordProvider, + useAPIClient, + useAssociationNames, + useDesignable, +} from '@tachybase/client'; +import { RecursionField, createForm, useField, useFieldSchema } from '@tachybase/schema'; +import { Fragment, useContext, useMemo, useRef } from 'react'; +import { useContextApprovalExecution } from './ApprovalExecution'; + +export const FormBlockProvider = (props) => { + const { approvalExecution } = useContextApprovalExecution(); + const { snapshot } = approvalExecution; + const fieldSchema = useFieldSchema(); + const field = useField(); + const formBlockRef = useRef(null); + const { getAssociationAppends } = useAssociationNames(); + const { appends, updateAssociationValues } = getAssociationAppends(); + // @ts-ignore + const { findComponent } = useDesignable(); + const ContainerFormComp = findComponent(field.component?.[0]) || Fragment; + const form = useMemo(() => createForm({ initialValues: snapshot }), [snapshot]); + const params = useMemo(() => ({ ...appends, ...props.params }), [appends, props.params]); + const service = useMemo(() => ({ loading: false, data: { data: snapshot } }), [snapshot]); + const collectionResource = useAPIClient().resource(props.collection); + const blockContext = useContext(BlockRequestContext_deprecated); + const formValue = useMemo( + () => ({ + params, + form, + field, + service, + updateAssociationValues, + formBlockRef, + }), + [field, form, params, service, updateAssociationValues], + ); + + return ( + + {/* @ts-ignore */} + + + + + + + +
+ +
+
+
+
+
+
+
+
+ ); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/SchemaComponent.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/SchemaComponent.tsx new file mode 100644 index 000000000..19bf99c11 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/SchemaComponent.tsx @@ -0,0 +1,11 @@ +import { SchemaComponentContext, useSchemaComponentContext } from '@tachybase/client'; +import React from 'react'; + +export function SchemaComponentContextProvider({ designable, children }) { + const schemaComponentContext = useSchemaComponentContext(); + return ( + + {children} + + ); +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/WithActionEnabled.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/WithActionEnabled.tsx new file mode 100644 index 000000000..c55c50237 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/context/WithActionEnabled.tsx @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const ContextWithActionEnabled = createContext({ actionEnabled: false }); diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/index.ts b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/index.ts index 38d0e06cb..8429961e7 100644 --- a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/index.ts +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/index.ts @@ -1,8 +1,13 @@ import { Plugin } from '@tachybase/client'; import { ApprovalBlockInitializer } from './ApprovalBlockInitializer'; import { ApprovalSettings } from './ApprovalSettings'; -import { TodosBlock } from './TodosBlock'; -import { InitiationsBlock } from './InitiationsBlock'; +import { TodosBlock } from './todos/TodosBlock'; +import { InitiationsBlock } from './initiations/InitiationsBlock'; +import { LauncherActionConfigInitializer } from './initiations/config/LauncherActionConfig'; +import { ViewActionTodosContent } from './todos/component/ViewActionTodosContent'; +import { UserInitiationsBlock } from './initiations/UserInitiationsBlock'; +import { ViewActionUserInitiationsContent } from './initiations/component/ViewActionUserInitiationsContent'; +import { ApprovalProcess } from './component/ApprovalProcess.view'; class PluginApproval extends Plugin { async load() { @@ -10,6 +15,10 @@ class PluginApproval extends Plugin { ApprovalBlockInitializer, InitiationsBlock, TodosBlock, + UserInitiationsBlock, + ViewActionTodosContent, + ViewActionUserInitiationsContent, + 'ApprovalCommon.ViewComponent.MApprovalProcess': ApprovalProcess, }); this.app.schemaSettingsManager.add(ApprovalSettings); this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'otherBlocks.approval', { @@ -18,6 +27,7 @@ class PluginApproval extends Plugin { type: 'item', Component: 'ApprovalBlockInitializer', }); + this.app.schemaInitializerManager.add(LauncherActionConfigInitializer); } } diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/InitiationsBlock.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/InitiationsBlock.tsx new file mode 100644 index 000000000..d39111047 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/InitiationsBlock.tsx @@ -0,0 +1,65 @@ +import { BlockItem, css, useAPIClient, useRequest } from '@tachybase/client'; +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { AutoCenter, Card, SearchBar, Selector, Space } from 'antd-mobile'; +import { TeamFill } from 'antd-mobile-icons'; + +export const InitiationsBlock = () => { + const [options, setOptions] = useState([]); + const api = useAPIClient(); + const navigate = useNavigate(); + useEffect(() => { + api + .request({ + url: 'workflows:list', + params: { pageSize: 9999, filter: { type: { $eq: 'approval' }, enabled: { $eq: true } } }, + }) + .then((res) => { + const option = res?.data?.data.map((value) => { + return { + ...value, + value: value.id, + label: ( +
{ + navigate(`/mobile/${value.config.collection}/approval/${value.id}/page`); + }} + > +
+ +
+ {value.title?.replace('审批流:', '') || ''} +
+ ), + }; + }); + setOptions(option); + }) + .catch(() => {}); + }, []); + return ( + +
+ + + + +
+
+ ); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/UserInitiationsBlock.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/UserInitiationsBlock.tsx new file mode 100644 index 000000000..b063c4776 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/UserInitiationsBlock.tsx @@ -0,0 +1,35 @@ +import { BlockItem, SchemaComponent, css } from '@tachybase/client'; +import { Divider, SearchBar, Space, Tabs } from 'antd-mobile'; +import React, { useState } from 'react'; +import { useFieldSchema } from '@tachybase/schema'; +import { ApprovalTemplateType } from './component/ApprovalTemplateType'; +import { ApprovalStatus } from './component/ApprovalStatus'; +import { ApprovalItem } from './component/ApprovalItem'; +import { ApprovalReachDataType } from './component/ApprovalReachDataType'; + +export const UserInitiationsBlock = () => { + const fieldSchema = useFieldSchema(); + fieldSchema['x-component-props'] = fieldSchema['x-component-props']?.['approvalKey'] + ? fieldSchema['x-component-props'] + : (() => { + fieldSchema['x-component-props']['approvalKey'] = 'pending'; + return fieldSchema['x-component-props']; + })(); + const props = fieldSchema['x-component-props']; + return ( + + + + {/* 模版类型 */} + + {/* 到达日期 */} + + {/* 审批状态 */} + + + + + ); +}; + +const spaceStyle = { width: '100%', fontSize: '10px', color: '#8e8e8e' }; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalItem.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalItem.tsx new file mode 100644 index 000000000..c35b5ee5e --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalItem.tsx @@ -0,0 +1,90 @@ +import { useAPIClient, useCollectionManager, useCurrentUserContext } from '@tachybase/client'; +import { useFieldSchema } from '@tachybase/schema'; +import { Badge, Empty, List, Space, Tag } from 'antd-mobile'; +import React, { useState } from 'react'; +import { useAsyncEffect } from 'ahooks'; +import { ApprovalPriorityType, ApprovalStatusEnums } from '../../constants'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from '../../locale'; + +export const ApprovalItem = () => { + const fieldSchema = useFieldSchema(); + const props = fieldSchema['x-component-props']; + const cm = useCollectionManager(); + const api = useAPIClient(); + const [data, setData] = useState([]); + const { t } = useTranslation(); + const navigate = useNavigate(); + const user = useCurrentUserContext(); + useAsyncEffect(async () => { + api + .request({ + url: 'approvals:listCentralized', + params: { pageSize: 99999, appends: ['workflow'] }, + }) + .then((res) => { + const result = res.data?.data.map((item) => { + const priorityType = ApprovalPriorityType.find((priorityItem) => priorityItem.value === item.data.priority); + const statusType = approvalTodoListStatus(item, t); + const categoryTitle = item.workflow.title.replace('审批流:', ''); + + return { + ...item, + title: `${user.data.data.nickname}的${categoryTitle}`, + categoryTitle: categoryTitle, + statusTitle: t(statusType.label), + statusColor: statusType.color, + reason: item.data.reason || item.data.reason_pay, + priorityTitle: priorityType.label, + priorityColor: priorityType.color, + }; + }); + result.sort((a, b) => { + return Date.parse(b.createdAt) - Date.parse(a.createdAt); + }); + setData(result); + }) + .catch(() => { + console.error; + }); + }, props); + + return ( +
+ {data.length ? ( + + {data.map((item, index) => { + return ( + { + navigate(`/mobile/approval/${item.id}/page`); + }} + > + + + {item.title} + + {item.statusTitle} + + + {item.priorityTitle} + + + + 事由:{item.reason} + + ); + })} + + ) : ( + + )} +
+ ); +}; + +const approvalTodoListStatus = (item, t) => { + const { status } = item; + return ApprovalStatusEnums.find((value) => value.value === status); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalReachDataType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalReachDataType.tsx new file mode 100644 index 000000000..799e0a966 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalReachDataType.tsx @@ -0,0 +1,31 @@ +import { Picker, Space } from 'antd-mobile'; +import { DownOutline } from 'antd-mobile-icons'; +import React, { useState } from 'react'; + +export const ApprovalReachDataType = () => { + const [visible, setVisible] = useState(false); + return ( + <> + + 到达日期 + + + { + setVisible(false); + }} + /> + + ); +}; + +const columns = [ + [ + { label: '全部', value: '1' }, + { label: '近7日', value: '2' }, + { label: '近30日', value: '3' }, + { label: '自定义区间', value: '4' }, + ], +]; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalStatus.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalStatus.tsx new file mode 100644 index 000000000..909113efb --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalStatus.tsx @@ -0,0 +1,43 @@ +import { Picker, Space } from 'antd-mobile'; +import { DownOutline } from 'antd-mobile-icons'; +import React, { useState } from 'react'; +import { useTranslation } from '../../locale'; + +export const ApprovalStatus = () => { + const [visible, setVisible] = useState(false); + const { t } = useTranslation(); + return ( + <> + { + setVisible(true); + }} + > + 申请状态 + + + { + return { ...value, label: t(value.label) }; + }), + ]} + visible={visible} + onClose={() => { + setVisible(false); + }} + /> + + ); +}; + +const columns = [ + [ + { label: 'Draft', value: '0' }, + { label: 'Returned', value: '1' }, + { label: 'Submitted', value: '2' }, + { label: 'Processing', value: '3' }, + { label: 'Approved', value: '4' }, + { label: 'Rejected', value: '-1' }, + ], +]; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalTemplateType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalTemplateType.tsx new file mode 100644 index 000000000..aed96a64b --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ApprovalTemplateType.tsx @@ -0,0 +1,37 @@ +import { Picker, Space } from 'antd-mobile'; +import { DownOutline } from 'antd-mobile-icons'; +import React, { useState } from 'react'; + +export const ApprovalTemplateType = () => { + const [visible, setVisible] = useState(false); + return ( + <> + { + setVisible(true); + }} + > + 模版类型 + + + { + setVisible(false); + }} + /> + + ); +}; + +const columns = [ + [ + { label: '全部', value: '0' }, + { label: '入职申请', value: '1' }, + { label: '转正申请', value: '2' }, + { label: '调用申请', value: '3' }, + { label: '离职申请', value: '4' }, + { label: 'xxx申请', value: '5' }, + ], +]; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ViewActionUserInitiationsContent.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ViewActionUserInitiationsContent.tsx new file mode 100644 index 000000000..023964d07 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/component/ViewActionUserInitiationsContent.tsx @@ -0,0 +1,175 @@ +import React, { useEffect, useState } from 'react'; +import { + RemoteSchemaComponent, + SchemaComponent, + SchemaComponentProvider, + useAPIClient, + useDestroyAction, + useFormBlockContext, + useRecord, + useRequest, +} from '@tachybase/client'; +import { DetailsBlockProvider, FlowContext } from '@tachybase/plugin-workflow/client'; +import { useForm } from '@tachybase/schema'; +import { Result, Spin } from 'antd'; +import { useContext } from 'react'; +import { ContextWithActionEnabled } from '../../context/WithActionEnabled'; +import { SchemaComponentContextProvider } from '../../context/SchemaComponent'; +import { NavBar, Skeleton, TabBar } from 'antd-mobile'; +import { useNavigate, useParams } from 'react-router-dom'; +import { FormBlockProvider } from '../../context/FormBlock'; +import { ActionBarProvider } from '../provider/ActionBar'; +import { ApplyActionStatusProvider } from '../provider/ApplyActionStatus'; +import { WithdrawActionProvider } from '../provider/WithdrawAction'; +import { ContextApprovalExecution } from '../../context/ApprovalExecution'; +import { useSubmit } from '../hook/useSubmit'; +import { useFormBlockProps } from '../hook/useFormBlockProps'; +import { useWithdrawAction } from '../hook/useWithdrawAction'; +import { FileOutline, UserContactOutline } from 'antd-mobile-icons'; +import { useTranslation } from '../../locale'; +import '../../style/style.css'; + +export const ViewActionUserInitiationsContent = () => { + const params = useParams(); + const navigate = useNavigate(); + const { id } = params; + const { actionEnabled } = useContext(ContextWithActionEnabled); + const [noDate, setNoDate] = useState(false); + const [recordData, setRecordDate] = useState({}); + const [currContext, setCurrContext] = useState('formContext'); + const { t } = useTranslation(); + const api = useAPIClient(); + useEffect(() => { + api + .request({ + url: 'approvalExecutions:get', + params: { + filter: { approvalId: id }, + appends: [ + 'execution', + 'execution.jobs', + 'approval', + 'approval.workflow', + 'approval.workflow.nodes', + 'approval.approvalExecutions', + 'approval.createdBy.id', + 'approval.createdBy.nickname', + 'approval.records', + 'approval.records.node.title', + 'approval.records.node.config', + 'approval.records.job', + 'approval.records.user.nickname', + ], + except: ['approval.approvalExecutions.snapshot', 'approval.records.snapshot'], + }, + }) + .then((res) => { + if (res.data?.data) { + setRecordDate(res.data.data); + } else { + setNoDate(true); + } + }) + .catch(() => { + console.error; + }); + }, []); + // @ts-ignore + const { approval, execution, ...approvalValue } = recordData || {}; + const { workflow } = approval || {}; + + return ( +
+ { + navigate(-1); + }} + className="navBarStyle" + > + {'审批'} + + +
+ {Object.keys(recordData).length && !noDate ? ( + + {UserInitiationsComponent(workflow?.config.applyForm, t, currContext)} + { + setCurrContext(item); + }} + className="tabsBarStyle" + > + } title="申请内容" /> + {actionEnabled ? null : } title="审批处理" />} + + + ) : ( +
+ + +
+ )} +
+
+
+ ); +}; + +const UserInitiationsComponent = (applyDetail, t, currContext) => { + const formContextSchema = { + type: 'void', + 'x-component': 'MPage', + 'x-designer': 'MPage.Designer', + 'x-component-props': {}, + properties: { + Approval: { + type: 'void', + 'x-decorator': 'SchemaComponentContextProvider', + 'x-decorator-props': { designable: false }, + 'x-component': 'RemoteSchemaComponent', + 'x-component-props': { + uid: applyDetail, + noForm: true, + }, + }, + }, + }; + + const approvalSchema = { + // type: 'void', + // 'x-component': 'MPage', + // 'x-designer': 'MPage.Designer', + // 'x-component-props': {}, + // properties: { + // process: { + // type: 'void', + // 'x-decorator': 'CardItem', + // 'x-component': 'ApprovalCommon.ViewComponent.ApprovalProcess', + // }, + // }, + }; + + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfig.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfig.tsx new file mode 100644 index 000000000..bb7f71104 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfig.tsx @@ -0,0 +1,28 @@ +import { SchemaInitializer } from '@tachybase/client'; +import { LauncherActionConfigComponent } from './LauncherActionConfigComponent'; +import { NAMESPACE } from '../../locale'; +import { APPROVAL_ACTION_STATUS, APPROVAL_STATUS } from '../../constants'; + +// 区块-配置操作 +export const LauncherActionConfigInitializer = new SchemaInitializer({ + name: 'ApprovalApplyAddActionButton', + title: '{{t("Configure actions")}}', + items: [ + { + name: 'submit', + type: 'item', + title: '{{t("Submit")}}', + Component: LauncherActionConfigComponent, + action: APPROVAL_ACTION_STATUS.SUBMITTED, + actionProps: { type: 'primary' }, + disabled: true, + }, + { + name: 'save', + type: 'item', + title: `{{t("Save draft", { ns: "${NAMESPACE}" })}}`, + Component: LauncherActionConfigComponent, + action: APPROVAL_ACTION_STATUS.DRAFT, + }, + ], +}); diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfigComponent.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfigComponent.tsx new file mode 100644 index 000000000..539ad063a --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/config/LauncherActionConfigComponent.tsx @@ -0,0 +1,31 @@ +import { ActionInitializer, useSchemaInitializerItem } from '@tachybase/client'; +import React from 'react'; + +// 区块-配置操作 +export const LauncherActionConfigComponent = () => { + const itemConfig = useSchemaInitializerItem(); + const { action, actionProps = {}, ...restItemConfig } = itemConfig; + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useDestroyAction.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useDestroyAction.tsx new file mode 100644 index 000000000..573c3ed2f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useDestroyAction.tsx @@ -0,0 +1,27 @@ +import { useAPIClient, useActionContext } from '@tachybase/client'; +import { useField } from '@tachybase/schema'; +import _ from 'lodash'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +export function useDestroyAction() { + const field = useField(); + const { setVisible, setSubmitted } = useActionContext() as any; + const { approval } = useContextApprovalExecution(); + const apiClient = useAPIClient(); + + return { + async run() { + try { + _.set(field, ['data', 'loading'], true); + + await apiClient.resource('approvals').destroy({ + filterByTk: approval.id, + }); + + setSubmitted(true); + } catch (err) { + _.set(field, ['data', 'loading'], false); + } + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useFormBlockProps.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useFormBlockProps.tsx new file mode 100644 index 000000000..420a5538c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useFormBlockProps.tsx @@ -0,0 +1,32 @@ +import { useCurrentUserContext } from '@tachybase/client'; +import { useFlowContext } from '@tachybase/plugin-workflow/client'; +import { useForm } from '@tachybase/schema'; +import { useEffect } from 'react'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; +import { ApprovalStatusEnums } from '../../constants'; + +export function useFormBlockProps() { + const { approval, id } = useContextApprovalExecution(); + const { workflow } = approval; + const form = useForm(); + const { data } = useCurrentUserContext(); + + const { editable } = ApprovalStatusEnums.find((value) => value.value === approval.status); + + const needEditable = + editable && approval?.latestExecutionId === id && approval?.createdById === data?.data.id && workflow.enabled; + + useEffect(() => { + if (!approval) { + return; + } + + if (needEditable) { + form.setPattern('editable'); + } else { + form.setPattern('readPretty'); + } + }, [form, approval, needEditable]); + + return { form }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useSubmit.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useSubmit.tsx new file mode 100644 index 000000000..db61534ee --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useSubmit.tsx @@ -0,0 +1,42 @@ +import { useAPIClient, useActionContext } from '@tachybase/client'; +import { useFlowContext } from '@tachybase/plugin-workflow/client'; +import { useField, useForm } from '@tachybase/schema'; +import _ from 'lodash'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; +import { useContextApprovalStatus } from '../provider/ApplyActionStatus'; + +export function useSubmit() { + const from = useForm(); + const field = useField(); + const { approval } = useContextApprovalExecution(); + const { setVisible, setSubmitted } = useActionContext() as any; + const { id } = approval; + const { workflow } = useFlowContext(); + const contextApprovalStatus = useContextApprovalStatus(); + const apiClient = useAPIClient(); + + return { + async run() { + try { + from.submit(); + + _.set(field, ['data', 'loading'], true); + + apiClient.resource('approvals').update({ + filterByTk: id, + values: { + collectionName: workflow.config.collection, + data: from.values, + status: contextApprovalStatus, + }, + }); + setSubmitted(true); + setVisible(false); + from.reset(); + _.set(field, ['data', 'loading'], false); + } catch (m) { + _.set(field, ['data', 'loading'], false); + } + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useWithdrawAction.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useWithdrawAction.tsx new file mode 100644 index 000000000..d5c56b862 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/hook/useWithdrawAction.tsx @@ -0,0 +1,31 @@ +import { useAPIClient, useActionContext } from '@tachybase/client'; +import { useField } from '@tachybase/schema'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +// 撤回 +export function useWithdrawAction() { + const field = useField(); + const { setVisible, setSubmitted } = useActionContext() as any; + const { approval } = useContextApprovalExecution(); + const api = useAPIClient(); + return { + async run() { + try { + field.data = field.data ?? {}; + field.data.loading = true; + + await api.resource('approvals').withdraw({ + filterByTk: approval.id, + }); + + setSubmitted(true); + + field.data.loading = false; + } catch (v) { + if (field.data) { + field.data.loading = false; + } + } + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ActionBar.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ActionBar.tsx new file mode 100644 index 000000000..1e395cace --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ActionBar.tsx @@ -0,0 +1,23 @@ +import { useCurrentUserContext } from '@tachybase/client'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; +import { APPROVAL_ACTION_STATUS } from '../../constants'; + +export function ActionBarProvider(props) { + const { data } = useCurrentUserContext(); + const { approval, id } = useContextApprovalExecution(); + const { status, createdById, latestExecutionId } = approval; + + const isSameId = data.data.id === createdById; + const isSameExcutionId = latestExecutionId === id; + const isExcutionDid = [ + APPROVAL_ACTION_STATUS.DRAFT, + APPROVAL_ACTION_STATUS.RETURNED, + APPROVAL_ACTION_STATUS.SUBMITTED, + ].includes(status); + + if (!isSameId || !isSameExcutionId || !isExcutionDid) { + return null; + } + + return props.children; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ApplyActionStatus.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ApplyActionStatus.tsx new file mode 100644 index 000000000..f3867776f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/ApplyActionStatus.tsx @@ -0,0 +1,28 @@ +import React, { useContext } from 'react'; +import { useCurrentUserContext } from '@tachybase/client'; +import { useFlowContext } from '@tachybase/plugin-workflow/client'; +import { createContext } from 'react'; +import { APPROVAL_ACTION_STATUS } from '../../constants'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +const ContextApprovalStatus = createContext(APPROVAL_ACTION_STATUS.SUBMITTED); + +export function useContextApprovalStatus() { + return useContext(ContextApprovalStatus); +} + +export function ApplyActionStatusProvider(props) { + const { value, children } = props; + const { approval } = useContextApprovalExecution(); + const { status, createdById, workflow } = approval; + const { data } = useCurrentUserContext(); + const isSameId = data.data.id === createdById; + const isEnbled = workflow.enabled; + const isStatusDid = [APPROVAL_ACTION_STATUS.DRAFT, APPROVAL_ACTION_STATUS.RETURNED].includes(status); + + if (isSameId && isEnbled && isStatusDid) { + return {children}; + } + + return null; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/WithdrawAction.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/WithdrawAction.tsx new file mode 100644 index 000000000..0e251f042 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/initiations/provider/WithdrawAction.tsx @@ -0,0 +1,18 @@ +import { useCurrentUserContext } from '@tachybase/client'; +import { APPROVAL_ACTION_STATUS } from '../../constants'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +export function WithdrawActionProvider({ children }) { + const { data } = useCurrentUserContext(); + const { approval } = useContextApprovalExecution(); + const { status, createdById, workflow } = approval; + + const isSameId = data.data.id === createdById; + const isEnabledWithdraw = workflow.enabled && workflow.config.withdrawable; + const isStatusSubmitted = APPROVAL_ACTION_STATUS.SUBMITTED === status; + if (isSameId && isEnabledWithdraw && isStatusSubmitted) { + return children; + } + + return null; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/locale.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/locale.tsx index 231dcd5fe..3c725186a 100644 --- a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/locale.tsx +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/locale.tsx @@ -1,15 +1,26 @@ -import { useApp, tval as nTval, i18n } from '@tachybase/client'; +import { tval as nTval, i18n } from '@tachybase/client'; -const NAMESPACE = '@hera/plugin-approval-mobile'; +export const NAMESPACE = '@hera/plugin-approval-mobile'; -export const useTranslation = (): any => { - const { i18n } = useApp(); - const t = (key: string, props = {}) => i18n.t(key, { ns: NAMESPACE, ...props }); - return { t }; -}; - -export const tval = (key: string) => nTval(key, { ns: NAMESPACE }); - -export function lang(key: string) { - return i18n.t(key, { ns: NAMESPACE }); +export function usePluginTranslation(): any { + return useTranslation(); } + +export function useTranslation() { + const t = (key: string, options = {}) => i18n.t(key, { ns: NAMESPACE, ...options }); + return { t }; +} +export function lang(key: string, options = {}) { + return i18n.t(key, { + ...options, + ns: NAMESPACE, + }); +} + +export const tval = (key: string, haveNamespace: boolean = true) => { + if (haveNamespace) { + return nTval(key, { ns: NAMESPACE }); + } else { + return nTval(key); + } +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/style/style.css b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/style/style.css new file mode 100644 index 000000000..0b8271ba6 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/style/style.css @@ -0,0 +1,21 @@ +.tabsBarStyle{ + position: fixed; + bottom: 0; + z-index: 2; + height: 5vh; + width: 100vw; + background-color: #ffffff; +} + +.navBarStyle{ + background-color: #ffffff; + margin-bottom: 10px; + position: fixed; + z-index: 2; + height: 5vh; + width: 100vw; +} + +.approvalContext{ + margin: 6vh 0 6vh 0; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/TodosBlock.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/TodosBlock.tsx similarity index 100% rename from packages/plugins/@hera/plugin-approval-mobile/src/client/approval/TodosBlock.tsx rename to packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/TodosBlock.tsx diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApplicantType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApplicantType.tsx similarity index 100% rename from packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApplicantType.tsx rename to packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApplicantType.tsx diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApprovalItem.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApprovalItem.tsx new file mode 100644 index 000000000..7e18e4f9f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApprovalItem.tsx @@ -0,0 +1,100 @@ +import { useAPIClient, useCollectionManager, useRequest } from '@tachybase/client'; +import { connect, useFieldSchema } from '@tachybase/schema'; +import { Badge, Empty, List, Space, Tag } from 'antd-mobile'; +import React, { useEffect, useState } from 'react'; +import { useAsyncEffect } from 'ahooks'; +import { APPROVAL_STATUS, ApprovalPriorityType, approvalStatusOptions } from '../../constants'; +import { useNavigate } from 'react-router-dom'; +import { tval, useTranslation } from '../../locale'; + +export const TabApprovalItem = () => { + const fieldSchema = useFieldSchema(); + const props = fieldSchema['x-component-props']; + const cm = useCollectionManager(); + const api = useAPIClient(); + const [data, setData] = useState([]); + const { t } = useTranslation(); + const navigate = useNavigate(); + useAsyncEffect(async () => { + const { data: user } = await api.request({ url: 'users:list', params: { pageSize: 999, appends: ['roles'] } }); + api + .request({ + url: 'approvalRecords:listCentralized', + params: { pageSize: 99999, appends: ['execution', 'job', 'node', 'workflow'] }, + }) + .then((res) => { + const result = res.data?.data.map((item) => { + const itemUser = user.data.find((value) => value.id === item.userId); + const priorityType = ApprovalPriorityType.find( + (priorityItem) => priorityItem.value === item.snapshot.priority, + ); + const statusType = approvalTodoListStatus(item, t); + const categoryTitle = item.workflow.title.replace('审批流:', ''); + + return { + ...item, + title: `${itemUser.nickname}的${categoryTitle}`, + categoryTitle: categoryTitle, + statusTitle: t(statusType.label), + statusColor: statusType.color, + reason: item.snapshot.reason || item.snapshot.reason_pay, + priorityTitle: priorityType.label, + priorityColor: priorityType.color, + }; + }); + result.sort((a, b) => { + return Date.parse(b.createdAt) - Date.parse(a.createdAt); + }); + setData(result); + }) + .catch(() => { + console.error; + }); + }, props); + + return ( +
+ {data.length ? ( + + {data.map((item, index) => { + return ( + { + navigate(`/mobile/approval/${item.id}/${item.categoryTitle}/detailspage`); + }} + > + + + {item.title} + + {item.statusTitle} + + + {item.priorityTitle} + + + + 事由:{item.reason} + + ); + })} + + ) : ( + + )} +
+ ); +}; + +const approvalTodoListStatus = (item, t) => { + const { workflow, execution, job, status } = item; + if ( + (!(workflow != null && workflow.enabled) || (execution != null && execution.stauts) || job?.status) && + [APPROVAL_STATUS.ASSIGNED, APPROVAL_STATUS.PENDING].includes(status) + ) { + return { label: t('Unprocessed'), color: 'default' }; + } else { + return approvalStatusOptions.find((value) => value.value === status); + } +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApprovalType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApprovalType.tsx similarity index 100% rename from packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabApprovalType.tsx rename to packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabApprovalType.tsx diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabBatchProcessingType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabBatchProcessingType.tsx similarity index 100% rename from packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabBatchProcessingType.tsx rename to packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabBatchProcessingType.tsx diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabReachDataType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabReachDataType.tsx similarity index 100% rename from packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabReachDataType.tsx rename to packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabReachDataType.tsx diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabReadingType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabReadingType.tsx similarity index 100% rename from packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabReadingType.tsx rename to packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabReadingType.tsx diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabTemplateType.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabTemplateType.tsx similarity index 100% rename from packages/plugins/@hera/plugin-approval-mobile/src/client/approval/component/TabTemplateType.tsx rename to packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/TabTemplateType.tsx diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/ViewActionTodosContent.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/ViewActionTodosContent.tsx new file mode 100644 index 000000000..145b8c9c5 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/component/ViewActionTodosContent.tsx @@ -0,0 +1,186 @@ +import { + RemoteSchemaComponent, + SchemaComponent, + SchemaComponentProvider, + useAPIClient, + useFormBlockContext, + useRecord, + useRequest, +} from '@tachybase/client'; +import { Result } from 'antd'; +import _ from 'lodash'; +import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from '../../locale'; +import { DetailsBlockProvider } from '@tachybase/plugin-workflow/client'; +import { ContextWithActionEnabled } from '../../context/WithActionEnabled'; +import { ContextApprovalExecution } from '../../context/ApprovalExecution'; +import { NavBar, Skeleton, TabBar } from 'antd-mobile'; +import { useNavigate, useParams } from 'react-router-dom'; +import { SchemaComponentContextProvider } from '../../context/SchemaComponent'; +import { ApprovalActionProvider } from '../provider/ApprovalAction'; +import { ApprovalFormBlockDecorator } from '../provider/ApprovalFormBlock'; +import { useApprovalDetailBlockProps } from '../hook/useApprovalDetailBlockProps'; +import { useApprovalFormBlockProps } from '../hook/useApprovalFormBlockProps'; +import { useSubmit } from '../hook/useSubmit'; +import { CouponOutline, FileOutline, ScanCodeOutline, UserContactOutline } from 'antd-mobile-icons'; +import { ActionBarProvider } from '../provider/ActionBarProvider'; +import { FormBlockProvider } from '../../context/FormBlock'; +import '../../style/style.css'; + +// 审批-待办-查看: 内容 +export const ViewActionTodosContent = () => { + const { t } = useTranslation(); + const { actionEnabled } = useContext(ContextWithActionEnabled); + const navigate = useNavigate(); + const params = useParams(); + const { id } = params; + const api = useAPIClient(); + const [noDate, setNoDate] = useState(false); + const [recordData, setRecordDate] = useState({}); + const [currContext, setCurrContext] = useState('formContext'); + useEffect(() => { + api + .request({ + url: 'approvalRecords:get', + params: { + filter: { id }, + appends: [ + 'approvalExecution', + 'node', + 'job', + 'workflow', + 'workflow.nodes', + 'execution', + 'execution.jobs', + 'user', + 'approval', + 'approval.createdBy', + 'approval.approvalExecutions', + 'approval.createdBy.nickname', + 'approval.records', + 'approval.records.node.title', + 'approval.records.node.config', + 'approval.records.job', + 'approval.records.user.nickname', + ], + except: [ + 'approval.data', + 'approval.approvalExecutions.snapshot', + 'approval.records.snapshot', + 'workflow.config', + 'workflow.options', + 'nodes.config', + ], + sort: ['-createdAt'], + }, + }) + .then((res) => { + if (res.data?.data) { + setRecordDate(res.data.data); + } else { + setNoDate(true); + } + }) + .catch(() => { + console.error; + }); + }, []); + + if (noDate) { + return ; + } + + const { node } = recordData as any; + + return ( +
+ { + navigate(-1); + }} + className="navBarStyle" + > + {'审批'} + +
+ {Object.keys(recordData).length && !noDate ? ( + + {todosComponent(node?.config.applyDetail, t, currContext)} + { + setCurrContext(item); + }} + className="tabsBarStyle" + > + } title="申请内容" /> + {actionEnabled ? null : } title="审批处理" />} + + + ) : ( +
+ + +
+ )} +
+
+ ); +}; + +const todosComponent = (applyDetail, t, currContext) => { + const formContextSchema = { + type: 'void', + 'x-component': 'MPage', + 'x-designer': 'MPage.Designer', + 'x-component-props': {}, + properties: { + Approval: { + type: 'void', + 'x-decorator': 'SchemaComponentContextProvider', + 'x-decorator-props': { designable: false }, + 'x-component': 'RemoteSchemaComponent', + 'x-component-props': { + uid: applyDetail, + noForm: true, + }, + }, + }, + }; + + const approvalSchema = { + type: 'void', + 'x-component': 'MPage', + 'x-designer': 'MPage.Designer', + 'x-component-props': {}, + properties: { + process: { + type: 'void', + 'x-decorator': 'CardItem', + 'x-component': 'ApprovalCommon.ViewComponent.MApprovalProcess', + }, + }, + }; + + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalDetailBlockProps.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalDetailBlockProps.tsx new file mode 100644 index 000000000..7835ebccb --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalDetailBlockProps.tsx @@ -0,0 +1,14 @@ +import { useFormBlockContext } from '@tachybase/client'; +import { useEffect } from 'react'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +export function useApprovalDetailBlockProps() { + const { snapshot } = useContextApprovalExecution(); + const { form } = useFormBlockContext(); + + useEffect(() => { + form.setValues(snapshot); + }, [form, snapshot]); + + return { form }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalFormBlockProps.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalFormBlockProps.tsx new file mode 100644 index 000000000..ec9df42f4 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useApprovalFormBlockProps.tsx @@ -0,0 +1,6 @@ +import { useFormBlockContext } from '@tachybase/client'; + +export function useApprovalFormBlockProps() { + const { form } = useFormBlockContext(); + return { form }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useDestroyAction.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useDestroyAction.tsx new file mode 100644 index 000000000..cffdb3f46 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useDestroyAction.tsx @@ -0,0 +1,27 @@ +import { useAPIClient, useActionContext } from '@tachybase/client'; +import { useField } from '@tachybase/schema'; +import _ from 'lodash'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +export function useDestroyAction() { + const field = useField(); + const { setSubmitted } = useActionContext() as any; + const { approval } = useContextApprovalExecution(); + const apiClient = useAPIClient(); + + return { + async run() { + try { + _.set(field, ['data', 'loading'], true); + + await apiClient.resource('approvals').destroy({ + filterByTk: approval.id, + }); + + setSubmitted(true); + } catch (err) { + _.set(field, ['data', 'loading'], false); + } + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useFormBlockProps.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useFormBlockProps.tsx new file mode 100644 index 000000000..37bb80045 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useFormBlockProps.tsx @@ -0,0 +1,30 @@ +import { useCurrentUserContext } from '@tachybase/client'; +import { useForm } from '@tachybase/schema'; +import { useEffect } from 'react'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; +import { ApprovalStatusEnumDict } from '../../constants'; + +export function useFormBlockProps() { + const { approval, id, workflow } = useContextApprovalExecution(); + const form = useForm(); + const { data } = useCurrentUserContext(); + + const { editable } = ApprovalStatusEnumDict[approval.status]; + + const needEditable = + editable && approval?.latestExecutionId === id && approval.createdById === data?.data.id && workflow.enabled; + + useEffect(() => { + if (!approval) { + return; + } + + if (needEditable) { + form.setPattern('editable'); + } else { + form.setPattern('readPretty'); + } + }, [form, approval, needEditable]); + + return { form }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useSubmit.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useSubmit.tsx new file mode 100644 index 000000000..22d3545f5 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/hook/useSubmit.tsx @@ -0,0 +1,37 @@ +import { useAPIClient, useActionContext } from '@tachybase/client'; +import { useField, useForm } from '@tachybase/schema'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; +import { useContextApprovalAction } from '../provider/ApprovalAction'; + +export function useSubmit() { + const field = useField(); + const api = useAPIClient(); + const form = useForm(); + const { id } = useContextApprovalExecution(); + const { status } = useContextApprovalAction(); + const { setVisible, setSubmitted } = useActionContext() as any; + return { + run: async () => { + try { + if (form.values.status) { + return; + } + await form.submit(); + field.data = field.data ?? {}; + field.data.loading = true; + setVisible(false); + await api.resource('approvalRecords').submit({ + filterByTk: id, + values: { ...form.values, status }, + }); + field.data.loading = false; + await form.reset(); + // refreshAction?.(); + setSubmitted?.(true); + } catch (error) { + console.error(error); + field.data && (field.data.loading = false); + } + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/interface/interface.ts b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/interface/interface.ts new file mode 100644 index 000000000..253354cbb --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/interface/interface.ts @@ -0,0 +1,146 @@ +export interface ApprovalExecution { + id: number; + approval: Approval; + approvalExecution?: approvalExecution; + approvalId: number; + comment?: any; + execution: execution; + executionId: number; + index?: string; + job?: job; + jobId?: number; + node?: node; + nodeId?: number; + snapshot: snapshot; + status: number; + user?: user; + userId?: number; + workflow?: any; + workflowId?: number; + updatedAt: any; +} + +export interface Approval { + id: number; + collectionName: string; + dataKey: string; + workflow: any; + executions: any[]; + approvalExecutions: any[]; + latestApprovalExecution: any; + records: any[]; + createdById: number; + status: number; + data: any; + applicantRole: any; + latestExecutionId?: any; +} + +export interface approvalExecution { + approvalId: number; + executionId: number; + id: number; + snapshot: {}; + status: any; +} + +export interface execution { + context: {}; + id: number; + jobs: any[]; + key: string; + status: number; + workflowId: number; +} + +export interface job { + executionId: number; + id: number; + nodeId: number; + nodeKey: string; + result: any; + status: number; + upstreamId: any; +} + +export interface node { + branchIndex: any; + config: {}; + downstreamId: any; + id: number; + key: string; + title: string; + type: string; + upstreamId: any; + workflowId: number; +} + +export interface snapshot { + ReasonCollection: any; + account_collection: any; + account_collection_id: any; + account_comment: any; + account_id: any; + account_pay: any; + account_pay_id: any; + amount_pay: any; + approve_status: string; + approver_list: any[]; + approver_pre_list: any[]; + attachments: any[]; + category: string; + cc_list: any[]; + comment_collection: any; + comment_pay: any; + company: any; + company_id: any; + company_pay: {}; + company_pay_id: number; + company_receive: {}; + company_receive_id: number; + createdById: number; + date_pay: any; + date_receive: any; + id: number; + items: any[]; + items_amount: any; + items_amount_pay: any; + items_amount_receive: number; + items_amount_show: any; + priority: string; + project: any; + project_collection_id: any; + project_id: any; + project_pay_id: any; + reason: any; + reason_collection: any; + reason_pay: any; + sort: number; + style_temp: any; +} + +export interface user { + appLang: string; + email: string; + id: number; + nickname: string; + pdf_top_margin: any; + phone: string; + systemSettings: {}; + username: string; +} + +export interface workflow { + allExecuted: number; + current: boolean; + description: any; + enabled: boolean; + executed: number; + id: number; + key: string; + nodes: any[]; + sync: boolean; + title: string; + triggerTitle: any; + type: string; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ActionBarProvider.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ActionBarProvider.tsx new file mode 100644 index 000000000..dc3b3f61f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ActionBarProvider.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { ActionBarProvider as ClientActionBarProvider, useCompile } from '@tachybase/client'; +import { str2moment } from '@tachybase/utils/client'; +import { Space, Tag } from 'antd'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; +import { approvalStatusOptions } from '../../constants'; + +export function ActionBarProvider(props) { + const { status } = useContextApprovalExecution(); + + if (status) { + return ; + } else { + return ; + } +} + +const ComponentUserInfo = () => { + const compile = useCompile(); + const { status, updatedAt, user } = useContextApprovalExecution(); + const configObj = approvalStatusOptions.find((value) => value.value === status); + return ( + + {compile(configObj.label)} + + {user.nickname} + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApplyActionStatus.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApplyActionStatus.tsx new file mode 100644 index 000000000..07faf720b --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApplyActionStatus.tsx @@ -0,0 +1,27 @@ +import React, { useContext } from 'react'; +import { useCurrentUserContext } from '@tachybase/client'; +import { createContext } from 'react'; +import { APPROVAL_ACTION_STATUS } from '../../constants'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +const ContextApprovalStatus = createContext(APPROVAL_ACTION_STATUS.SUBMITTED); + +export function useContextApprovalStatus() { + return useContext(ContextApprovalStatus); +} + +export function ApplyActionStatusProvider(props) { + const { value, children } = props; + const { approval } = useContextApprovalExecution(); + const { status, createdById, workflow } = approval; + const { data } = useCurrentUserContext(); + const isSameId = data.data.id === createdById; + const isEnbled = workflow.enabled; + const isStatusDid = [APPROVAL_ACTION_STATUS.DRAFT, APPROVAL_ACTION_STATUS.RETURNED].includes(status); + + if (isSameId && isEnbled && isStatusDid) { + return {children}; + } + + return null; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalAction.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalAction.tsx new file mode 100644 index 000000000..4ff8e2cb1 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalAction.tsx @@ -0,0 +1,23 @@ +import React, { useContext } from 'react'; +import { createContext } from 'react'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +export interface ApprovalAction { + status: number | null; +} + +const ContextApprovalAction = createContext>({}); + +export function useContextApprovalAction() { + return useContext(ContextApprovalAction); +} + +export function ApprovalActionProvider({ children, ...props }) { + const { status } = useContextApprovalExecution(); + + if (!status || status === props.status) { + return {children}; + } + + return null; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalFormBlock.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalFormBlock.tsx new file mode 100644 index 000000000..eb5379410 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/ApprovalFormBlock.tsx @@ -0,0 +1,85 @@ +import { + BlockRequestContext_deprecated, + FormActiveFieldsProvider, + FormBlockContext, + FormV2, + useAPIClient, + useAssociationNames, + useCurrentUserContext, + useDesignable, +} from '@tachybase/client'; +import { RecursionField, createForm, useField, useFieldSchema } from '@tachybase/schema'; +import _ from 'lodash'; +import React, { Fragment, useContext, useMemo, useRef } from 'react'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +export function ApprovalFormBlockDecorator(props) { + const approvalExecutions = useContextApprovalExecution(); + const { job, execution, workflow } = approvalExecutions; + const omitApproval = _.omit(approvalExecutions, ['approval', 'job', 'node', 'snapshot']); + const fieldSchema = useFieldSchema(); + + const field = useField(); + const formBlockRef = useRef(null); + const { getAssociationAppends } = useAssociationNames(); + const { appends, updateAssociationValues } = getAssociationAppends(); + const { data } = useCurrentUserContext(); + const { findComponent } = useDesignable(); + const ContainerFormComp = findComponent(field.component?.[0]) || Fragment; + + const form = useMemo(() => { + return createForm({ + initialValues: omitApproval, + pattern: + !workflow?.enabled || execution?.status || job?.status || omitApproval?.status == null + ? 'disabled' + : omitApproval.status || data.data?.id !== omitApproval.userId + ? 'readPretty' + : 'editable', + }); + }, [data.data?.id, omitApproval?.status, workflow?.enabled]); + const params = useMemo(() => ({ appends: appends, ...props.params }), [appends, props.params]); + const result = useMemo(() => ({ loading: false, data: { data: omitApproval } }), [omitApproval]); + const collectionApi = useAPIClient().resource(props.collection); + const blockRequestContext = useContext(BlockRequestContext_deprecated); + + const formValue = useMemo( + () => ({ + params: params, + form: form, + field: field, + service: result, + updateAssociationValues: updateAssociationValues, + formBlockRef: formBlockRef, + }), + [field, form, params, result, updateAssociationValues], + ); + + if (!omitApproval.status && omitApproval.userId !== data.data?.id) { + return null; + } + + return ( + + + + + +
+ +
+
+
+
+
+ ); +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/WithdrawAction.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/WithdrawAction.tsx new file mode 100644 index 000000000..68a515479 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/approval/todos/provider/WithdrawAction.tsx @@ -0,0 +1,19 @@ +import { useCurrentUserContext } from '@tachybase/client'; +import { APPROVAL_ACTION_STATUS } from '../../constants'; +import { useContextApprovalExecution } from '../../context/ApprovalExecution'; + +export function WithdrawActionProvider({ children }) { + const { data } = useCurrentUserContext(); + const { approval } = useContextApprovalExecution(); + const { workflow, status, createdById } = approval; + + const isSameId = data.data.id === createdById; + const isEnabledWithdraw = workflow.enabled && workflow.config.withdrawable; + const isStatusSubmitted = APPROVAL_ACTION_STATUS.SUBMITTED === status; + + if (isSameId && isEnabledWithdraw && isStatusSubmitted) { + return children; + } + + return null; +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/client/index.tsx b/packages/plugins/@hera/plugin-approval-mobile/src/client/index.tsx index 262f1b764..b6863f26e 100644 --- a/packages/plugins/@hera/plugin-approval-mobile/src/client/index.tsx +++ b/packages/plugins/@hera/plugin-approval-mobile/src/client/index.tsx @@ -8,7 +8,16 @@ export class PluginApprovalMobileClient extends Plugin { async beforeLoad() {} - async load() {} + async load() { + this.app.router.add('mobile.approval.page', { + path: '/mobile/:name/:id/page', + Component: 'ViewActionUserInitiationsContent', + }); + this.app.router.add('mobile.approval.detailspage', { + path: '/mobile/:name/:id/:category/detailspage', + Component: 'ViewActionTodosContent', + }); + } } export default PluginApprovalMobileClient; diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/locale/en-US.json b/packages/plugins/@hera/plugin-approval-mobile/src/locale/en-US.json new file mode 100644 index 000000000..6ef02338c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/locale/en-US.json @@ -0,0 +1,65 @@ +{ + "Approval": "Approval", + "Approvals": "Approvals", + "Approval blocks": "Approval blocks", + "Launch": "Launch", + "Todos": "Todos", + "Approval applications": "Approval applications", + "Approval todos": "Approval todos", + "Related approvals": "Related approvals", + "Apply new": "Apply new", + "Apply": "Apply", + "Initiator": "Initiator", + "Application content": "Application content", + "Approval process": "Approval process", + "History": "History", + "Approval ID": "Approval ID", + "Node": "Node", + "Assignee": "Assignees", + "Assignees": "Assignees", + "Add assignee": "Add assignee", + "Negotiation mode": "Negotiation mode", + "Or": "Or", + "And": "And", + "Voting": "Voting", + "Anyone approve or reject as result.": "Anyone approve or reject as result.", + "Everyone approve as approved, or any one reject as rejected.": "Everyone approve as approved, or any one reject as rejected.", + "Approve when approvals rate greater than the set percentage, reject when rejections rate greater than or equal to (1 - percentage).": "Approve when approvals rate greater than the set percentage, reject when rejections rate greater than or equal to (1 - percentage).", + "Order": "Order", + "Sequentially": "Sequentially", + "Parallelly": "Parallelly", + "Draft": "Draft", + "Withdraw": "Withdraw", + "Are you sure you want to withdraw it?": "Are you sure you want to withdraw it?", + "Returned": "Returned", + "Submitted": "Submitted", + "Processing": "Processing", + "Approved": "Approved", + "Rejected": "Rejected", + "Approve": "Approve", + "Reject": "Reject", + "Return": "Return", + "Canceled": "Canceled", + "Assigned": "Assigned", + "Pending": "Pending", + "Withdrawn": "Withdrawn", + "Unprocessed": "Unprocessed", + "User interface": "User interface", + "View user interface": "View user interface", + "Configure user interface": "Configure user interface", + "Apply form": "Apply form", + "Withdrawable": "Withdrawable", + "Returnable": "Returnable", + "Comment": "Comment", + "Event will be triggered when submitted current workflow bound form action, or new application from approval block.": "Event will be triggered when submitted current workflow bound form action, or new application from approval block.", + "Used to perform manual approval operations within approval workflow, approvers can perform approval action through the to-do list in the approval block, such as approval, rejection or return.": "Used to perform manual approval operations within approval workflow, approvers can perform approval action through the to-do list in the approval block, such as approval, rejection or return.", + "Pass mode": "Pass mode", + "Passthrough mode": "Passthrough mode", + "When rejected or returned, the workflow will be terminated immediately.": "When rejected or returned, the workflow will be terminated immediately.", + "Branch mode": "Branch mode", + "Could run different branch based on result.": "Could run different branch based on result.", + "End on reject": "End on reject", + "If checked, the workflow will be terminated after rejection branch processed.": "If checked, the workflow will be terminated after rejection branch processed.", + "Disabled": "Disabled", + "Trigger data": "Trigger data" +} diff --git a/packages/plugins/@hera/plugin-approval-mobile/src/locale/zh-CN.json b/packages/plugins/@hera/plugin-approval-mobile/src/locale/zh-CN.json new file mode 100644 index 000000000..9857c51f3 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval-mobile/src/locale/zh-CN.json @@ -0,0 +1,80 @@ +{ + "Approval event": "审批事件", + "Approval": "审批", + "Approvals": "审批", + "Approval blocks": "审批区块", + "Launch": "发起", + "Todos": "待办", + "Approval applications": "审批申请", + "Approval todos": "审批待办", + "Related approvals": "相关审批", + "Apply new": "发起新申请", + "Apply": "发起", + "Initiator": "发起人", + "Application content": "申请内容", + "Approval process": "审批处理", + "No data yet": "暂无数据", + "Approval ID": "单据编号", + "Current status": "当前状态", + "Task node": "任务节点", + "Assignee": "审批人", + "Assignees": "审批人", + "Add assignee": "添加审批人", + "Select assignees": "选择审批人", + "Query assignees": "查询审批人", + "Negotiation mode": "协商模式", + "Or": "或签", + "And": "会签", + "Voting": "投票", + "The approval or rejection by anyone of them is the result.": "任意一人通过或否决即为结果。", + "If it's approved by all, it's approved. If it's rejected by anyone, it's rejected.": "所有人通过才通过,任意一人否决则否决。", + "Approved if the approval rate is greater than the set percentage, otherwise rejected.": "通过率大于设置的百分比时通过,否则否决。", + "Order": "多人处理顺序", + "Parallelly": "并行", + "Multiple approvers can approve in any order.": "多个审批人可以任意顺序审批。", + "Sequentially": "顺序", + "Multiple approvers in sequential order.": "多个审批人按照排序审批。", + "Save draft": "保存草稿", + "Draft": "草稿", + "Withdraw": "撤回", + "Are you sure you want to withdraw it?": "确定要撤回吗?", + "Returned": "退回", + "Submitted": "提交", + "Processing": "处理中", + "Approved": "通过", + "Rejected": "否决", + "Approve": "通过", + "Reject": "否决", + "Return": "退回", + "Canceled": "取消", + "Assigned": "已分配", + "Pending": "待处理", + "Withdrawn": "撤回", + "Unprocessed": "未处理", + "Where to initiate and approve": "发起和审批的位置", + "Initiate and approve in data blocks only": "仅在数据区块中发起和审批", + "Actions from any form block can be bound to this workflow for initiating approvals, and the approval process can be handled and tracked in the approval block of a single record which is typically applicable to business data.": "可以将任意表单区块的操作绑定到该工作流,用于发起审批,并在单条数据的审批区块里处理和跟踪审批过程,通常适用于业务数据。", + "Initiate and approve in both data blocks and global approval blocks": "在数据区块和审批中心都可以发起和审批", + "In addition to data blocks, a global approval block can also be used to initiates and processes approvals, which typically applies to administrative data.": "除了数据区块,还可以在全局的审批中心发起和处理审批,这通常适用于行政数据。", + "Initiator's interface": "发起人的操作界面", + "Approver's interface": "审批人的操作界面", + "Go to configure": "进入配置", + "For initiating approvals, or viewing and manipulating initiated approvals.": "用于发起审批,或者查看和操作已发起的审批。", + "Apply form": "申请表单", + "Allowed to be withdrawn": "允许撤回", + "Allow the initiator to withdraw the approval before the approval starts.": "在审批开始之前,允许发起人撤回审批。", + "Returnable": "可退回", + "Comment": "意见", + "Triggered when an approval request is initiated through an action button or API. Dedicated to the approval process, with exclusive approval node and block for managing documents and tracking processing processes.": "通过操作按钮或 API 发起审批申请时触发。专用于审批流程,有专属的审批节点和区块用于管理单据和追踪处理过程。", + "Manual approval operations within the approval process, the approver can approve in the global approval block or in the approval block of a single record.": "在审批流程内进行人工审批操作,审批人可以在全局的审批区块里进行审批,也可以在单条数据的审批区块里审批。", + "Pass mode": "通过模式", + "Passthrough mode": "直通模式", + "When rejected or returned, the workflow will be terminated immediately.": "当否决或退回时,工作流将立即终止。", + "Branch mode": "分支模式", + "Could run different branch based on result.": "产生结果后可按结果继续不同的分支。", + "End the workflow after rejection branch": "否决后终止流程", + "When checked, the workflow will terminate when the rejection branch ends.": "勾选后,否决分支结束后工作流将终止。", + "Disabled": "已失效", + "Submission may be withdrawn, please try refresh the list.": "提交可能已被撤回,请尝试刷新列表。", + "Trigger data": "触发器数据" +} diff --git a/packages/plugins/@hera/plugin-approval/src/server/actions.ts b/packages/plugins/@hera/plugin-approval/src/server/actions.ts index 15dfe3f67..9bbab1e91 100644 --- a/packages/plugins/@hera/plugin-approval/src/server/actions.ts +++ b/packages/plugins/@hera/plugin-approval/src/server/actions.ts @@ -103,7 +103,6 @@ const approvals = { return actions.destroy(context, next); }, async withdraw(context, next) { - let _a; const { filterByTk } = context.action.params; const repository = utils.getRepositoryFromParams(context); const approval = await repository.findOne({ @@ -114,7 +113,7 @@ const approvals = { if (!approval) { return context.throw(404); } - if (approval.createdById !== ((_a = context.state.currentUser) == null ? void 0 : _a.id)) { + if (approval.createdById !== context.state.currentUser?.id) { return context.throw(403); } if (approval.status !== APPROVAL_STATUS.SUBMITTED || !approval.workflow.config.withdrawable) { @@ -157,7 +156,7 @@ const approvals = { map.set(record.job.id, record.job); } return map; - }, /* @__PURE__ */ new Map()); + }, new Map()); return Array.from(jobsMap.values()); }); context.body = approval; diff --git a/packages/plugins/@hera/plugin-homepage/package.json b/packages/plugins/@hera/plugin-homepage/package.json index 449d2b5da..d8cce81eb 100644 --- a/packages/plugins/@hera/plugin-homepage/package.json +++ b/packages/plugins/@hera/plugin-homepage/package.json @@ -8,6 +8,7 @@ }, "peerDependencies": { "@tachybase/client": "0.x", + "@tachybase/database": "0.x", "@tachybase/server": "0.x", "@tachybase/test": "0.x" } diff --git a/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx b/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx index 305664b08..2853ce48b 100644 --- a/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx +++ b/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx @@ -33,7 +33,7 @@ export const HomePage: React.FC<{}> = () => {
- {data.data.map((item) => ( + {data?.data?.map((item) => (
diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx index 063e6cbcf..11afe4d76 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx @@ -98,3 +98,5 @@ export const MobileCore: React.FC = (props) => { ); }; + +export * from './schema'; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx index 62123d384..f012eab95 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx @@ -88,3 +88,5 @@ export class MobileClientPlugin extends Plugin { } export default MobileClientPlugin; + +export * from './core'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 989719235..e6acfdac7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1418,6 +1418,9 @@ importers: '@tachybase/client': specifier: workspace:* version: link:../../../core/client + '@tachybase/plugin-workflow': + specifier: workspace:* + version: link:../../@tachybase/plugin-workflow '@tachybase/server': specifier: workspace:* version: link:../../../core/server @@ -1437,6 +1440,9 @@ importers: '@types/lodash': specifier: ^4.17.0 version: 4.17.0 + ahooks: + specifier: ^3.7.2 + version: 3.7.11(react@18.2.0) antd: specifier: 5.16.1 version: 5.16.1(react-dom@18.2.0)(react@18.2.0) @@ -1455,6 +1461,9 @@ importers: react-i18next: specifier: ^11.15.1 version: 11.18.6(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0) + react-router-dom: + specifier: 6.x + version: 6.22.3(react-dom@18.2.0)(react@18.2.0) packages/plugins/@hera/plugin-audit-logs: dependencies: @@ -1668,6 +1677,9 @@ importers: '@tachybase/client': specifier: 0.x version: link:../../../core/client + '@tachybase/database': + specifier: 0.x + version: link:../../../core/database '@tachybase/server': specifier: 0.x version: link:../../../core/server @@ -13923,7 +13935,7 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.24.4 + '@babel/runtime': 7.24.5 dayjs: 1.11.10 intersection-observer: 0.12.2 js-cookie: 2.2.1 @@ -26198,7 +26210,7 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: - '@babel/runtime': 7.24.0 + '@babel/runtime': 7.24.5 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-is: 18.3.1