diff --git a/packages/core/client/src/schema-component/antd/action/context.tsx b/packages/core/client/src/schema-component/antd/action/context.tsx index 024a660f3..da261d212 100644 --- a/packages/core/client/src/schema-component/antd/action/context.tsx +++ b/packages/core/client/src/schema-component/antd/action/context.tsx @@ -6,7 +6,9 @@ import { useActionContext } from './hooks'; export const ActionContext = createContext({}); ActionContext.displayName = 'ActionContext'; -export const ActionContextProvider: React.FC = (props) => { +export const ActionContextProvider: React.FC = ( + props, +) => { const contextProps = useActionContext(); return ( diff --git a/packages/core/client/src/schema-component/core/SchemaComponentOptions.tsx b/packages/core/client/src/schema-component/core/SchemaComponentOptions.tsx index 231ff51f1..403e6535d 100644 --- a/packages/core/client/src/schema-component/core/SchemaComponentOptions.tsx +++ b/packages/core/client/src/schema-component/core/SchemaComponentOptions.tsx @@ -1,5 +1,5 @@ import { ExpressionScope, SchemaComponentsContext, SchemaOptionsContext } from '@nocobase/schema'; -import React, { memo, useContext, useMemo } from 'react'; +import React, { PropsWithChildren, memo, useContext, useMemo } from 'react'; import { ISchemaComponentOptionsProps } from '../types'; export const useSchemaOptionsContext = () => { @@ -7,7 +7,7 @@ export const useSchemaOptionsContext = () => { return options || {}; }; -export const SchemaComponentOptions: React.FC = memo((props) => { +export const SchemaComponentOptions: React.FC> = memo((props) => { const { children } = props; const options = useSchemaOptionsContext(); const components = useMemo(() => { diff --git a/packages/core/client/src/schema-component/core/SchemaComponentProvider.tsx b/packages/core/client/src/schema-component/core/SchemaComponentProvider.tsx index cea796ccb..4d879ebea 100644 --- a/packages/core/client/src/schema-component/core/SchemaComponentProvider.tsx +++ b/packages/core/client/src/schema-component/core/SchemaComponentProvider.tsx @@ -1,7 +1,7 @@ import { createForm } from '@nocobase/schema'; import { FormProvider, Schema } from '@nocobase/schema'; import { uid } from '@nocobase/schema'; -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, PropsWithChildren } from 'react'; import { useTranslation } from 'react-i18next'; import { SchemaComponentContext } from '../context'; import { ISchemaComponentProvider } from '../types'; @@ -42,7 +42,7 @@ const Registry = { Schema.registerCompiler(Registry.compile); -export const SchemaComponentProvider: React.FC = (props) => { +export const SchemaComponentProvider: React.FC> = (props) => { const { designable, onDesignableChange, components, children } = props; const [uidValue, setUid] = useState(uid()); const [formId, setFormId] = useState(uid()); diff --git a/packages/plugins/@hera/plugin-approval/package.json b/packages/plugins/@hera/plugin-approval/package.json index cc9048ddf..4a9e8c6a2 100644 --- a/packages/plugins/@hera/plugin-approval/package.json +++ b/packages/plugins/@hera/plugin-approval/package.json @@ -3,6 +3,9 @@ "displayName": "Workflow: Approval Center", "version": "0.1.13", "description": "Can configure a universal approval process to complete the complete approval process in the approval center.", + "keywords": [ + "Workflow" + ], "main": "dist/server/index.js", "devDependencies": { "@ant-design/icons": "5.x", diff --git a/packages/plugins/@hera/plugin-approval/src/client/ApprovalApplyAddActionButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/ApprovalApplyAddActionButton.tsx deleted file mode 100644 index d5f7affdf..000000000 --- a/packages/plugins/@hera/plugin-approval/src/client/ApprovalApplyAddActionButton.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { SchemaInitializer } from '@nocobase/client'; -import { NAMESPACE } from '../locale'; -import { ke, X } from './refined'; - -export const ApprovalApplyAddActionButton = new SchemaInitializer({ - name: 'ApprovalApplyAddActionButton', - title: '{{t("Configure actions")}}', - items: [ - { - name: 'submit', - type: 'item', - title: '{{t("Submit")}}', - Component: ke, - action: X.SUBMITTED, - actionProps: { type: 'primary' }, - disabled: !0, - }, - { - name: 'save', - type: 'item', - title: `{{t("Save draft", { ns: "${NAMESPACE}" })}}`, - Component: ke, - action: X.DRAFT, - }, - ], -}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/ApprovalApplyAddBlockButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/ApprovalApplyAddBlockButton.tsx deleted file mode 100644 index 83b96f0db..000000000 --- a/packages/plugins/@hera/plugin-approval/src/client/ApprovalApplyAddBlockButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { SchemaInitializer, gridRowColWrap } from '@nocobase/client'; -import { NAMESPACE } from '../locale'; -import { Po } from './refined'; - -export const ApprovalApplyAddBlockButton = new SchemaInitializer({ - name: 'ApprovalApplyAddBlockButton', - wrap: gridRowColWrap, - title: '{{t("Add block")}}', - items: [ - { - name: 'forms', - type: 'itemGroup', - title: '{{t("Form")}}', - children: [{ name: 'form', type: 'item', title: `{{t("Apply form", { ns: "${NAMESPACE}" })}}`, Component: Po }], - }, - { - name: 'others', - type: 'itemGroup', - title: '{{t("Other blocks")}}', - children: [{ name: 'markdown', type: 'item', title: '{{t("Markdown")}}', Component: 'MarkdownBlockInitializer' }], - }, - ], -}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/ApprovalInstruction.tsx b/packages/plugins/@hera/plugin-approval/src/client/ApprovalInstruction.tsx deleted file mode 100644 index 0fe062022..000000000 --- a/packages/plugins/@hera/plugin-approval/src/client/ApprovalInstruction.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { ArrayItems } from '@formily/antd-v5'; -import { css } from '@nocobase/client'; -import { Instruction, RadioWithTooltip } from '@nocobase/plugin-workflow/client'; -import { uid } from '@nocobase/utils/client'; -import { NAMESPACE } from '../locale'; -import { T, rt, ot, et, Ro, jo, it, st, at, b } from './refined'; - -export class ApprovalInstruction extends Instruction { - constructor() { - super(...arguments); - T(this, 'title', `{{t("Approval", { ns: "${NAMESPACE}" })}}`); - T(this, 'type', 'approval'); - T( - this, - 'description', - `{{t("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.", { ns: "${NAMESPACE}" })}}` - ); - T(this, 'group', 'manual'); - T(this, 'fieldset', { - branchMode: { - type: 'boolean', - title: `{{t("Pass mode", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'Radio.Group', - 'x-component-props': { disabled: !0 }, - enum: rt([ - { - value: !1, - label: `{{t('Passthrough mode', { ns: "${NAMESPACE}" })}}`, - tooltip: `{{t('When rejected or returned, the workflow will be terminated immediately.', { ns: "${NAMESPACE}" })}}`, - }, - { - value: !0, - label: `{{t('Branch mode', { ns: "${NAMESPACE}" })}}`, - tooltip: `{{t('Could run different branch based on result.', { ns: "${NAMESPACE}" })}}`, - }, - ]), - default: !1, - }, - assignees: { - type: 'array', - title: `{{t("Assignees", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'ArrayItems', - 'x-component-props': { - className: css` - &[disabled] { - > .ant-formily-array-base-addition { - display: none; - } - > .ant-formily-array-items-item .ant-space-item:not(:nth-child(2)) { - display: none; - } - } - `, - }, - items: { - type: 'void', - 'x-component': 'Space', - 'x-component-props': { - className: css` - width: 100%; - &.ant-space.ant-space-horizontal { - flex-wrap: nowrap; - } - > .ant-space-item:nth-child(2) { - flex-grow: 1; - } - `, - }, - properties: { - sort: { type: 'void', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems.SortHandle' }, - input: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'AssigneesSelect' }, - remove: { type: 'void', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems.Remove' }, - }, - }, - required: !0, - properties: { - add: { - type: 'void', - title: `{{t("Add assignee", { ns: "${NAMESPACE}" })}}`, - 'x-component': 'AssigneesAddition', - }, - }, - }, - negotiation: { - type: 'number', - title: `{{t("Negotiation mode", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'NegotiationConfig', - default: 0, - }, - order: { - type: 'boolean', - title: `{{t("Order", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'RadioWithTooltip', - 'x-component-props': { - options: [ - { - label: `{{t("Parallelly", { ns: "${NAMESPACE}" })}}`, - value: !1, - tooltip: `{{t("Multiple approvers can approve in any order.", { ns: "${NAMESPACE}" })}}`, - }, - { - label: `{{t("Sequentially", { ns: "${NAMESPACE}" })}}`, - value: !0, - tooltip: `{{t("Multiple approvers in sequential order.", { ns: "${NAMESPACE}" })}}`, - }, - ], - }, - default: !1, - }, - endOnReject: { - type: 'boolean', - 'x-decorator': 'FormItem', - 'x-component': 'Checkbox', - 'x-content': `{{t("End the workflow after rejection branch", { ns: "${NAMESPACE}" })}}`, - description: `{{t("When checked, the workflow will terminate when the rejection branch ends.", { ns: "${NAMESPACE}" })}}`, - 'x-reactions': [{ dependencies: ['.branchMode'], fulfill: { state: { visible: '{{$deps[0]}}' } } }], - }, - applyDetail: { - type: 'void', - title: `{{t("Approver's interface", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'SchemaConfigButton', - properties: { applyDetail: { type: 'void', 'x-component': 'SchemaConfig' } }, - required: !0, - }, - }); - T(this, 'options', [ - { - label: `{{t('Passthrough mode', { ns: "${NAMESPACE}" })}}`, - key: 'false', - value() { - return { branchMode: !1, applyDetail: uid() }; - }, - }, - { - label: `{{t('Branch mode', { ns: "${NAMESPACE}" })}}`, - key: 'true', - value() { - return { branchMode: !0, applyDetail: uid() }; - }, - }, - ]); - T(this, 'Component', ot); - T(this, 'components', { - ArrayItems: ArrayItems, - SchemaConfigButton: et, - SchemaConfig: Ro, - AssigneesSelect: jo, - NegotiationConfig: it, - RadioWithTooltip: RadioWithTooltip, - AssigneesAddition: st, - }); - } - isAvailable({ workflow: n, upstream: a, branchIndex: s }) { - return !( - n.type !== 'approval' || at(a, s, (i, v) => (i == null ? void 0 : i.type) === 'approval' && v === b.RETURNED) - ); - } -} diff --git a/packages/plugins/@hera/plugin-approval/src/client/ApprovalProcessAddActionButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/ApprovalProcessAddActionButton.tsx deleted file mode 100644 index a7c4f857c..000000000 --- a/packages/plugins/@hera/plugin-approval/src/client/ApprovalProcessAddActionButton.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { SchemaInitializer } from '@nocobase/client'; -import { NAMESPACE } from '../locale'; -import { b, ie } from './refined'; - -export const ApprovalProcessAddActionButton = new SchemaInitializer({ - name: 'ApprovalProcessAddActionButton', - title: '{{t("Configure actions")}}', - items: [ - { - name: `action-${b.APPROVED}`, - type: 'item', - title: `{{t("Approve", { ns: "${NAMESPACE}" })}}`, - Component: ie, - action: b.APPROVED, - actionProps: { type: 'primary' }, - }, - { - name: `action-${b.REJECTED}`, - type: 'item', - title: `{{t("Reject", { ns: "${NAMESPACE}" })}}`, - Component: ie, - action: b.REJECTED, - actionProps: { danger: !0 }, - }, - { - name: `action-${b.RETURNED}`, - type: 'item', - title: `{{t("Return", { ns: "${NAMESPACE}" })}}`, - Component: ie, - action: b.RETURNED, - }, - ], -}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/ApprovalProcessAddBlockButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/ApprovalProcessAddBlockButton.tsx deleted file mode 100644 index 8ebe92f34..000000000 --- a/packages/plugins/@hera/plugin-approval/src/client/ApprovalProcessAddBlockButton.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { SchemaInitializer, gridRowColWrap, usePlugin } from '@nocobase/client'; -import PluginWorkflow, { - useAvailableUpstreams, - useFlowContext, - useNodeContext, useTrigger -} from '@nocobase/plugin-workflow/client'; -import { NAMESPACE } from '../locale'; -import { Lo, Jo } from './refined'; - -export const ApprovalProcessAddBlockButton = new SchemaInitializer({ - name: 'ApprovalProcessAddBlockButton', - wrap: gridRowColWrap, - title: "{{t('Add block')}}", - items: [ - { - name: 'approval', - type: 'itemGroup', - title: `{{t("Approval blocks", { ns: "${NAMESPACE}" })}}`, - children: [ - { name: 'detail', type: 'item', title: '{{t("Details")}}', Component: Lo }, - { name: 'actions', type: 'item', title: '{{t("Actions")}}', Component: Jo }, - ], - }, - { - type: 'itemGroup', - name: 'dataBlocks', - title: '{{t("Data blocks")}}', - checkChildrenLength: !0, - useChildren() { - var l; - const e = usePlugin(PluginWorkflow), { workflow: t } = useFlowContext(), n = useTrigger(), a = useNodeContext(), s = useAvailableUpstreams(a), i = [(l = n.useInitializers) == null ? void 0 : l.call(n, t.config)].filter(Boolean), v = s - .map((d) => { - var h; - const m = e.instructions.get(d.type); - return (h = m == null ? void 0 : m.useInitializers) == null ? void 0 : h.call(m, d); - }) - .filter(Boolean); - return [ - ...i, - ...(v.length - ? [{ name: 'nodes', type: 'subMenu', title: '{{t("Node result", { ns: "workflow" })}}', children: v }] - : []), - ].filter(Boolean); - }, - }, - { - name: 'others', - type: 'itemGroup', - title: '{{t("Other blocks")}}', - children: [{ name: 'markdown', type: 'item', title: '{{t("Markdown")}}', component: 'MarkdownBlockInitializer' }], - }, - ], -}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/common/Cn.ApprovalTodos.tsx b/packages/plugins/@hera/plugin-approval/src/client/common/Cn.ApprovalTodos.tsx new file mode 100644 index 000000000..a48f6416f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/common/Cn.ApprovalTodos.tsx @@ -0,0 +1,94 @@ +import { approvalStatusOptions } from '../constants'; +import { NAMESPACE } from '../locale'; + +export const CollectionApprovalTodos = { + title: `{{t("Approval todos", { ns: "${NAMESPACE}" })}}`, + name: 'approvalRecords', + fields: [ + { + type: 'bigInt', + name: 'approvalId', + interface: 'number', + uiSchema: { type: 'number', title: 'ID', 'x-component': 'InputNumber' }, + }, + { + type: 'belongsTo', + name: 'user', + target: 'users', + foreignKey: 'userId', + interface: 'm2o', + uiSchema: { + type: 'number', + title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'RemoteSelect', + 'x-component-props': { fieldNames: { label: 'nickname', value: 'id' }, service: { resource: 'users' } }, + }, + }, + { + type: 'belongsTo', + name: 'node', + target: 'flow_nodes', + foreignKey: 'nodeId', + interface: 'm2o', + isAssociation: true, + uiSchema: { + type: 'number', + title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'RemoteSelect', + 'x-component-props': { fieldNames: { label: 'title', value: 'id' }, service: { resource: 'flow_nodes' } }, + }, + }, + { + type: 'belongsTo', + name: 'workflow', + target: 'workflows', + foreignKey: 'workflowId', + interface: 'm2o', + uiSchema: { + type: 'number', + title: '{{t("Workflow", { ns: "workflow" })}}', + 'x-component': 'RemoteSelect', + 'x-component-props': { fieldNames: { label: 'title', value: 'id' }, service: { resource: 'workflows' } }, + }, + }, + { + type: 'integer', + name: 'status', + interface: 'select', + uiSchema: { + type: 'number', + title: '{{t("Status", { ns: "workflow" })}}', + 'x-component': 'Select', + enum: approvalStatusOptions, + }, + }, + { + type: 'text', + name: 'comment', + interface: 'markdown', + uiSchema: { type: 'string', 'x-component': 'Markdown', title: `{{t("Comment", { ns: "${NAMESPACE}" })}}` }, + }, + { + name: 'createdAt', + type: 'date', + interface: 'createdAt', + uiSchema: { + type: 'datetime', + title: '{{t("Created at")}}', + 'x-component': 'DatePicker', + 'x-component-props': { showTime: true }, + }, + }, + { + name: 'updatedAt', + type: 'date', + interface: 'updatedAt', + uiSchema: { + type: 'datetime', + title: '{{t("Updated at")}}', + 'x-component': 'DatePicker', + 'x-component-props': { showTime: true }, + }, + }, + ], +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/common/Pd.FormBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/common/Pd.FormBlock.tsx new file mode 100644 index 000000000..59b03c3e0 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/common/Pd.FormBlock.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { + BlockRequestContext_deprecated, + CollectionProvider_deprecated, + FormActiveFieldsProvider, + FormBlockContext, + FormProvider, + FormV2, + RecordProvider, + useAPIClient, + useAssociationNames, + useDesignable, +} from '@nocobase/client'; +import { RecursionField, createForm, useField, useFieldSchema } from '@nocobase/schema'; +import { Fragment, useContext, useMemo, useRef } from 'react'; +import { useContextApprovalExecution } from '../usage/approval-block/common/Pd.ApprovalExecution'; + +export const FormBlockProvider = (props) => { + const context = useContextApprovalExecution(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const formBlockRef = useRef(null); + const { getAssociationAppends } = useAssociationNames(); + const { appends, updateAssociationValues } = getAssociationAppends(); + // @ts-ignore + const snapshot = context?.snapshot; + 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/src/client/configuration/index.ts b/packages/plugins/@hera/plugin-approval/src/client/configuration/index.ts new file mode 100644 index 000000000..9adbaf6f0 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/index.ts @@ -0,0 +1,12 @@ +import { Plugin } from '@nocobase/client'; +import PluginKitApprovalInstruction from './instruction'; +import PluginKitApprovalTrigger from './trigger'; + +export default class PluginKitApprovalConfiguration extends Plugin { + async afterAdd() { + this.pm.add(PluginKitApprovalTrigger); + this.pm.add(PluginKitApprovalInstruction); + } + async beforeLoad() {} + async load() {} +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/Pd.ContextApproverConfig.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/Pd.ContextApproverConfig.tsx new file mode 100644 index 000000000..776fbeed1 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/Pd.ContextApproverConfig.tsx @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const ContextApproverConfig = createContext({}) as any; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/VC.ApprovalInstructionNode.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/VC.ApprovalInstructionNode.tsx new file mode 100644 index 000000000..752c85219 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/VC.ApprovalInstructionNode.tsx @@ -0,0 +1,60 @@ +import { css, useCompile } from '@nocobase/client'; +import { Branch, NodeDefaultView, useFlowContext, useStyles } from '@nocobase/plugin-workflow/client'; +import { Tag } from 'antd'; +import React from 'react'; +import { APPROVAL_ACTION_STATUS, approvalStatusConfigObj } from '../../constants'; + +// 审批节点组件 +export const ApprovalInstructionNode = ({ data }) => { + const { styles } = useStyles(); + const { config } = data; + return ( + + {config.branchMode ? ( +
+
+ +
+
+ ) : null} +
+ ); +}; + +// Child Component; 审批节点分支 +const BranchListComp = (props) => { + const { data } = props; + const compile = useCompile(); + const { nodes } = useFlowContext(); + + const { id, config } = data; + const isAllowReturned = config?.actions?.includes(APPROVAL_ACTION_STATUS.RETURNED); + const baseStatusArr = [APPROVAL_ACTION_STATUS.REJECTED, APPROVAL_ACTION_STATUS.APPROVED]; + const statusArr = isAllowReturned ? [...baseStatusArr, APPROVAL_ACTION_STATUS.RETURNED] : baseStatusArr; + + const isEntry = (targetStatus) => nodes.find((node) => node.upstreamId === id && node.branchIndex === targetStatus); + const isEnd = (targetStatus) => + targetStatus === APPROVAL_ACTION_STATUS.RETURNED || + (targetStatus === APPROVAL_ACTION_STATUS.REJECTED && config.endOnReject); + + return statusArr.map((targetStatus) => ( + + {compile(approvalStatusConfigObj[targetStatus].label)} + + } + /> + )); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.AssigneesAddition.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.AssigneesAddition.tsx new file mode 100644 index 000000000..328f2169c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.AssigneesAddition.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { PlusOutlined } from '@ant-design/icons'; +import { ArrayItems } from '@formily/antd-v5'; +import { useWorkflowExecuted } from '@nocobase/plugin-workflow/client'; +import { Button, Popover, Space } from 'antd'; +import { useCallback, useState } from 'react'; +import { useTranslation } from '../../../locale'; + +// 添加审批人(选择/查询) +export const AssigneesAddition = () => { + const isWorkflowExecuted = useWorkflowExecuted(); + + const array = ArrayItems.useArray(); + const [isOpen, setIsOpen] = useState(false); + const { t } = useTranslation(); + + const onSlecet = useCallback(() => { + array.field.push(''); + setIsOpen(false); + }, [array.field]); + const onQuery = useCallback(() => { + array.field.push({ filter: {} }); + setIsOpen(false); + }, [array.field]); + + return ( + } + > + + + ); +}; + +// Child Component +const PopoverContent = ({ onSlecet, onQuery }) => { + const { t } = useTranslation(); + return ( + + + + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.AssigneesSelect.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.AssigneesSelect.tsx new file mode 100644 index 000000000..b94c96ebb --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.AssigneesSelect.tsx @@ -0,0 +1,62 @@ +import { RemoteSelect, SchemaComponent, Variable, useCollectionFilterOptions, useToken } from '@nocobase/client'; +import { FilterDynamicComponent, useWorkflowVariableOptions } from '@nocobase/plugin-workflow/client'; +import { useField } from '@nocobase/schema'; +import { default as React } from 'react'; + +// 添加审批人-选择器 +export const AssigneesSelect = (props) => { + if (typeof props.value === 'object' && props.value) { + return ; + } else { + return ; + } +}; + +function isUserKeyField(field) { + if (field.isForeignKey) { + return field.target === 'users'; + } else { + return field.collectionName === 'users' && field.name === 'id'; + } +} + +const AssigneesSelectNormal = ({ value, onChange }) => { + const scope = useWorkflowVariableOptions({ types: [isUserKeyField] }); + return ( + + + + ); +}; + +const AssigneesSelectCustom = () => { + const field = useField(); + const currentFormFields = useCollectionFilterOptions('users'); + const { token } = useToken(); + return ( +
+ +
+ ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.ContentTooltip.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.ContentTooltip.tsx new file mode 100644 index 000000000..4c0a5dc64 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.ContentTooltip.tsx @@ -0,0 +1,22 @@ +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { css, useCompile } from '@nocobase/client'; +import { Tooltip } from 'antd'; +import React from 'react'; + +export const ContentTooltip = ({ content, tooltip }) => { + const compile = useCompile(); + return ( + <> + + {compile(content)} + + {tooltip && {}} + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.NegotiationConfig.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.NegotiationConfig.tsx new file mode 100644 index 000000000..d35fded63 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.NegotiationConfig.tsx @@ -0,0 +1,76 @@ +// @ts-nocheck +import React from 'react'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { InputNumber, css } from '@nocobase/client'; +import { Radio, Tooltip } from 'antd'; +import { useCallback } from 'react'; +import { useTranslation } from '../../../locale'; + +// 协商模式 +export const NegotiationConfig = ({ value, onChange }) => { + const { t } = useTranslation(); + const percentSign = value > 0 && value < 1 ? '%' : value; + const onChangeRadio = useCallback( + ({ target }) => { + if (target.value !== percentSign) { + onChange(target.value === '%' ? 0.5 : target.value); + } + }, + [percentSign, onChange], + ); + const onChangeInput = useCallback( + (val) => { + onChange(val / 100); + }, + [onChange], + ); + return ( +
+ + + + {t('Or')} + + + + + + {t('And')} + + + + + + {t('Voting')} + + + + + {percentSign === '%' ? ( + + ) : null} +
+ ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.SchemaConfigButtonApprover.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.SchemaConfigButtonApprover.tsx new file mode 100644 index 000000000..71bc9135f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approval-config/VC.SchemaConfigButtonApprover.tsx @@ -0,0 +1,14 @@ +import { useActionContext } from '@nocobase/client'; +import React from 'react'; +import { SchemaConfigButton } from '../../trigger/VC.ConfigButton'; +import { ContextApproverConfig } from '../Pd.ContextApproverConfig'; + +// 审批人操作界面->进入配置按钮 +export const SchemaConfigButtonApprover = (props) => { + const { setFormValueChanged } = useActionContext(); + return ( + + + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Iz.ApproverActionConfig.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Iz.ApproverActionConfig.tsx new file mode 100644 index 000000000..43bc69b4c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Iz.ApproverActionConfig.tsx @@ -0,0 +1,34 @@ +import { SchemaInitializer } from '@nocobase/client'; +import { NAMESPACE } from '../../../locale'; +import { APPROVAL_ACTION_STATUS } from '../../../constants'; +import { ApprovalActionConfigComponent } from './VC.ApprovalActionConfig'; + +export const ApproverActionConfigInitializer = new SchemaInitializer({ + name: 'ApprovalProcessAddActionButton', + title: '{{t("Configure actions")}}', + items: [ + { + name: `action-${APPROVAL_ACTION_STATUS.APPROVED}`, + type: 'item', + title: `{{t("Approve", { ns: "${NAMESPACE}" })}}`, + Component: ApprovalActionConfigComponent, + action: APPROVAL_ACTION_STATUS.APPROVED, + actionProps: { type: 'primary' }, + }, + { + name: `action-${APPROVAL_ACTION_STATUS.REJECTED}`, + type: 'item', + title: `{{t("Reject", { ns: "${NAMESPACE}" })}}`, + Component: ApprovalActionConfigComponent, + action: APPROVAL_ACTION_STATUS.REJECTED, + actionProps: { danger: true }, + }, + { + name: `action-${APPROVAL_ACTION_STATUS.RETURNED}`, + type: 'item', + title: `{{t("Return", { ns: "${NAMESPACE}" })}}`, + Component: ApprovalActionConfigComponent, + action: APPROVAL_ACTION_STATUS.RETURNED, + }, + ], +}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Iz.ApproverAddBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Iz.ApproverAddBlock.tsx new file mode 100644 index 000000000..d991efb9a --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Iz.ApproverAddBlock.tsx @@ -0,0 +1,83 @@ +import { SchemaInitializer, gridRowColWrap, usePlugin } from '@nocobase/client'; +import PluginWorkflow, { + useAvailableUpstreams, + useFlowContext, + useNodeContext, + useTrigger, +} from '@nocobase/plugin-workflow/client'; +import { NAMESPACE } from '../../../locale'; +import { ApproverAddBlockComponent } from './VC.ApproverAddBlock'; +import { ApproverAddBlockKit } from './VC.ApproverAddBlockKit'; + +export const ApproverAddBlockInitializer = new SchemaInitializer({ + name: 'ApprovalProcessAddBlockButton', + wrap: gridRowColWrap, + title: "{{t('Add block')}}", + items: [ + { + name: 'approval', + type: 'itemGroup', + title: `{{t("Approval blocks", { ns: "${NAMESPACE}" })}}`, + children: [ + { + name: 'detail', + type: 'item', + title: '{{t("Details")}}', + Component: ApproverAddBlockComponent, + }, + { + name: 'actions', + type: 'item', + title: '{{t("Actions")}}', + Component: ApproverAddBlockKit, + }, + ], + }, + { + type: 'itemGroup', + name: 'dataBlocks', + title: '{{t("Data blocks")}}', + checkChildrenLength: true, + useChildren() { + const workflowPlugin = usePlugin(PluginWorkflow); + const { workflow } = useFlowContext(); + const trigger = useTrigger(); + const currentNodeContext = useNodeContext(); + const nodes = useAvailableUpstreams(currentNodeContext); + const triggerInitializers = [trigger.useInitializers?.call(trigger, workflow.config)].filter(Boolean); + const nodeBlockInitializers = nodes + .map((node) => { + const instruction = workflowPlugin.instructions.get(node.type); + return instruction?.useInitializers?.call(instruction, node); + }) + .filter(Boolean); + return [ + ...triggerInitializers, + ...(nodeBlockInitializers.length + ? [ + { + name: 'nodes', + type: 'subMenu', + title: '{{t("Node result", { ns: "workflow" })}}', + children: nodeBlockInitializers, + }, + ] + : []), + ].filter(Boolean); + }, + }, + { + name: 'others', + type: 'itemGroup', + title: '{{t("Other blocks")}}', + children: [ + { + name: 'markdown', + type: 'item', + title: '{{t("Markdown")}}', + component: 'MarkdownBlockInitializer', + }, + ], + }, + ], +}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.ActionBarProvider.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.ActionBarProvider.tsx new file mode 100644 index 000000000..28e9a3b44 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.ActionBarProvider.tsx @@ -0,0 +1,3 @@ +export const ActionBarProvider = (props) => { + return props.children; +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.ApprovalActionProvider.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.ApprovalActionProvider.tsx new file mode 100644 index 000000000..dcf362b53 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.ApprovalActionProvider.tsx @@ -0,0 +1,3 @@ +export const ApprovalActionProvider = (props) => { + return props.children; +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.SchemaConfigButtonContext.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.SchemaConfigButtonContext.tsx new file mode 100644 index 000000000..751aa1424 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/Pd.SchemaConfigButtonContext.tsx @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const ContextApproverBlock = createContext({}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApprovalActionConfig.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApprovalActionConfig.tsx new file mode 100644 index 000000000..85c1bbd05 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApprovalActionConfig.tsx @@ -0,0 +1,28 @@ +import { ActionInitializer, useSchemaInitializerItem } from '@nocobase/client'; +import React from 'react'; + +export const ApprovalActionConfigComponent = () => { + const itemConfig = useSchemaInitializerItem(); + const { action, actionProps = {}, ...restItemConfig } = itemConfig; + + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApprovalFormBlockProvider.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApprovalFormBlockProvider.tsx new file mode 100644 index 000000000..41f27229c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApprovalFormBlockProvider.tsx @@ -0,0 +1,67 @@ +import { + BlockRequestContext_deprecated, + CollectionProvider_deprecated, + FormActiveFieldsProvider, + FormBlockContext, + FormV2, + RecordProvider, + useAPIClient, + useAssociationNames, + useDesignable, +} from '@nocobase/client'; +import { RecursionField, createForm, useField, useFieldSchema } from '@nocobase/schema'; +import React, { Fragment, useContext, useMemo, useRef } from 'react'; + +export const ApprovalFormBlockProvider = (props) => { + const fieldSchema = useFieldSchema(); + const field = useField(); + const formBlockRef = useRef(null); + const { getAssociationAppends } = useAssociationNames(); + const { appends, updateAssociationValues } = getAssociationAppends(); + const { findComponent } = useDesignable(); + const ContainerFormComp = findComponent(field.component?.[0]) || Fragment; + const form = useMemo(() => createForm({}), []); + const params = useMemo(() => ({ ...appends, ...props.params }), [appends, props.params]); + const service = useMemo(() => ({ loading: false, data: { data: {} } }), []); + 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/src/client/configuration/instruction/approver-interface/VC.ApproverAddBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverAddBlock.tsx new file mode 100644 index 000000000..65b0b92e1 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverAddBlock.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { + SchemaInitializerItem, + createReadPrettyFormBlockSchema, + parseCollectionName, + useRecordCollectionDataSourceItems, + useSchemaInitializer, + useSchemaInitializerItem, + useSchemaTemplateManager, +} from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import _ from 'lodash'; + +export const ApproverAddBlockComponent = () => { + const { workflow } = useFlowContext(); + const [dataSourceName, collection] = parseCollectionName(workflow.config.collection); + const itemConfig = useSchemaInitializerItem(); + const { insert } = useSchemaInitializer(); + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const items = useRecordCollectionDataSourceItems('FormItem'); + + const onClick = async ({ item }) => { + const template = item.template ? await getTemplateSchemaByMode(item) : null; + const targetSchema = createReadPrettyFormBlockSchema({ + actionInitializers: null, + resource: collection, + collection, + dataSource: dataSourceName, + template, + settings: 'blockSettings:singleDataDetails', + }); + + delete targetSchema['x-acl-action-props']; + delete targetSchema['x-acl-action']; + + const [firstPropertyKey] = Object.keys(targetSchema.properties); + + _.set(targetSchema.properties[firstPropertyKey], 'x-component-props.useProps', '{{useApprovalDetailBlockProps}}'); + + insert(targetSchema); + }; + return ; +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverAddBlockKit.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverAddBlockKit.tsx new file mode 100644 index 000000000..f4a39dbc8 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverAddBlockKit.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { InitializerWithSwitch, createFormBlockSchema, useSchemaInitializerItem } from '@nocobase/client'; +import _ from 'lodash'; +import { NAMESPACE } from '../../../locale'; +import { APPROVAL_ACTION_STATUS } from '../../../constants'; +import { flatSchemaArray } from '../../../constants'; + +// 创建区块-操作switch +export const ApproverAddBlockKit = () => { + const itemConfig = useSchemaInitializerItem(); + const formSchema = createFormBlockSchema({ + actionInitializers: 'ApprovalProcessAddActionButton', + actions: { + approve: { + type: 'void', + title: `{{t("Approve", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'ApprovalActionProvider', + 'x-decorator-props': { status: APPROVAL_ACTION_STATUS.APPROVED }, + 'x-component': 'Action', + 'x-component-props': { type: 'primary', htmlType: 'submit', useAction: '{{ useSubmit }}' }, + 'x-designer': 'Action.Designer', + 'x-designer-props': {}, + 'x-action': `${APPROVAL_ACTION_STATUS.APPROVED}`, + }, + }, + resource: 'approvalRecords', + collection: 'approvalRecords', + }); + + delete formSchema['x-acl-action-props']; + delete formSchema['x-acl-action']; + formSchema['x-decorator'] = 'ApprovalFormBlockProvider'; + + const [firstKey] = Object.keys(formSchema.properties); + const firstProperty = formSchema.properties[firstKey]; + + _.set(firstProperty, 'x-component-props.useProps', '{{useApprovalFormBlockProps}}'); + + const [firstSchema] = flatSchemaArray( + formSchema.properties[firstKey], + (property) => property['x-component'] === 'ActionBar', + ); + + firstSchema['x-decorator'] = 'ActionBarProvider'; + + _.set(firstSchema, 'x-component-props.style', { marginTop: '1.5em', flexWrap: 'wrap' }); + + formSchema['x-block'] = 'action-form'; + const item = { ...itemConfig, schema: formSchema }; + + return ; +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverBlock.tsx new file mode 100644 index 000000000..a9521fb62 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverBlock.tsx @@ -0,0 +1,95 @@ +import { SchemaComponent, useAPIClient, useFormBlockContext, useRequest } from '@nocobase/client'; +import { uid } from '@nocobase/utils/client'; +import { default as React, useCallback, useContext } from 'react'; +import { useForm } from '@nocobase/schema'; +import { Spin } from 'antd'; +import { flatSchemaArray } from '../../../constants'; + +import { ApprovalFormBlockProvider } from './VC.ApprovalFormBlockProvider'; +import { FormBlockProvider } from '../../../common/Pd.FormBlock'; +import { ActionBarProvider } from './Pd.ActionBarProvider'; +import { ApprovalActionProvider } from './Pd.ApprovalActionProvider'; +import { DetailsBlockProvider, SimpleDesigner } from '@nocobase/plugin-workflow/client'; +import { ContextApproverBlock } from './Pd.SchemaConfigButtonContext'; +import { useApprovalFormBlockProps } from './useApprovalFormBlockProps'; +import { ContextApproverConfig } from '../Pd.ContextApproverConfig'; + +export const ApproverBlock = ({ value: srcID, onChange }) => { + const apiClient = useAPIClient(); + // const workflowPlugin = usePlugin(PluginWorkflow); + const { values, setValuesIn } = useForm(); + const { setFormValueChanged } = useContext(ContextApproverConfig) as any; + // const current = useNodeContext(); + // const nodes = useAvailableUpstreams(current); + const onChangeFunc = useCallback( + (data) => { + const flatSet = flatSchemaArray( + data.toJSON(), + (field) => field['x-decorator'] === 'ApprovalFormBlockProvider', + ).reduce((accSet, curr) => { + flatSchemaArray(curr, (field) => field['x-component'] === 'Action').forEach((field) => { + accSet.add(Number.parseInt(field['x-action'], 10)); + }); + return accSet; + }, new Set()); + + if (flatSet.size !== values.actions?.length || !values.actions.every((action) => flatSet.has(action))) { + setFormValueChanged(true); + } + setValuesIn('actions', [...flatSet]); + }, + [setFormValueChanged, setValuesIn, values.actions], + ); + + const { data, loading } = useRequest(async () => { + if (srcID) { + const { data: serviceData } = await apiClient.request({ url: `uiSchemas:getJsonSchema/${srcID}` }); + if (serviceData?.data?.['x-uid'] === srcID) { + return serviceData.data; + } + } + const id = srcID ?? uid(); + const schema = { + type: 'void', + name: id, + 'x-uid': id, + 'x-component': 'Grid', + 'x-initializer': 'ApprovalProcessAddBlockButton', + properties: {}, + }; + + await apiClient.resource('uiSchemas').insert({ values: schema }); + onChange(id); + return schema; + }); + + if (loading) { + return ; + } + + return ( + + { + return { run() {} }; + }, + useApprovalFormBlockProps, + useDetailsBlockProps: useFormBlockContext, + }} + components={{ + FormBlockProvider, + ApprovalFormBlockProvider, + ActionBarProvider, + ApprovalActionProvider, + DetailsBlockProvider, + SimpleDesigner, + }} + // @ts-ignore + schema={data} + onChange={onChangeFunc} + /> + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverInterface.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverInterface.tsx new file mode 100644 index 000000000..e5a72a2b4 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/VC.ApproverInterface.tsx @@ -0,0 +1,65 @@ +import { + ExtendCollectionsProvider, + RecordProvider, + SchemaComponent, + SchemaComponentContext, + css, +} from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { uid } from '@nocobase/utils/client'; +import React, { useContext, useState } from 'react'; +import { CollectionApprovalTodos } from '../../../common/Cn.ApprovalTodos'; +import { NAMESPACE } from '../../../locale'; +import { ApproverBlock } from './VC.ApproverBlock'; + +// 审批人操作界面 +export const ApproverInterfaceComponent = () => { + const context = useContext(SchemaComponentContext); + const [, setId] = useState(uid()); + const { workflow } = useFlowContext(); + const commentFields = CollectionApprovalTodos.fields.filter((field) => field.name === 'comment'); + return ( + + setId(uid()), + designable: !workflow.executed, + }} + > + {/* @ts-ignore */} + + + + + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/useApprovalFormBlockProps.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/useApprovalFormBlockProps.tsx new file mode 100644 index 000000000..f6124475e --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/approver-interface/useApprovalFormBlockProps.tsx @@ -0,0 +1,6 @@ +import { useFormBlockContext } from '@nocobase/client'; + +export function useApprovalFormBlockProps(e) { + const { form } = useFormBlockContext(); + return { form }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/index.ts b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/index.ts new file mode 100644 index 000000000..249a4aad5 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/index.ts @@ -0,0 +1,17 @@ +import { Plugin } from '@nocobase/client'; +import PluginWorkflow from '@nocobase/plugin-workflow/client'; +import { ApprovalInstruction } from './node.ApprovalInstruction'; +import { ApproverActionConfigInitializer } from './approver-interface/Iz.ApproverActionConfig'; +import { ApproverAddBlockInitializer } from './approver-interface/Iz.ApproverAddBlock'; + +export default class PluginKitApprovalInstruction extends Plugin { + async afterAdd() {} + async beforeLoad() {} + async load() { + const workflowPlugin = this.app.pm.get(PluginWorkflow); + workflowPlugin.registerInstruction('approval', ApprovalInstruction); + + this.app.schemaInitializerManager.add(ApproverActionConfigInitializer); + this.app.schemaInitializerManager.add(ApproverAddBlockInitializer); + } +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/node.ApprovalInstruction.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/node.ApprovalInstruction.tsx new file mode 100644 index 000000000..36f19f0fd --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/node.ApprovalInstruction.tsx @@ -0,0 +1,196 @@ +import { ArrayItems } from '@formily/antd-v5'; +import { css } from '@nocobase/client'; +import { Instruction, RadioWithTooltip } from '@nocobase/plugin-workflow/client'; +import { uid } from '@nocobase/utils/client'; +import React from 'react'; +import { APPROVAL_ACTION_STATUS } from '../../constants'; +import { NAMESPACE } from '../../locale'; +import { AssigneesAddition } from './approval-config/VC.AssigneesAddition'; +import { AssigneesSelect } from './approval-config/VC.AssigneesSelect'; +import { ContentTooltip } from './approval-config/VC.ContentTooltip'; +import { NegotiationConfig } from './approval-config/VC.NegotiationConfig'; +import { ApproverInterfaceComponent } from './approver-interface/VC.ApproverInterface'; +import { SchemaConfigButtonApprover } from './approval-config/VC.SchemaConfigButtonApprover'; +import { ApprovalInstructionNode } from './VC.ApprovalInstructionNode'; +import { isApprovalReturnFunc } from './utils'; + +// 工作流节点 nodes - 人工处理->审批 +export class ApprovalInstruction extends Instruction { + title = `{{t("Approval", { ns: "${NAMESPACE}" })}}`; + type = 'approval'; + group = 'manual'; + description = `{{t("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.", { ns: "${NAMESPACE}" })}}`; + // 审批节点类别 + options = [ + { + label: `{{t('Passthrough mode', { ns: "${NAMESPACE}" })}}`, + key: 'false', + value() { + return { branchMode: false, applyDetail: uid() }; + }, + }, + { + label: `{{t('Branch mode', { ns: "${NAMESPACE}" })}}`, + key: 'true', + value() { + return { branchMode: true, applyDetail: uid() }; + }, + }, + ]; + // 审批节点表单设置 + fieldset = { + branchMode: { + type: 'boolean', + title: `{{t("Pass mode", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'Radio.Group', + 'x-component-props': { disabled: true }, + enum: [ + { + value: false, + label: ( + + ), + }, + { + value: true, + label: ( + + ), + }, + ], + default: false, + }, + assignees: { + type: 'array', + title: `{{t("Assignees", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems', + 'x-component-props': { + className: css` + &[disabled] { + > .ant-formily-array-base-addition { + display: none; + } + > .ant-formily-array-items-item .ant-space-item:not(:nth-child(2)) { + display: none; + } + } + `, + }, + items: { + type: 'void', + 'x-component': 'Space', + 'x-component-props': { + className: css` + width: 100%; + &.ant-space.ant-space-horizontal { + flex-wrap: nowrap; + } + > .ant-space-item:nth-child(2) { + flex-grow: 1; + } + `, + }, + properties: { + sort: { type: 'void', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems.SortHandle' }, + input: { type: 'string', 'x-decorator': 'FormItem', 'x-component': 'AssigneesSelect' }, + remove: { type: 'void', 'x-decorator': 'FormItem', 'x-component': 'ArrayItems.Remove' }, + }, + }, + required: true, + properties: { + add: { + type: 'void', + title: `{{t("Add assignee", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'AssigneesAddition', + }, + }, + }, + negotiation: { + type: 'number', + title: `{{t("Negotiation mode", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'NegotiationConfig', + default: 0, + }, + order: { + type: 'boolean', + title: `{{t("Order", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'RadioWithTooltip', + 'x-component-props': { + options: [ + { + label: `{{t("Parallelly", { ns: "${NAMESPACE}" })}}`, + value: false, + tooltip: `{{t("Multiple approvers can approve in any order.", { ns: "${NAMESPACE}" })}}`, + }, + { + label: `{{t("Sequentially", { ns: "${NAMESPACE}" })}}`, + value: true, + tooltip: `{{t("Multiple approvers in sequential order.", { ns: "${NAMESPACE}" })}}`, + }, + ], + }, + default: false, + }, + endOnReject: { + type: 'boolean', + 'x-decorator': 'FormItem', + 'x-component': 'Checkbox', + 'x-content': `{{t("End the workflow after rejection branch", { ns: "${NAMESPACE}" })}}`, + description: `{{t("When checked, the workflow will terminate when the rejection branch ends.", { ns: "${NAMESPACE}" })}}`, + 'x-reactions': [ + { + dependencies: ['.branchMode'], + fulfill: { + state: { + visible: '{{$deps[0]}}', + }, + }, + }, + ], + }, + applyDetail: { + type: 'void', + title: `{{t("Approver's interface", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'SchemaConfigButtonApprover', + properties: { + applyDetail: { + type: 'void', + 'x-component': 'SchemaConfig', + }, + }, + required: true, + }, + }; + // 审批节点组件 + Component = ApprovalInstructionNode; + components = { + ArrayItems, + SchemaConfigButtonApprover, + SchemaConfig: ApproverInterfaceComponent, + AssigneesSelect, + NegotiationConfig, + RadioWithTooltip, + AssigneesAddition, + }; + + isAvailable({ workflow, upstream, branchIndex }) { + const isApproval = workflow.type === 'approval'; + const isNotApprovalReturn = !isApprovalReturnFunc( + upstream, + branchIndex, + (currU, currB) => currU?.type === 'approval' && currB === APPROVAL_ACTION_STATUS.RETURNED, + ); + return isApproval && isNotApprovalReturn; + } +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/utils.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/utils.tsx new file mode 100644 index 000000000..4429e14d9 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/instruction/utils.tsx @@ -0,0 +1,20 @@ +export function isApprovalReturnFunc(upstream, branchIndex = null, filter) { + for (let currU = upstream, currB = branchIndex; currU; currU = currU.upstream) { + const currentUpstream = findCurrentUpstream(currU, currB); + if (filter(currentUpstream, currB)) { + return true; + } + currB = currU.branchIndex; + } + return false; +} + +function findCurrentUpstream(upstream, branchIndex = null) { + for (let currU = upstream, currB = branchIndex; currU; currU = currU.upstream) { + if (branchIndex != null) { + return currU; + } + currB = currU.branchIndex; + } + return null; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/VC.ConfigButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/VC.ConfigButton.tsx new file mode 100644 index 000000000..106e3e8fa --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/VC.ConfigButton.tsx @@ -0,0 +1,20 @@ +import { ActionContextProvider } from '@nocobase/client'; +import { Button } from 'antd'; +import React, { useState } from 'react'; +import { useTranslation } from '../../locale'; + +// 发起人操作界面->进入配置按钮 +export function SchemaConfigButton(props) { + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + return ( + <> + + + {props.children} + + + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/index.ts b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/index.ts new file mode 100644 index 000000000..c6d632a52 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/index.ts @@ -0,0 +1,20 @@ +import { Plugin } from '@nocobase/client'; +import PluginWorkflow from '@nocobase/plugin-workflow/client'; +import { ApprovalTrigger } from './node.ApprovalTrigger'; +import { LauncherActionConfigInitializer } from './launcher-interface/Iz.LauncherActionConfig'; +import { LauncherAddBlockButtonIntializer } from './launcher-interface/Iz.LauncherAddBlockButton'; + +export default class PluginKitApprovalTrigger extends Plugin { + async afterAdd() {} + async beforeLoad() {} + async load() { + const workflowPlugin = this.app.pm.get(PluginWorkflow); + workflowPlugin.registerTrigger('approval', ApprovalTrigger); + // workflowPlugin.registerInstruction('approval', ApprovalInstruction); + + this.app.schemaInitializerManager.add(LauncherActionConfigInitializer); + this.app.schemaInitializerManager.add(LauncherAddBlockButtonIntializer); + // this.app.schemaInitializerManager.add(ApproverActionConfigInitializer); + // this.app.schemaInitializerManager.add(ApproverAddBlockInitializer); + } +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Iz.LauncherActionConfig.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Iz.LauncherActionConfig.tsx new file mode 100644 index 000000000..3e1b824e4 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Iz.LauncherActionConfig.tsx @@ -0,0 +1,28 @@ +import { SchemaInitializer } from '@nocobase/client'; +import { NAMESPACE } from '../../../locale'; +import { APPROVAL_STATUS } from '../../../constants'; +import { LauncherActionConfigComponent } from './VC.LauncherActionConfig'; + +// 区块-配置操作 +export const LauncherActionConfigInitializer = new SchemaInitializer({ + name: 'ApprovalApplyAddActionButton', + title: '{{t("Configure actions")}}', + items: [ + { + name: 'submit', + type: 'item', + title: '{{t("Submit")}}', + Component: LauncherActionConfigComponent, + action: APPROVAL_STATUS.SUBMITTED, + actionProps: { type: 'primary' }, + disabled: true, + }, + { + name: 'save', + type: 'item', + title: `{{t("Save draft", { ns: "${NAMESPACE}" })}}`, + Component: LauncherActionConfigComponent, + action: APPROVAL_STATUS.DRAFT, + }, + ], +}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Iz.LauncherAddBlockButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Iz.LauncherAddBlockButton.tsx new file mode 100644 index 000000000..9d33e1f18 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Iz.LauncherAddBlockButton.tsx @@ -0,0 +1,38 @@ +import { SchemaInitializer, gridRowColWrap } from '@nocobase/client'; +import { NAMESPACE } from '../../../locale'; +import { LauncherAddBlockButtonComponent } from './VC.LauncherBlockButton'; + +// 创建区块 +export const LauncherAddBlockButtonIntializer = new SchemaInitializer({ + name: 'ApprovalApplyAddBlockButton', + wrap: gridRowColWrap, + title: '{{t("Add block")}}', + items: [ + { + name: 'forms', + type: 'itemGroup', + title: '{{t("Form")}}', + children: [ + { + name: 'form', + type: 'item', + title: `{{t("Apply form", { ns: "${NAMESPACE}" })}}`, + Component: LauncherAddBlockButtonComponent, + }, + ], + }, + { + name: 'others', + type: 'itemGroup', + title: '{{t("Other blocks")}}', + children: [ + { + name: 'markdown', + type: 'item', + title: '{{t("Markdown")}}', + Component: 'MarkdownBlockInitializer', + }, + ], + }, + ], +}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Sm.LauncherInterface.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Sm.LauncherInterface.tsx new file mode 100644 index 000000000..82d2e2ecb --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/Sm.LauncherInterface.tsx @@ -0,0 +1,33 @@ +import { css } from '@nocobase/client'; +import { NAMESPACE } from '../../../locale'; + +// 发起人操作界面 +export const getSchemaLauncherInterface = ({ values, dataSource, name }) => ({ + name: values.collection, + type: 'void', + properties: { + drawer: { + type: 'void', + title: `{{t("Initiator's interface", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'Action.Drawer', + 'x-component-props': { + className: css` + .ant-drawer-body { + background: const(--nb-box-bg); + } + `, + }, + properties: { + applyForm: { + type: 'string', + 'x-decorator': 'CollectionProvider_deprecated', + 'x-decorator-props': { + dataSource, + name, + }, + 'x-component': 'SchemaAddBlock', + }, + }, + }, + }, +}); diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherActionConfig.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherActionConfig.tsx new file mode 100644 index 000000000..3a3d8e743 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherActionConfig.tsx @@ -0,0 +1,31 @@ +import { ActionInitializer, useSchemaInitializerItem } from '@nocobase/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/src/client/configuration/trigger/launcher-interface/VC.LauncherBlockButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherBlockButton.tsx new file mode 100644 index 000000000..4ff0e6a4c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherBlockButton.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { + SchemaInitializerItem, + createFormBlockSchema, + useCollection_deprecated, + useRecordCollectionDataSourceItems, + useSchemaInitializer, + useSchemaInitializerItem, + useSchemaTemplateManager, +} from '@nocobase/client'; +import { NAMESPACE } from '../../../locale'; +import { flatSchemaArray } from '../../../constants'; +import { APPROVAL_STATUS } from '../../../constants'; + +// 创建区块-Initializer的component +export const LauncherAddBlockButtonComponent = () => { + const itemConfig = useSchemaInitializerItem(); + const { insert } = useSchemaInitializer(); + const collection = useCollection_deprecated(); + const { getTemplateSchemaByMode } = useSchemaTemplateManager(); + const items = useRecordCollectionDataSourceItems('FormItem'); + + const onClick = async ({ item }) => { + const template = item.template ? await getTemplateSchemaByMode(item) : null; + const { + ['x-acl-action-props']: deleteA, + ['x-acl-action']: deleteB, + ...formSchema + } = createFormBlockSchema({ + actionInitializers: 'ApprovalApplyAddActionButton', + actions: { + submit: { + type: 'void', + title: '{{t("Submit")}}', + 'x-decorator': 'ApplyActionStatusProvider', + 'x-decorator-props': { value: APPROVAL_STATUS.SUBMITTED }, + 'x-component': 'Action', + 'x-component-props': { type: 'primary', htmlType: 'submit', useAction: '{{ useSubmit }}' }, + 'x-designer': 'Action.Designer', + 'x-designer-props': {}, + 'x-action': `${APPROVAL_STATUS.SUBMITTED}`, + 'x-action-settings': { removable: false, assignedValues: {} }, + }, + withdraw: { + type: 'void', + title: `{{t("Withdraw", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'WithdrawActionProvider', + 'x-component': 'Action', + 'x-component-props': { + confirm: { + title: `{{t('Withdraw', { ns: "${NAMESPACE}" })}}`, + content: `{{t('Are you sure you want to withdraw it?', { ns: "${NAMESPACE}" })}}`, + }, + useAction: '{{ useWithdrawAction }}', + }, + 'x-designer': 'WithdrawActionDesigner', + 'x-action': 'withdraw', + }, + }, + dataSource: collection.dataSource, + resource: collection.name, + collection: collection.name, + template: template, + }); + + const [key] = Object.keys(formSchema.properties); + const [targetSchema] = flatSchemaArray( + formSchema.properties[key], + (property) => property['x-component'] === 'ActionBar', + ); + + targetSchema['x-decorator'] = 'ActionBarProvider'; + targetSchema['x-component-props'].style = { marginTop: '1.5em', flexWrap: 'wrap' }; + + insert(formSchema); + }; + + return ; +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherInterface.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherInterface.tsx new file mode 100644 index 000000000..030148c9b --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.LauncherInterface.tsx @@ -0,0 +1,21 @@ +import { SchemaComponent, parseCollectionName } from '@nocobase/client'; +import { useForm } from '@nocobase/schema'; +import React from 'react'; +import { SchemaAddBlock } from './VC.SchemaAddBlock'; +import { getSchemaLauncherInterface } from './Sm.LauncherInterface'; + +// 触发器-发起人的操作界面 +export const LauncherInterface = () => { + const { values } = useForm(); + const [dataSource, name] = parseCollectionName(values.collection); + const schema = getSchemaLauncherInterface({ values, dataSource, name }); + + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.SchemaAddBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.SchemaAddBlock.tsx new file mode 100644 index 000000000..b23d652ce --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/launcher-interface/VC.SchemaAddBlock.tsx @@ -0,0 +1,101 @@ +import { + SchemaComponent, + SchemaComponentContext, + SchemaComponentProvider, + useAPIClient, + useFormBlockProps, + useRequest, +} from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { uid } from '@nocobase/utils/client'; +import { Spin } from 'antd'; +import React, { useContext } from 'react'; + +// 发起人操作界面-创建区块 +export const SchemaAddBlock = ({ value, onChange }) => { + const api = useAPIClient(); + const { workflow } = useFlowContext(); + const { components } = useContext(SchemaComponentContext); + + // TODO: + // 获取对应数据表的表单Schema + const { data, loading } = useRequest(() => + E(this, null, function* () { + let m; + if (value) { + const { data: h } = yield api.request({ url: `uiSchemas:getJsonSchema/${value}` }); + if (((m = h.data) == null ? void 0 : m['x-uid']) === value) return h.data; + } + const l = uid(), + d = { + type: 'void', + name: l, + 'x-uid': l, + 'x-component': 'Grid', + 'x-initializer': 'ApprovalApplyAddBlockButton', + properties: {}, + }; + return yield api.resource('uiSchemas').insert({ values: d }), onChange(l), d; + }), + ); + + if (loading) { + return ; + } + + return ( + + + + ); +}; +function useSubmit() { + return { run() {} }; +} +function useWithdrawAction() { + return { run() {} }; +} +function ActionBarProvider(props) { + return props.children; +} +function ApplyActionStatusProvider(props) { + return props.children; +} +function WithdrawActionProvider(props) { + return null; +} +// TODO: +const E = (f, o, p) => + // eslint-disable-next-line promise/param-names + new Promise((r, G) => { + const u = ($) => { + try { + A(p.next($)); + } catch (U) { + G(U); + } + }, + y = ($) => { + try { + A(p.throw($)); + } catch (U) { + G(U); + } + }, + A = ($) => ($.done ? r($.value) : Promise.resolve($.value).then(u, y)); + A((p = p.apply(f, o)).next()); + }); diff --git a/packages/plugins/@hera/plugin-approval/src/client/ApprovalTrigger.tsx b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/node.ApprovalTrigger.tsx similarity index 60% rename from packages/plugins/@hera/plugin-approval/src/client/ApprovalTrigger.tsx rename to packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/node.ApprovalTrigger.tsx index f0db17158..336f2a4a5 100644 --- a/packages/plugins/@hera/plugin-approval/src/client/ApprovalTrigger.tsx +++ b/packages/plugins/@hera/plugin-approval/src/client/configuration/trigger/node.ApprovalTrigger.tsx @@ -6,20 +6,22 @@ import { useWorkflowAnyExecuted, } from '@nocobase/plugin-workflow/client'; import { useForm } from '@nocobase/schema'; -import { NAMESPACE } from '../locale'; -import { T, Vo, zo, useTranslation, w, x } from './refined'; -import { usePluginTranslation } from '../locale'; +import { SchemaConfigButton } from './VC.ConfigButton'; +import { NAMESPACE, usePluginTranslation } from '../../locale'; +import { LauncherInterface } from './launcher-interface/VC.LauncherInterface'; +// 工作流节点-审批触发器节点 export class ApprovalTrigger extends Trigger { sync = false; title = `{{t('Approval event', { ns: "${NAMESPACE}" })}}`; description = `{{t("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.", { ns: "${NAMESPACE}" })}}`; + // 触发器配置表 fieldset = { collection: { type: 'string', title: '{{t("Collection")}}', - required: !0, + required: true, 'x-decorator': 'FormItem', 'x-component': 'DataSourceCollectionCascader', 'x-disabled': '{{ useWorkflowAnyExecuted() }}', @@ -34,17 +36,17 @@ export class ApprovalTrigger extends Trigger { options: [ { label: `{{t("Initiate and approve in data blocks only", { ns: "${NAMESPACE}" })}}`, - value: !1, + value: false, tooltip: `{{t("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.", { ns: "${NAMESPACE}" })}}`, }, { label: `{{t("Initiate and approve in both data blocks and global approval blocks", { ns: "${NAMESPACE}" })}}`, - value: !0, + value: true, tooltip: `{{t("In addition to data blocks, a global approval block can also be used to initiates and processes approvals, which typically applies to administrative data.", { ns: "${NAMESPACE}" })}}`, }, ], }, - default: !1, + default: false, }, withdrawable: { type: 'boolean', @@ -59,8 +61,23 @@ export class ApprovalTrigger extends Trigger { description: `{{t("For initiating approvals, or viewing and manipulating initiated approvals.", { ns: "${NAMESPACE}" })}}`, 'x-decorator': 'FormItem', 'x-component': 'SchemaConfigButton', - 'x-reactions': [{ dependencies: ['collection'], fulfill: { state: { visible: '{{!!$deps[0]}}' } } }], - properties: { applyForm: { type: 'void', 'x-component': 'SchemaConfig', default: null } }, + 'x-reactions': [ + { + dependencies: ['collection'], + fulfill: { + state: { + visible: '{{!!$deps[0]}}', + }, + }, + }, + ], + properties: { + applyForm: { + type: 'void', + 'x-component': 'SchemaInitiatorForm', + default: null, + }, + }, }, appends: { type: 'array', @@ -71,40 +88,59 @@ export class ApprovalTrigger extends Trigger { 'x-component': 'AppendsTreeSelect', 'x-component-props': { title: 'Preload associations', - multiple: !0, + multiple: true, useCollection() { - const { values: n } = useForm(); - return n == null ? void 0 : n.collection; + const { values } = useForm(); + return values?.collection; }, }, - 'x-reactions': [{ dependencies: ['collection'], fulfill: { state: { visible: '{{!!$deps[0]}}' } } }], + 'x-reactions': [ + { + dependencies: ['collection'], + fulfill: { + state: { + visible: '{{!!$deps[0]}}', + }, + }, + }, + ], }, }; - scope = { useWorkflowAnyExecuted: useWorkflowAnyExecuted }; - components = { SchemaConfigButton: Vo, SchemaConfig: zo, RadioWithTooltip: RadioWithTooltip }; - isActionTriggerable = (n, a) => ['create', 'update'].includes(a.action) && !a.direct; + scope = { useWorkflowAnyExecuted }; + components = { + SchemaConfigButton, + SchemaInitiatorForm: LauncherInterface, + RadioWithTooltip, + }; - useVariables(n, a) { - var m; - const s = useCompile(), - { getCollectionFields: i } = useCollectionManager_deprecated(), - { t: v } = usePluginTranslation(), - l = [ - { - collectionName: n.collection, - name: 'data', - type: 'hasOne', - target: n.collection, - uiSchema: { title: v('Trigger data', { ns: 'workflow' }) }, - }, - ]; - return getCollectionFieldOptions( - w(x({ appends: ['data', ...(((m = n.appends) == null ? void 0 : m.map((h) => `data.${h}`)) || [])] }, a), { - fields: l, - compile: s, - getCollectionFields: i, - }), - ); + isActionTriggerable = (config, context) => { + return ['create', 'update'].includes(context.action) && !context.direct; + }; + + useVariables(config, options) { + // eslint-disable-next-line react-hooks/rules-of-hooks + const compile = useCompile(); + // eslint-disable-next-line react-hooks/rules-of-hooks + const { getCollectionFields } = useCollectionManager_deprecated(); + // eslint-disable-next-line react-hooks/rules-of-hooks + const { t } = usePluginTranslation(); + const rootFields = [ + { + collectionName: config.collection, + name: 'data', + type: 'hasOne', + target: config.collection, + uiSchema: { title: t('Trigger data', { ns: 'workflow' }) }, + }, + ]; + return getCollectionFieldOptions({ + // depth, + appends: ['data', ...(config.appends?.map((item) => `data.${item}`) || [])], + ...options, + fields: rootFields, + compile, + getCollectionFields, + }); } } diff --git a/packages/plugins/@hera/plugin-approval/src/client/constants.ts b/packages/plugins/@hera/plugin-approval/src/client/constants.ts new file mode 100644 index 000000000..de3f6a727 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/constants.ts @@ -0,0 +1,92 @@ +import { JOB_STATUS } from '@nocobase/plugin-workflow/client'; +import { NAMESPACE, lang } from './locale'; +// 审批状态 +export const APPROVAL_STATUS = { + DRAFT: 0, + RETURNED: 1, + SUBMITTED: 2, + PROCESSING: 3, + APPROVED: 4, + REJECTED: -1, +}; + +export const APPROVAL_ACTION_STATUS = { + ASSIGNED: null, + PENDING: 0, + RETURNED: 1, + APPROVED: 2, + REJECTED: -1, + CANCELED: -2, + WITHDRAWN: -3, +}; +export const approvalStatusOptions = [ + { value: APPROVAL_ACTION_STATUS.ASSIGNED, label: `{{t("Assigned", { ns: "${NAMESPACE}" })}}`, color: 'blue' }, + { value: APPROVAL_ACTION_STATUS.PENDING, label: `{{t("Pending", { ns: "${NAMESPACE}" })}}`, color: 'gold' }, + { value: APPROVAL_ACTION_STATUS.RETURNED, label: `{{t("Returned", { ns: "${NAMESPACE}" })}}`, color: 'purple' }, + { value: APPROVAL_ACTION_STATUS.APPROVED, label: `{{t("Approved", { ns: "${NAMESPACE}" })}}`, color: 'green' }, + { value: APPROVAL_ACTION_STATUS.REJECTED, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, color: 'red' }, + { value: APPROVAL_ACTION_STATUS.WITHDRAWN, label: `{{t("Withdrawn", { ns: "${NAMESPACE}" })}}` }, +]; +export const approvalStatusConfigObj = approvalStatusOptions.reduce( + (e, t) => + Object.assign(e, { + [t.value]: t, + }), + {}, +); +export const ApprovalStatusEnums = [ + { value: APPROVAL_STATUS.DRAFT, label: `{{t("Draft", { ns: "${NAMESPACE}" })}}`, editable: true }, + { + value: APPROVAL_STATUS.RETURNED, + label: `{{t("Returned", { ns: "${NAMESPACE}" })}}`, + color: 'purple', + editable: true, + }, + { value: APPROVAL_STATUS.SUBMITTED, label: `{{t("Submitted", { ns: "${NAMESPACE}" })}}`, color: 'cyan' }, + { value: APPROVAL_STATUS.PROCESSING, label: `{{t("Processing", { ns: "${NAMESPACE}" })}}`, color: 'gold' }, + { value: APPROVAL_STATUS.APPROVED, label: `{{t("Approved", { ns: "${NAMESPACE}" })}}`, color: 'green' }, + { value: APPROVAL_STATUS.REJECTED, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, color: 'red' }, +]; +export const ApprovalStatusEnumDict = ApprovalStatusEnums.reduce((e, t) => Object.assign(e, { [t.value]: t }), {}); +export const JobStatusEnums = { + [JOB_STATUS.PENDING]: { color: 'gold', label: `{{t('Pending', { ns: "${NAMESPACE}" })}}` }, + [JOB_STATUS.RESOLVED]: { color: 'green', label: `{{t('Approved', { ns: "${NAMESPACE}" })}}` }, + [JOB_STATUS.REJECTED]: { color: 'red', label: `{{t('Rejected', { ns: "${NAMESPACE}" })}}` }, + [JOB_STATUS.RETRY_NEEDED]: { color: 'red', label: `{{t('Returned', { ns: "${NAMESPACE}" })}}` }, +}; +export const VoteCategory = { SINGLE: Symbol('single'), ALL: Symbol('all'), VOTE: Symbol('vote') }; +export const VoteCategoryEnums = [ + { value: VoteCategory.SINGLE, label: `{{t("Or", { ns: "${NAMESPACE}" })}}` }, + { value: VoteCategory.ALL, label: `{{t("And", { ns: "${NAMESPACE}" })}}` }, + { + 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/src/client/index.ts b/packages/plugins/@hera/plugin-approval/src/client/index.ts index f83ac009a..18b9bcaa8 100644 --- a/packages/plugins/@hera/plugin-approval/src/client/index.ts +++ b/packages/plugins/@hera/plugin-approval/src/client/index.ts @@ -1,40 +1,13 @@ import { Plugin } from '@nocobase/client'; -import { ApprovalInstruction } from './ApprovalInstruction'; -import { ApprovalTrigger } from './ApprovalTrigger'; -import { ApprovalProcessAddBlockButton } from './ApprovalProcessAddBlockButton'; -import { ApprovalProcessAddActionButton } from './ApprovalProcessAddActionButton'; -import { ApprovalApplyAddBlockButton } from './ApprovalApplyAddBlockButton'; -import { ApprovalApplyAddActionButton } from './ApprovalApplyAddActionButton'; -import PluginWorkflow from '@nocobase/plugin-workflow/client'; -import { NAMESPACE } from '../locale'; -import { ApprovalBlock } from './refined'; +import PluginKitApprovalConfiguration from './configuration'; +import PluginKitApprovalUsage from './usage'; -export default class ApprovalPlugin extends Plugin { - async afterAdd() {} - async beforeLoad() {} - async load() { - const plugin = this.app.pm.get(PluginWorkflow); - plugin.registerTrigger('approval', ApprovalTrigger); - plugin.registerInstruction('approval', ApprovalInstruction); - this.app.addComponents({ ApprovalBlock }); - this.app.schemaInitializerManager.add(ApprovalApplyAddActionButton); - this.app.schemaInitializerManager.add(ApprovalApplyAddBlockButton); - this.app.schemaInitializerManager.add(ApprovalProcessAddActionButton); - this.app.schemaInitializerManager.add(ApprovalProcessAddBlockButton); - this.app.schemaInitializerManager.get('BlockInitializers').add('otherBlocks.approval', { - key: 'approvalBlock', - name: 'approvalBlock', - type: 'item', - title: `{{t("Approval", { ns: "${NAMESPACE}" })}}`, - Component: 'ApprovalBlock.BlockInitializer', - icon: 'AuditOutlined', - }); - this.app.schemaInitializerManager.get('RecordBlockInitializers').add('otherBlocks.approvalsBlock', { - type: 'item', - name: 'approvalsBlock', - title: `{{t("Related approvals", { ns: "${NAMESPACE}" })}}`, - Component: 'ApprovalBlock.RecordApprovals.BlockInitializer', - }); +export default class PluginApproval extends Plugin { + async afterAdd() { + this.pm.add(PluginKitApprovalConfiguration); + this.pm.add(PluginKitApprovalUsage); } + async beforeLoad() {} + async load() {} } diff --git a/packages/plugins/@hera/plugin-approval/src/client/locale.ts b/packages/plugins/@hera/plugin-approval/src/client/locale.ts new file mode 100644 index 000000000..2945c2501 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/locale.ts @@ -0,0 +1,20 @@ +import { i18n, tval as nTval } from '@nocobase/client'; + +export const NAMESPACE = '@hera/plugin-approval'; + +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) => nTval(key, { ns: NAMESPACE }); diff --git a/packages/plugins/@hera/plugin-approval/src/client/refined.tsx b/packages/plugins/@hera/plugin-approval/src/client/refined.tsx deleted file mode 100644 index 151bb8460..000000000 --- a/packages/plugins/@hera/plugin-approval/src/client/refined.tsx +++ /dev/null @@ -1,2315 +0,0 @@ -import React from 'react'; -import { AuditOutlined, DownOutlined, PlusOutlined, QuestionCircleOutlined, TableOutlined } from '@ant-design/icons'; -import { ArrayItems } from '@formily/antd-v5'; -import { - ActionBarProvider, - i18n, - ActionContextProvider, - ActionInitializer, - BlockRequestContext_deprecated, - CollectionProvider_deprecated, - DatePicker, - ExtendCollectionsProvider, - FormActiveFieldsProvider, - FormBlockContext, - FormProvider, - FormV2, - InitializerWithSwitch, - InputNumber, - InputReadPretty, - List, - RecordProvider, - RemoteSchemaComponent, - RemoteSelect, - SchemaComponent, - SchemaComponentContext, - SchemaComponentProvider, - SchemaInitializerItem, - TableBlockProvider, - Variable, - createFormBlockSchema, - createReadPrettyFormBlockSchema, - createStyles, - css, - joinCollectionName, - parseCollectionName, - useAPIClient, - useActionContext, - useAssociationNames, - useBlockRequestContext, - useCollectionFilterOptions, - useCollection_deprecated, - useCompile, - useCurrentUserContext, - useDesignable, - useFormBlockContext, - useFormBlockProps, - usePlugin, - useRecord, - useRecordCollectionDataSourceItems, - useRequest, - useSchemaComponentContext, - useSchemaInitializer, - useSchemaInitializerItem, - useSchemaTemplateManager, - useToken, -} from '@nocobase/client'; -import PluginWorkflow, { - Branch, - DetailsBlockProvider, - EXECUTION_STATUS, - ExecutionContextProvider, - FilterDynamicComponent, - FlowContext, - JOB_STATUS, - NodeDefaultView, - SimpleDesigner, - useAvailableUpstreams, - useFlowContext, - useNodeContext, - useStyles, - useWorkflowExecuted, - useWorkflowVariableOptions, -} from '@nocobase/plugin-workflow/client'; -import { RecursionField, createForm, observer, useField, useFieldSchema, useForm } from '@nocobase/schema'; -import { str2moment, uid } from '@nocobase/utils/client'; -import { Button, Dropdown, Popover, Progress, Radio, Result, Space, Spin, Table, Tag, Tooltip } from 'antd'; -import lodash from 'lodash'; -import { Fragment, createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; -import { usePluginTranslation, NAMESPACE } from '../locale'; - -export function useTranslation(key: string, t = {}) { - return i18n.t(key, w(x({}, t), { ns: NAMESPACE })); -} - -var ut = Object.defineProperty, - mt = Object.defineProperties; -var vt = Object.getOwnPropertyDescriptors; -var Z = Object.getOwnPropertySymbols; -var $e = Object.prototype.hasOwnProperty, - Ne = Object.prototype.propertyIsEnumerable; -export var ce = (f, o, p) => (o in f ? ut(f, o, { enumerable: !0, configurable: !0, writable: !0, value: p }) : (f[o] = p)), - x = (f, o) => { - for (var p in o || (o = {})) $e.call(o, p) && ce(f, p, o[p]); - if (Z) for (var p of Z(o)) Ne.call(o, p) && ce(f, p, o[p]); - return f; - }, - w = (f, o) => mt(f, vt(o)); -var V = (f, o) => { - var p = {}; - for (var r in f) $e.call(f, r) && o.indexOf(r) < 0 && (p[r] = f[r]); - if (f != null && Z) for (var r of Z(f)) o.indexOf(r) < 0 && Ne.call(f, r) && (p[r] = f[r]); - return p; -}; -export var T = (f, o, p) => (ce(f, typeof o != 'symbol' ? o + '' : o, p), p); -var E = (f, o, p) => - new Promise((r, G) => { - var u = ($) => { - try { - A(p.next($)); - } catch (U) { - G(U); - } - }, - y = ($) => { - try { - A(p.throw($)); - } catch (U) { - G(U); - } - }, - A = ($) => ($.done ? r($.value) : Promise.resolve($.value).then(u, y)); - A((p = p.apply(f, o)).next()); - }); -export const Ve = { - type: 'void', - name: 'initiations', - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { style: { marginBottom: 16 } }, - properties: { - filter: { - type: 'void', - title: '{{ t("Filter") }}', - 'x-action': 'filter', - 'x-designer': 'Filter.Action.Designer', - 'x-component': 'Filter.Action', - 'x-use-component-props': 'useFilterActionProps', - 'x-component-props': { icon: 'FilterOutlined' }, - 'x-align': 'left', - }, - refresher: { - type: 'void', - title: '{{ t("Refresh") }}', - 'x-action': 'refresh', - 'x-component': 'Action', - 'x-use-component-props': 'useRefreshActionProps', - 'x-designer': 'Action.Designer', - 'x-component-props': { icon: 'ReloadOutlined' }, - 'x-align': 'right', - }, - apply: { - type: 'void', - title: `{{t("Apply new", { ns: "${NAMESPACE}" })}}`, - 'x-component': 'ApplyButton', - 'x-align': 'right', - }, - }, - }, - table: { - type: 'array', - 'x-component': 'TableV2', - 'x-use-component-props': 'useTableBlockProps', - 'x-component-props': { rowKey: 'id', tableLayout: 'fixed' }, - properties: { - action: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 60 }, - title: '{{t("Actions")}}', - properties: { action: { 'x-component': 'ApprovalBlock.ViewAction' } }, - }, - id: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 80 }, - title: '{{t("ID")}}', - properties: { id: { type: 'number', 'x-component': 'Input', 'x-read-pretty': !0 } }, - }, - workflow: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: null }, - title: '{{t("Workflow", { ns: "workflow" })}}', - properties: { workflow: { 'x-component': 'WorkflowColumn', 'x-read-pretty': !0 } }, - }, - status: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 100 }, - title: '{{t("Status", { ns: "workflow" })}}', - properties: { status: { 'x-component': 'CollectionField', 'x-read-pretty': !0 } }, - }, - createdBy: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 140 }, - title: `{{t("Initiator", { ns: "${NAMESPACE}" })}}`, - properties: { createdBy: { 'x-component': 'UserColumn', 'x-read-pretty': !0 } }, - }, - createdAt: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 160 }, - properties: { createdAt: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': !0 } }, - }, - }, - }, - }, - }, - Oe = { - type: 'void', - name: 'todos', - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { style: { marginBottom: 16 } }, - properties: { - filter: { - type: 'void', - title: '{{ t("Filter") }}', - 'x-action': 'filter', - 'x-designer': 'Filter.Action.Designer', - 'x-component': 'Filter.Action', - 'x-use-component-props': 'useFilterActionProps', - 'x-component-props': { icon: 'FilterOutlined' }, - 'x-align': 'left', - }, - refresher: { - type: 'void', - title: '{{ t("Refresh") }}', - 'x-action': 'refresh', - 'x-component': 'Action', - 'x-use-component-props': 'useRefreshActionProps', - 'x-designer': 'Action.Designer', - 'x-component-props': { icon: 'ReloadOutlined' }, - 'x-align': 'right', - }, - }, - }, - table: { - type: 'array', - 'x-component': 'TableV2', - 'x-use-component-props': 'useTableBlockProps', - 'x-component-props': { rowKey: 'id', tableLayout: 'fixed' }, - properties: { - action: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 60 }, - title: '{{t("Actions")}}', - properties: { action: { 'x-component': 'ApprovalBlock.AssigneeViewAction' } }, - }, - approvalId: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 80 }, - title: '{{t("ID")}}', - properties: { approvalId: { type: 'number', 'x-component': 'Input', 'x-read-pretty': !0 } }, - }, - node: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: null }, - title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`, - properties: { node: { 'x-component': 'NodeColumn', 'x-read-pretty': !0 } }, - }, - workflow: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: null }, - title: '{{t("Workflow", { ns: "workflow" })}}', - properties: { workflow: { 'x-component': 'WorkflowColumn', 'x-read-pretty': !0 } }, - }, - status: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 100 }, - title: '{{t("Status", { ns: "workflow" })}}', - properties: { status: { 'x-component': 'ApprovalRecordStatusColumn', 'x-read-pretty': !0 } }, - }, - user: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 140 }, - title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`, - properties: { user: { 'x-component': 'UserColumn', 'x-read-pretty': !0 } }, - }, - createdAt: { - type: 'void', - 'x-decorator': 'TableV2.Column.Decorator', - 'x-component': 'TableV2.Column', - 'x-component-props': { width: 160 }, - properties: { createdAt: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': !0 } }, - }, - }, - }, - }, - }, - I = { DRAFT: 0, RETURNED: 1, SUBMITTED: 2, PROCESSING: 3, APPROVED: 4, REJECTED: -1 }, - pe = [ - { value: I.DRAFT, label: `{{t("Draft", { ns: "${NAMESPACE}" })}}`, editable: !0 }, - { value: I.RETURNED, label: `{{t("Returned", { ns: "${NAMESPACE}" })}}`, color: 'purple', editable: !0 }, - { value: I.SUBMITTED, label: `{{t("Submitted", { ns: "${NAMESPACE}" })}}`, color: 'cyan' }, - { value: I.PROCESSING, label: `{{t("Processing", { ns: "${NAMESPACE}" })}}`, color: 'gold' }, - { value: I.APPROVED, label: `{{t("Approved", { ns: "${NAMESPACE}" })}}`, color: 'green' }, - { value: I.REJECTED, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, color: 'red' }, - ], - de = pe.reduce((e, t) => Object.assign(e, { [t.value]: t }), {}), - b = { ASSIGNED: null, PENDING: 0, RETURNED: 1, APPROVED: 2, REJECTED: -1, CANCELED: -2, WITHDRAWN: -3 }, - ue = [ - { value: b.ASSIGNED, label: `{{t("Assigned", { ns: "${NAMESPACE}" })}}`, color: 'blue' }, - { value: b.PENDING, label: `{{t("Pending", { ns: "${NAMESPACE}" })}}`, color: 'gold' }, - { value: b.RETURNED, label: `{{t("Returned", { ns: "${NAMESPACE}" })}}`, color: 'purple' }, - { value: b.APPROVED, label: `{{t("Approved", { ns: "${NAMESPACE}" })}}`, color: 'green' }, - { value: b.REJECTED, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, color: 'red' }, - { value: b.WITHDRAWN, label: `{{t("Withdrawn", { ns: "${NAMESPACE}" })}}` }, - ], - _ = ue.reduce((e, t) => Object.assign(e, { [t.value]: t }), {}), - q = { SINGLE: Symbol('single'), ALL: Symbol('all'), VOTE: Symbol('vote') }, - ze = [ - { value: q.SINGLE, label: `{{t("Or", { ns: "${NAMESPACE}" })}}` }, - { value: q.ALL, label: `{{t("And", { ns: "${NAMESPACE}" })}}` }, - { - value: q.VOTE, - label(e) { - return `${useTranslation('Voting')} ( > ${(e * 100).toFixed(0)}%)`; - }, - }, - ].reduce((e, t) => Object.assign(e, { [t.value]: t }), {}); -function Me(e) { - switch (!0) { - case e === 1: - return q.ALL; - case 0 < e && e < 1: - return q.VOTE; - default: - return q.SINGLE; - } -} -const H = createContext({}); -function z() { - return useContext(H); -} -const ee = createContext({}); -function oe() { - return useContext(ee); -} -function Ue(n) { - var a = n, - { useData: e = useRecord } = a, - t = V(a, ['useData']); - const s = e(); - return {t.children}; -} -const me = observer( - () => { - var t, n, a; - const e = useField(); - return (a = (t = e == null ? void 0 : e.value) == null ? void 0 : t.title) != null - ? a - : `#${(n = e.value) == null ? void 0 : n.id}`; - }, - { displayName: 'NodeColumn' }, - ), - te = observer( - () => { - var n, a, s, i; - const e = useField(), - t = - (s = (n = e == null ? void 0 : e.value) == null ? void 0 : n.title) != null - ? s - : `#${(a = e.value) == null ? void 0 : a.id}`; - return (i = e == null ? void 0 : e.value) != null && i.enabled - ? t - : {t}; - }, - { displayName: 'WorkflowColumn' }, - ), - re = observer( - () => { - var t, n, a; - const e = useField(); - return (a = (t = e == null ? void 0 : e.value) == null ? void 0 : t.nickname) != null - ? a - : (n = e.value) == null - ? void 0 - : n.id; - }, - { displayName: 'UserColumn' }, - ); -function ve(a) { - var s = a, - { value: e, record: t } = s, - n = V(s, ['value', 'record']); - const i = useCompile(), - { option: v = _[e] } = n, - { workflow: l, execution: d, job: m } = t != null ? t : {}; - return (!(l != null && l.enabled) || (d != null && d.stauts) || (m != null && m.status)) && - [b.ASSIGNED, b.PENDING].includes(e) - ? {useTranslation('Unprocessed')} - : {i(v.label)}; -} -const xe = observer( - () => { - const { value: e } = useField(), - t = useRecord(); - return ; - }, - { displayName: 'ApprovalRecordStatusColumn' }, -); -function fe({ designable: e, children: t }) { - const n = useSchemaComponentContext(); - return {t}; -} -function ne(e) { - var F; - const t = oe(), - n = useFieldSchema(), - a = useField(), - s = useRef(null), - { getAssociationAppends: i } = useAssociationNames(), - { appends: v, updateAssociationValues: l } = i(), - d = t == null ? void 0 : t.snapshot, - { findComponent: m } = useDesignable(), - h = m((F = a.component) == null ? void 0 : F[0]) || Fragment, - C = useMemo(() => createForm({ initialValues: d }), [d]), - g = useMemo(() => x({ appends: v }, e.params), [v, e.params]), - S = useMemo(() => ({ loading: !1, data: { data: d } }), [d]), - P = useAPIClient().resource(e.collection), - D = useContext(BlockRequestContext_deprecated), - M = useMemo( - () => ({ params: g, form: C, field: a, service: S, updateAssociationValues: l, formBlockRef: s }), - [a, C, g, S, l], - ); - return ( - {{{{{_jsxs( - h, - w(x({}, a.componentProps), { - children: [ - , - {
{}
}
, - ], - }), - )}
}
}
}
}
- ); -} -function je() { - const e = useField(), - t = useAPIClient(), - n = useForm(), - a = L(), - { status: s } = useContext(he), - { setVisible: i, setSubmitted: v } = useActionContext(); - return { - run() { - return E(this, null, function* () { - var d; - try { - if (n.values.status) return; - yield n.submit(), - (e.data = (d = e.data) != null ? d : {}), - (e.data.loading = !0), - yield t.resource('approvalRecords').submit({ filterByTk: a.id, values: w(x({}, n.values), { status: s }) }), - (e.data.loading = !1), - yield n.reset(), - v(!0), - i(!1); - } catch (m) { - console.error(m), e.data && (e.data.loading = !1); - } - }); - }, - }; -} -function _e() { - const { snapshot: e } = L(), - { form: t } = useFormBlockContext(); - return ( - useEffect(() => { - t.setValues(x({}, e)); - }, [t, e]), - { form: t } - ); -} -function qe() { - const { form: e } = useFormBlockContext(); - return { form: e }; -} -function Le() { - const e = useCompile(), - { status: t, updatedAt: n, user: a } = L(), - s = _[t]; - return ( - {[ - {e(s.label)}, - , - {a.nickname}, - ]} - ); -} -function Ge(e) { - const { status: t } = L(); - return t ? : _jsx(ActionBarProvider, x({}, e)); -} -const he = createContext({}); -function Je(n) { - var a = n, - { children: e } = a, - t = V(a, ['children']); - const { status: s } = L(); - return (!s || s === t.status) && {e}; -} -const ye = createContext({}); -function L() { - return useContext(ye); -} -function Ke(e) { - var Be, De, Ee; - const Pe = L(), - { approval: t, job: n, node: a, snapshot: s } = Pe, - i = V(Pe, ['approval', 'job', 'node', 'snapshot']), - { execution: v, workflow: l } = useFlowContext(), - d = useFieldSchema(), - m = useField(), - h = useRef(null), - { getAssociationAppends: C } = useAssociationNames(), - { appends: g, updateAssociationValues: S } = C(), - { data: B } = useCurrentUserContext(), - { findComponent: P } = useDesignable(), - D = P((Be = m.component) == null ? void 0 : Be[0]) || Fragment, - M = useMemo(() => { - var Fe; - return createForm({ - initialValues: i, - pattern: - !l.enabled || v.status || n.status || i.status == null - ? 'disabled' - : i.status || ((Fe = B.data) == null ? void 0 : Fe.id) !== i.userId - ? 'readPretty' - : 'editable', - }); - }, [(De = B.data) == null ? void 0 : De.id, i.status, l.enabled]), - F = useMemo(() => x({ appends: g }, e.params), [g, e.params]), - W = useMemo(() => ({ loading: !1, data: { data: i } }), [i]), - Q = useAPIClient().resource(e.collection), - pt = useContext(BlockRequestContext_deprecated), - dt = useMemo( - () => ({ params: F, form: M, field: m, service: W, updateAssociationValues: S, formBlockRef: h }), - [m, M, F, W, S], - ); - return !i.status && ((Ee = B.data) == null ? void 0 : Ee.id) !== i.userId - ? null - : {{{{{_jsxs( - D, - w(x({}, m.componentProps), { - children: [ - , -
{}
, - ], - }), - )}
}
}
}
}
; -} -function We() { - const { id: e } = useRecord(), - { actionEnabled: t } = useContext(J), - { loading: n, data: a } = useRequest( - { - resource: 'approvalRecords', - action: 'get', - params: { - filterByTk: e, - 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'], - }, - }, - { refreshDeps: [e] }, - ); - if (n) return ; - if (!(a != null && a.data)) - return ( - - ); - const h = a.data, - { approvalExecution: s, node: i, approval: v, workflow: C } = h, - g = C, - { nodes: l } = g, - d = V(g, ['nodes']), - { execution: m } = h; - return ( - {{{{}}}} - ); -} -function Ae({ popoverComponent: e = 'Action.Drawer', popoverComponentProps: t = {} }) { - const n = useRecord(); - return ( - - ); -} -function Qe() { - const e = z(), - t = oe(), - { workflow: n } = useFlowContext(), - a = useForm(), - { data: s } = useCurrentUserContext(); - return ( - useEffect(() => { - if (!e) return; - const { editable: i } = de[e.status]; - a.setPattern( - i && e.latestExecutionId === t.id && e.createdById === (s == null ? void 0 : s.data.id) && n.enabled - ? 'editable' - : 'readPretty', - ); - }, [a, e, s]), - { form: a } - ); -} -function He() { - const e = useForm(), - t = useField(), - { setVisible: n, setSubmitted: a } = useActionContext(), - { id: s } = z(), - { workflow: i } = useFlowContext(), - v = useContext(Ce), - l = useAPIClient(); - return { - run() { - return E(this, null, function* () { - try { - yield e.submit(), - (t.data = t.data || {}), - (t.data.loading = !0), - yield l - .resource('approvals') - .update({ filterByTk: s, values: { collectionName: i.config.collection, data: e.values, status: v } }), - a(!0), - n(!1), - yield e.reset(), - (t.data.loading = !1); - } catch (m) { - t.data && (t.data.loading = !1); - } - }); - }, - }; -} -function Ye() { - const e = useField(), - { setVisible: t, setSubmitted: n } = useActionContext(), - a = z(), - s = useAPIClient(); - return { - run() { - return E(this, null, function* () { - try { - (e.data = e.data || {}), - (e.data.loading = !0), - yield s.resource('approvals').withdraw({ filterByTk: a.id }), - n(!0), - t(!1), - (e.data.loading = !1); - } catch (v) { - e.data && (e.data.loading = !1); - } - }); - }, - }; -} -function Xe() { - const e = useField(), - { setVisible: t, setSubmitted: n } = useActionContext(), - a = z(), - s = useAPIClient(); - return { - run() { - return E(this, null, function* () { - try { - (e.data = e.data || {}), - (e.data.loading = !0), - yield s.resource('approvals').destroy({ filterByTk: a.id }), - n(!0), - t(!1); - } catch (v) { - e.data && (e.data.loading = !1); - } - }); - }, - }; -} -function Ze(e) { - const { data: t } = useCurrentUserContext(), - { status: n, createdById: a, latestExecutionId: s } = z(), - i = oe(); - return t.data.id === a && s === i.id && [I.DRAFT, I.RETURNED, I.SUBMITTED].includes(n) ? e.children : null; -} -const Ce = createContext(I.SUBMITTED); -function Re(e) { - const { status: t, createdById: n } = z(), - { workflow: a } = useFlowContext(), - { data: s } = useCurrentUserContext(); - return s.data.id === n && a.enabled && [I.DRAFT, I.RETURNED].includes(t) - ? {e.children} - : null; -} -function eo(e) { - const { data: t } = useCurrentUserContext(), - { status: n, createdById: a } = z(), - { workflow: s } = useFlowContext(); - return t.data.id === a && s.enabled && s.config.withdrawable && [I.SUBMITTED].includes(n) ? e.children : null; -} -function oo(e) { - var h; - const { id: t } = useRecord(), - { actionEnabled: n } = useContext(J), - { loading: a, data: s } = useRequest( - { - resource: 'approvalExecutions', - action: 'get', - params: { - filterByTk: t, - 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'], - }, - }, - { refreshDeps: [t] }, - ); - if (a) return ; - if (!(s != null && s.data)) return ; - const m = s.data, - { approval: i, execution: v } = m, - l = V(m, ['approval', 'execution']), - { workflow: d } = i; - return ( - {{{}}} - ); -} -function to(e) { - const { latestExecutionId: t } = useRecord(); - return {e.children}; -} -function be({ popoverComponent: e = 'Action.Drawer', popoverComponentProps: t = {} }) { - const n = useRecord(); - return ( - - ); -} -const ro = { - [JOB_STATUS.PENDING]: { color: 'gold', label: `{{t('Pending', { ns: "${NAMESPACE}" })}}` }, - [JOB_STATUS.RESOLVED]: { color: 'green', label: `{{t('Approved', { ns: "${NAMESPACE}" })}}` }, - [JOB_STATUS.REJECTED]: { color: 'red', label: `{{t('Rejected', { ns: "${NAMESPACE}" })}}` }, - [JOB_STATUS.RETRY_NEEDED]: { color: 'red', label: `{{t('Returned', { ns: "${NAMESPACE}" })}}` }, -}; -function no(e, { node: t, job: n, groupCount: a, statusCount: s }, i) { - var C; - const v = useCompile(), - { branchMode: l, negotiation: d } = (C = t == null ? void 0 : t.config) != null ? C : {}, - m = ze[Me(d)], - h = l ? _[n.result] : ro[n == null ? void 0 : n.status]; - return ( - <_Fragment>{[ - {e}, - i && a > 1 - ?
{[ - {typeof m.label == 'function' ? m.label(d) : v(m.label)}, - s - ? - : null, - ]}
- : null, - ]} - ); -} -function ao(e, t, n) { - const a = useCompile(); - if (!n) { - const i = de[t.status === I.DRAFT ? I.DRAFT : I.SUBMITTED]; - return {a(i.label)}; - } - const s = _[e]; - return ; -} -function so(e) { - const { data: t } = useCurrentUserContext(), - { actionEnabled: n } = useContext(J); - return ( - <_Fragment>{[ - , - n && e.user.id === (t == null ? void 0 : t.data.id) - ? {} - : null, - ]} - ); -} -function io(e) { - const { data: t } = useCurrentUserContext(), - { actionEnabled: n } = useContext(J); - return ( - <_Fragment>{[ - e.status - ? <_Fragment>{[ - e.comment ? : null, - , - ]} - : null, - n && e.userId === (t == null ? void 0 : t.data.id) - ? {} - : null, - ]} - ); -} -function co(e, t, n) { - return n ? _jsx(io, x({}, t)) : _jsx(so, x({}, t)); -} -const lo = createStyles(({ css: e, token: t }) => ({ - layout: e` - display: flex; - `, - columnDetail: e` - .ant-description-textarea { - margin-bottom: 0.5em; - } - time { - display: block; - color: ${t.colorTextTertiary}; - } - `, -})); -function po({ approval: e, currentUser: t }) { - const { workflow: n, approvalExecutions: a, records: s } = e; - a.sort((l, d) => Date.parse(l.createdAt) - Date.parse(d.createdAt)); - const i = a.reduce( - (l, d) => - Object.assign(l, { - [d.id]: Object.assign(d, { - records: [ - { - groupCount: 1, - node: { title: useTranslation('Apply') }, - user: w(x({}, e.createdBy), { id: e.createdById }), - status: d.status ? I.SUBMITTED : e.status, - updatedAt: d.createdAt, - execution: x({}, d), - }, - ], - }), - }), - {}, - ); - s - .sort((l, d) => { - const m = new Date(l.job.createdAt), - h = new Date(d.job.createdAt); - return m < h ? -1 : m > h ? 1 : l.id - d.id; - }) - .forEach((l) => { - const d = i[l.approvalExecutionId], - C = d, - { records: m } = C, - h = V(C, ['records']); - (l.workflow = n), - (l.execution = x({}, h)), - d.records.push(l), - d.jobs || (d.jobs = {}), - d.jobs[l.jobId] - ? (d.jobs[l.jobId].first.groupCount += 1) - : ((d.jobs[l.jobId] = { first: l }), - (l.groupCount = 1), - (l.statusCount = { [b.APPROVED]: 0, [b.REJECTED]: 0 })), - [b.APPROVED, b.REJECTED].includes(l.status) && (d.jobs[l.jobId].first.statusCount[l.status] += 1); - }), - e.createdById === (t == null ? void 0 : t.data.id) && - a.forEach((l) => { - l.status === EXECUTION_STATUS.CANCELED && - l.records.length === 1 && - ((l.records[0].groupCount = 2), - l.records.push({ user: { nickname: e.createdBy.nickname }, status: b.WITHDRAWN, updatedAt: l.updatedAt })); - }); - const v = a.length; - return a.filter( - (l, d) => (v - 1 === d && (!l.status || l.status === EXECUTION_STATUS.CANCELED)) || l.records.length > 1, - ); -} -const J = createContext({ actionEnabled: !1 }); -function uo(e) { - const { t } = usePluginTranslation(), - n = z(), - { styles: a } = lo(), - { data: s } = useCurrentUserContext(), - i = po({ approval: n, currentUser: s }); - return ( - {{i.map((v, l) => - _jsx( - Table, - { - dataSource: v.records, - rowKey: 'id', - columns: [ - { - title: useTranslation('Task node'), - dataIndex: ['node', 'title'], - onCell(d) { - var m; - return { rowSpan: (m = d.groupCount) != null ? m : 0 }; - }, - render: no, - }, - { title: t('User'), dataIndex: ['user', 'nickname'], width: 120 }, - { title: t('Status', { ns: 'workflow' }), dataIndex: 'status', render: ao, width: 120 }, - { title: t('Details'), dataIndex: 'updatedAt', render: co, width: 200, className: a.columnDetail }, - ], - pagination: !1, - }, - v.id, - ), - )}} - ); -} -function mo({ workflow: e, children: t }) { - return {t}; -} -const we = createContext(I.SUBMITTED); -function vo(e) { - return {e.children}; -} -function xo(e) { - return e.children; -} -function fo(e) { - return null; -} -function ho() { - const e = useForm(), - t = useField(), - { setVisible: n } = useActionContext(), - { __parent: a } = useBlockRequestContext(), - s = useCollection_deprecated(), - i = useContext(we), - v = useAPIClient(), - { workflow: l } = useFlowContext(); - return { - run() { - return E(this, null, function* () { - var m; - try { - yield e.submit(), - (t.data = t.data || {}), - (t.data.loading = !0), - yield v.resource('approvals').create({ - values: { - collectionName: joinCollectionName(s.dataSource, s.name), - data: e.values, - status: i, - workflowId: l.id, - }, - }), - n(!1), - yield e.reset(), - (t.data.loading = !1), - (m = a == null ? void 0 : a.service) == null || m.refresh(); - } catch (h) { - t.data && (t.data.loading = !1); - } - }); - }, - }; -} -function yo() { - return { run() {} }; -} -function Ao(e) { - const t = useContext(SchemaComponentContext), - n = useAPIClient(), - [a, s] = useState(!1), - [i, v] = useState([]), - [l, d] = useState(null); - useEffect(() => { - n.resource('workflows') - .listApprovalFlows({ filter: { 'config.centralized': !0 } }) - .then(({ data: h }) => { - v(h.data); - }) - .catch(console.error); - }, []); - const m = useCallback( - ({ key: h }) => { - var P; - const C = i.find((D) => D.id == h), - { applyForm: g } = (P = C == null ? void 0 : C.config) != null ? P : {}, - [S, B] = parseCollectionName(C.config.collection); - d({ - type: 'void', - properties: { - [`drawer-${C.id}`]: { - type: 'void', - title: C.title, - 'x-decorator': 'FlowContextProvider', - 'x-decorator-props': { workflow: C }, - 'x-component': 'Action.Drawer', - 'x-component-props': { - className: css` - .ant-drawer-body { - background: var(--nb-box-bg); - } - `, - }, - properties: { - [g]: { - type: 'void', - 'x-decorator': 'CollectionProvider_deprecated', - 'x-decorator-props': { name: B, dataSource: S }, - 'x-component': 'RemoteSchemaComponent', - 'x-component-props': { uid: g, noForm: !0 }, - }, - }, - }, - }, - }), - s(!0); - }, - [i], - ); - return ( - {[ - ({ key: h.id, label: h.title })), onClick: m }} - disabled={!i.length}>{}, - {}, - ]} - ); -} -const ge = { - title: `{{t("Node", { ns: "${NAMESPACE}" })}}`, - name: 'flow_nodes', - fields: [ - { - type: 'bigInt', - name: 'id', - interface: 'm2o', - uiSchema: { - type: 'number', - title: 'ID', - 'x-component': 'RemoteSelect', - 'x-component-props': { - fieldNames: { label: 'title', value: 'id' }, - service: { resource: 'flow_nodes', params: { filter: { type: 'manual' } } }, - }, - }, - }, - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { type: 'string', title: '{{t("Title")}}', 'x-component': 'Input' }, - }, - ], - }, - Se = { - title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`, - name: 'workflows', - fields: [ - { - type: 'string', - name: 'title', - interface: 'input', - uiSchema: { title: '{{t("Name")}}', type: 'string', 'x-component': 'Input', required: !0 }, - }, - { - type: 'boolean', - name: 'enabled', - interface: 'select', - uiSchema: { - type: 'boolean', - title: '{{t("Status", { ns: "workflow" })}}', - 'x-component': 'Select', - enum: [ - { label: '{{t("On", { ns: "workflow" })}}', value: !0 }, - { label: '{{t("Off", { ns: "workflow" })}}', value: !1 }, - ], - }, - }, - ], - }, - ae = { - title: `{{t("Approval applications", { ns: "${NAMESPACE}" })}}`, - name: 'approvals', - fields: [ - { - type: 'bigInt', - name: 'id', - interface: 'number', - uiSchema: { type: 'number', title: 'ID', 'x-component': 'InputNumber' }, - }, - { - type: 'belongsTo', - name: 'workflow', - target: 'workflows', - foreignKey: 'workflowId', - interface: 'm2o', - uiSchema: { - type: 'number', - title: '{{t("Workflow", { ns: "workflow" })}}', - 'x-component': 'RemoteSelect', - 'x-component-props': { fieldNames: { label: 'title', value: 'id' }, service: { resource: 'workflows' } }, - }, - }, - { - type: 'belongsTo', - name: 'createdBy', - target: 'users', - foreignKey: 'createdById', - interface: 'm2o', - uiSchema: { - type: 'number', - title: `{{t("Initiator", { ns: "${NAMESPACE}" })}}`, - 'x-component': 'RemoteSelect', - 'x-component-props': { fieldNames: { label: 'nickname', value: 'id' }, service: { resource: 'users' } }, - }, - }, - { - type: 'integer', - name: 'status', - interface: 'select', - uiSchema: { type: 'number', title: '{{t("Status", { ns: "workflow" })}}', 'x-component': 'Select', enum: pe }, - }, - { - name: 'createdAt', - type: 'date', - interface: 'createdAt', - uiSchema: { - type: 'datetime', - title: '{{t("Created at")}}', - 'x-component': 'DatePicker', - 'x-component-props': { showTime: !0 }, - }, - }, - ], - }, - Y = { - title: `{{t("Approval todos", { ns: "${NAMESPACE}" })}}`, - name: 'approvalRecords', - fields: [ - { - type: 'bigInt', - name: 'approvalId', - interface: 'number', - uiSchema: { type: 'number', title: 'ID', 'x-component': 'InputNumber' }, - }, - { - type: 'belongsTo', - name: 'user', - target: 'users', - foreignKey: 'userId', - interface: 'm2o', - uiSchema: { - type: 'number', - title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`, - 'x-component': 'RemoteSelect', - 'x-component-props': { fieldNames: { label: 'nickname', value: 'id' }, service: { resource: 'users' } }, - }, - }, - { - type: 'belongsTo', - name: 'node', - target: 'flow_nodes', - foreignKey: 'nodeId', - interface: 'm2o', - isAssociation: !0, - uiSchema: { - type: 'number', - title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`, - 'x-component': 'RemoteSelect', - 'x-component-props': { fieldNames: { label: 'title', value: 'id' }, service: { resource: 'flow_nodes' } }, - }, - }, - { - type: 'belongsTo', - name: 'workflow', - target: 'workflows', - foreignKey: 'workflowId', - interface: 'm2o', - uiSchema: { - type: 'number', - title: '{{t("Workflow", { ns: "workflow" })}}', - 'x-component': 'RemoteSelect', - 'x-component-props': { fieldNames: { label: 'title', value: 'id' }, service: { resource: 'workflows' } }, - }, - }, - { - type: 'integer', - name: 'status', - interface: 'select', - uiSchema: { type: 'number', title: '{{t("Status", { ns: "workflow" })}}', 'x-component': 'Select', enum: ue }, - }, - { - type: 'text', - name: 'comment', - interface: 'markdown', - uiSchema: { type: 'string', 'x-component': 'Markdown', title: `{{t("Comment", { ns: "${NAMESPACE}" })}}` }, - }, - { - name: 'createdAt', - type: 'date', - interface: 'createdAt', - uiSchema: { - type: 'datetime', - title: '{{t("Created at")}}', - 'x-component': 'DatePicker', - 'x-component-props': { showTime: !0 }, - }, - }, - { - name: 'updatedAt', - type: 'date', - interface: 'updatedAt', - uiSchema: { - type: 'datetime', - title: '{{t("Updated at")}}', - 'x-component': 'DatePicker', - 'x-component-props': { showTime: !0 }, - }, - }, - ], - }; -function Co(e) { - const t = useSchemaInitializerItem(), - { insert: n } = useSchemaInitializer(); - return _jsx( - SchemaInitializerItem, - w(x({ icon: }, t), { - items: [ - { - type: 'item', - title: `{{t("Initiations", { ns: "${NAMESPACE}" })}}`, - 'x-component': 'ApprovalBlock.Initiations', - collection: 'approvals', - params: { appends: ['createdBy.nickname', 'workflow.title', 'workflow.enabled'], except: ['data'] }, - }, - { - type: 'item', - title: `{{t("Todos", { ns: "${NAMESPACE}" })}}`, - 'x-component': 'ApprovalBlock.Todos', - collection: 'approvalRecords', - params: { - appends: [ - 'user.id', - 'user.nickname', - 'node.id', - 'node.title', - 'job.id', - 'job.status', - 'job.result', - 'workflow.id', - 'workflow.title', - 'workflow.enabled', - 'execution.id', - 'execution.status', - ], - }, - }, - ], - onClick: ({ item: a }) => { - const s = uid(); - n({ - type: 'void', - name: s, - 'x-uid': s, - 'x-component': 'CardItem', - 'x-decorator': 'ApprovalBlock.Decorator', - 'x-decorator-props': { collection: a.collection, action: 'listCentralized', params: a.params }, - 'x-designer': 'TableBlockDesigner', - properties: { block: { type: 'void', 'x-component': a['x-component'] } }, - }); - }, - }), - ); -} -const bo = { - type: 'void', - name: 'recordApprovals', - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': { style: { marginBottom: 16 } }, - properties: { - refresher: { - type: 'void', - title: '{{ t("Refresh") }}', - 'x-action': 'refresh', - 'x-component': 'Action', - 'x-use-component-props': 'useRefreshActionProps', - 'x-designer': 'Action.Designer', - 'x-component-props': { icon: 'ReloadOutlined' }, - 'x-align': 'right', - }, - }, - }, - list: { - type: 'array', - 'x-component': 'List', - 'x-component-props': { locale: { emptyText: `{{ t("No data yet", { ns: "${NAMESPACE}" }) }}` } }, - properties: { - item: { - type: 'object', - 'x-component': 'List.Item', - 'x-use-component-props': 'useListItemProps', - 'x-read-pretty': !0, - properties: { - grid: { - type: 'void', - 'x-component': 'Space', - 'x-component-props': { direction: 'vertical' }, - properties: { - row: { - type: 'void', - 'x-component': 'Space', - 'x-component-props': { size: 'large', wrap: !0 }, - properties: { - id: { - type: 'number', - title: '{{t("ID")}}', - 'x-decorator': 'FormItem', - 'x-component': 'Input', - 'x-read-pretty': !0, - }, - workflow: { - type: 'string', - title: '{{t("Workflow", { ns: "workflow" })}}', - 'x-decorator': 'FormItem', - 'x-component': 'WorkflowColumn', - 'x-read-pretty': !0, - }, - status: { - type: 'number', - title: `{{t("Current status", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'CollectionField', - 'x-read-pretty': !0, - }, - createdBy: { - type: 'string', - title: `{{t("Initiator", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'FormItem', - 'x-component': 'UserColumn', - 'x-read-pretty': !0, - }, - createdAt: { - type: 'string', - title: '{{t("Created at")}}', - 'x-decorator': 'FormItem', - 'x-component': 'CollectionField', - 'x-read-pretty': !0, - }, - }, - }, - processRow: { - type: 'void', - 'x-decorator': 'ApprovalBlock.ApprovalDataProvider', - 'x-component': 'ApprovalBlock.ApprovalProcess', - 'x-component-props': { actionEnabled: !0 }, - }, - }, - }, - }, - }, - }, - }, - }, -}; -function se() { - return ( - - ); -} -function wo({ params: e, children: t }) { - const n = useCollection_deprecated(), - a = useRecord(), - s = useMemo( - () => ({ - collection: 'approvals', - resource: 'approvals', - action: 'list', - params: w(x({ sort: ['-createdAt'] }, e), { - filter: w(x({}, e == null ? void 0 : e.filter), { - dataKey: `${a[n.filterTargetKey]}`, - collectionName: joinCollectionName(n.dataSource, n.name), - }), - appends: [ - 'workflow', - 'approvalExecutions', - 'createdBy.nickname', - 'records', - 'records.node.title', - 'records.node.config', - 'records.job', - 'records.user.nickname', - ], - except: ['data', 'approvalExecutions.snapshot', 'records.snapshot'], - }), - dragSort: !1, - }), - [e, a, n], - ); - return {{_jsx(List.Decorator, w(x({}, s), { children: t }))}}; -} -function go(e) { - const t = useSchemaInitializerItem(), - { insert: n } = useSchemaInitializer(); - return _jsx( - SchemaInitializerItem, - w(x({ icon: }, t), { - onClick: () => { - const a = uid(); - n({ - type: 'void', - name: a, - 'x-uid': a, - 'x-decorator': 'ApprovalBlock.RecordApprovals.Decorator', - 'x-component': 'CardItem', - 'x-designer': 'List.Designer', - properties: { block: { type: 'void', 'x-component': 'ApprovalBlock.RecordApprovals' } }, - }); - }, - }), - ); -} -(se.BlockInitializer = go), (se.Decorator = wo); -function So() { - return ( - - ); -} -function ko() { - return ( - - ); -} -function Io({ collection: e, action: t = 'list', params: n = { filter: {} }, children: a }) { - const s = useRecord(), - i = { - collection: e, - resource: e, - action: t, - params: w(x({ pageSize: 20, sort: ['-createdAt'] }, n), { - filter: x(x({}, s != null && s.id ? { dataKey: `${s.id}` } : {}), n.filter), - }), - rowKey: 'id', - showIndex: !0, - dragSort: !1, - }; - return {_jsx(TableBlockProvider, w(x({ name: e }, i), { children: a }))}; -} -export function ApprovalBlock() { - return null; -} -(ApprovalBlock.Decorator = Io), - (ApprovalBlock.BlockInitializer = Co), - (ApprovalBlock.Initiations = So), - (ApprovalBlock.Todos = ko), - (ApprovalBlock.ViewAction = be), - (ApprovalBlock.AssigneeViewAction = Ae), - (ApprovalBlock.ApprovalProcess = uo), - (ApprovalBlock.RecordApprovals = se), - (ApprovalBlock.ApprovalDataProvider = Ue); -function K(e, t, n = !1) { - const a = []; - return e - ? t(e) && (!n || !e.properties) - ? (a.push(e), a) - : (e.properties && - Object.keys(e.properties).forEach((s) => { - a.push(...K(e.properties[s], t)); - }), - a) - : a; -} -export const X = { DRAFT: 0, RETURNED: 1, SUBMITTED: 2, PROCESSING: 3, APPROVED: 4, REJECTED: -1 }; -export function ke() { - const a = useSchemaInitializerItem(), - { action: e, actionProps: t = {} } = a, - n = V(a, ['action', 'actionProps']); - return _jsx( - ActionInitializer, - w(x({}, n), { - schema: { - type: 'void', - title: n.title, - 'x-decorator': 'ApplyActionStatusProvider', - 'x-decorator-props': { value: e }, - 'x-component': 'Action', - 'x-component-props': w(x({}, t), { useAction: '{{ useSubmit }}' }), - 'x-designer': 'Action.Designer', - 'x-action': `${e}`, - 'x-action-settings': { assignedValues: {} }, - }, - }), - ); -} -export function Po() { - const e = useSchemaInitializerItem(), - { insert: t } = useSchemaInitializer(), - n = useCollection_deprecated(), - { getTemplateSchemaByMode: a } = useSchemaTemplateManager(), - s = useRecordCollectionDataSourceItems('FormItem'); - function i(l) { - return E(this, arguments, function* ({ item: v }) { - const d = v.template ? yield a(v) : null, - m = createFormBlockSchema({ - actionInitializers: 'ApprovalApplyAddActionButton', - actions: { - submit: { - type: 'void', - title: '{{t("Submit")}}', - 'x-decorator': 'ApplyActionStatusProvider', - 'x-decorator-props': { value: X.SUBMITTED }, - 'x-component': 'Action', - 'x-component-props': { type: 'primary', htmlType: 'submit', useAction: '{{ useSubmit }}' }, - 'x-designer': 'Action.Designer', - 'x-designer-props': {}, - 'x-action': `${X.SUBMITTED}`, - 'x-action-settings': { removable: !1, assignedValues: {} }, - }, - withdraw: { - type: 'void', - title: `{{t("Withdraw", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'WithdrawActionProvider', - 'x-component': 'Action', - 'x-component-props': { - confirm: { - title: `{{t('Withdraw', { ns: "${NAMESPACE}" })}}`, - content: `{{t('Are you sure you want to withdraw it?', { ns: "${NAMESPACE}" })}}`, - }, - useAction: '{{ useWithdrawAction }}', - }, - 'x-designer': 'WithdrawActionDesigner', - 'x-action': 'withdraw', - }, - }, - dataSource: n.dataSource, - resource: n.name, - collection: n.name, - template: d, - }); - delete m['x-acl-action-props'], delete m['x-acl-action']; - const [h] = Object.keys(m.properties), - [C] = K(m.properties[h], (g) => g['x-component'] === 'ActionBar'); - (C['x-decorator'] = 'ActionBarProvider'), - (C['x-component-props'].style = { marginTop: '1.5em', flexWrap: 'wrap' }), - t(m); - }); - } - return _jsx(SchemaInitializerItem, w(x({}, e), { onClick: i, items: s })); -} -function Do() { - return { run() {} }; -} -function Eo() { - return { run() {} }; -} -function Fo(e) { - return e.children; -} -function $o(e) { - return e.children; -} -function No(e) { - return null; -} -export function Vo(e) { - useFlowContext(); - const [t, n] = useState(!1); - return ( - <_Fragment>{[ - , - {e.children}, - ]} - ); -} -function Oo({ value: e, onChange: t }) { - const n = useAPIClient(), - { workflow: a } = useFlowContext(), - { components: s } = useContext(SchemaComponentContext), - { data: i, loading: v } = useRequest(() => - E(this, null, function* () { - var m; - if (e) { - const { data: h } = yield n.request({ url: `uiSchemas:getJsonSchema/${e}` }); - if (((m = h.data) == null ? void 0 : m['x-uid']) === e) return h.data; - } - const l = uid(), - d = { - type: 'void', - name: l, - 'x-uid': l, - 'x-component': 'Grid', - 'x-initializer': 'ApprovalApplyAddBlockButton', - properties: {}, - }; - return yield n.resource('uiSchemas').insert({ values: d }), t(l), d; - }), - ); - return v - ? - : {}; -} -export function zo() { - const { values: e } = useForm(), - [t, n] = parseCollectionName(e.collection); - return ( - - ); -} - -function Uo(e) { - return e.isForeignKey ? e.target === 'users' : e.collectionName === 'users' && e.name === 'id'; -} -export function jo(e) { - return typeof e.value === 'object' && e.value ? _jsx(qo, x({}, e)) : _jsx(_o, x({}, e)); -} -function _o({ value: e, onChange: t }) { - const n = useWorkflowVariableOptions({ types: [Uo] }); - return ( - {} - ); -} -function qo(e) { - const t = useField(), - n = useCollectionFilterOptions('users'), - { token: a } = useToken(); - return ( -
{}
- ); -} -export function Lo(e) { - const { workflow: t } = useFlowContext(), - [n, a] = parseCollectionName(t.config.collection), - s = useSchemaInitializerItem(), - { insert: i } = useSchemaInitializer(), - { getTemplateSchemaByMode: v } = useSchemaTemplateManager(), - l = useRecordCollectionDataSourceItems('FormItem'); - function d(h) { - return E(this, arguments, function* ({ item: m }) { - const C = m.template ? yield v(m) : null, - g = createReadPrettyFormBlockSchema({ - actionInitializers: null, - resource: a, - collection: a, - dataSource: n, - template: C, - settings: 'blockSettings:singleDataDetails', - }); - delete g['x-acl-action-props'], delete g['x-acl-action']; - const [S] = Object.keys(g.properties); - lodash.set(g.properties[S], 'x-component-props.useProps', '{{useApprovalDetailBlockProps}}'), i(g); - }); - } - return _jsx(SchemaInitializerItem, w(x({}, s), { onClick: d, items: l })); -} -export function ie() { - const a = useSchemaInitializerItem(), - { action: e, actionProps: t = {} } = a, - n = V(a, ['action', 'actionProps']); - return _jsx( - ActionInitializer, - w(x({}, n), { - schema: { - type: 'void', - title: n.title, - 'x-decorator': 'ApprovalActionProvider', - 'x-decorator-props': { status: e }, - 'x-component': 'Action', - 'x-component-props': w(x({}, t), { useAction: '{{ useSubmit }}' }), - 'x-designer': 'Action.Designer', - 'x-action': `${e}`, - }, - }), - ); -} -export function Jo() { - const e = useSchemaInitializerItem(), - t = createFormBlockSchema({ - actionInitializers: 'ApprovalProcessAddActionButton', - actions: { - approve: { - type: 'void', - title: `{{t("Approve", { ns: "${NAMESPACE}" })}}`, - 'x-decorator': 'ApprovalActionProvider', - 'x-decorator-props': { status: b.APPROVED }, - 'x-component': 'Action', - 'x-component-props': { type: 'primary', htmlType: 'submit', useAction: '{{ useSubmit }}' }, - 'x-designer': 'Action.Designer', - 'x-designer-props': {}, - 'x-action': `${b.APPROVED}`, - }, - }, - resource: 'approvalRecords', - collection: 'approvalRecords', - }); - delete t['x-acl-action-props'], delete t['x-acl-action'], (t['x-decorator'] = 'ApprovalFormBlockProvider'); - const [n] = Object.keys(t.properties), - a = t.properties[n]; - lodash.set(a, 'x-component-props.useProps', '{{useApprovalFormBlockProps}}'); - const [s] = K(t.properties[n], (v) => v['x-component'] === 'ActionBar'); - (s['x-decorator'] = 'ActionBarProvider'), - lodash.set(s, 'x-component-props.style', { marginTop: '1.5em', flexWrap: 'wrap' }), - (t['x-block'] = 'action-form'); - const i = w(x({}, e), { schema: t }); - return _jsx(InitializerWithSwitch, w(x({}, i), { item: i, type: 'x-block' })); -} -function Wo(e) { - return e.children; -} -function Qo(e) { - return e.children; -} -function Ho(e) { - var D; - const t = useFieldSchema(), - n = useField(), - a = useRef(null), - { getAssociationAppends: s } = useAssociationNames(), - { appends: i, updateAssociationValues: v } = s(), - { findComponent: l } = useDesignable(), - d = l((D = n.component) == null ? void 0 : D[0]) || Fragment, - m = useMemo(() => createForm({}), []), - h = useMemo(() => x({ appends: i }, e.params), [i, e.params]), - C = useMemo(() => ({ loading: !1, data: { data: {} } }), []), - S = useAPIClient().resource(e.collection), - B = useContext(BlockRequestContext_deprecated), - P = useMemo( - () => ({ params: h, form: m, field: n, service: C, updateAssociationValues: v, formBlockRef: a }), - [n, m, h, C, v], - ); - return ( - {{{{{_jsxs( - d, - w(x({}, n.componentProps), { - children: [ - , -
{}
, - ], - }), - )}
}
}
}
}
- ); -} -function Yo(e) { - const { form: t } = useFormBlockContext(); - return { form: t }; -} -const Xo = createContext({}); -function Zo({ value: e, onChange: t }) { - const n = useAPIClient(), - a = usePlugin(PluginWorkflow), - { values: s, setValuesIn: i } = useForm(), - { setFormValueChanged: v } = useContext(Ie), - l = useNodeContext(), - d = useAvailableUpstreams(l), - m = useCallback( - (S) => { - var D, M; - const P = K(S.toJSON(), (F) => F['x-decorator'] === 'ApprovalFormBlockProvider').reduce( - (F, W) => ( - K(W, (Q) => Q['x-component'] === 'Action').forEach((Q) => { - F.add(Number.parseInt(Q['x-action'], 10)); - }), - F - ), - new Set(), - ); - (P.size !== ((D = s.actions) == null ? void 0 : D.length) || - !((M = s.actions) != null && M.every((F) => P.has(F)))) && - v(!0), - i('actions', [...P]); - }, - [v, i, s.actions], - ), - { data: h, loading: C } = useRequest(() => - E(this, null, function* () { - var P; - if (e) { - const { data: D } = yield n.request({ url: `uiSchemas:getJsonSchema/${e}` }); - if (((P = D.data) == null ? void 0 : P['x-uid']) === e) return D.data; - } - const S = e != null ? e : uid(), - B = { - type: 'void', - name: S, - 'x-uid': S, - 'x-component': 'Grid', - 'x-initializer': 'ApprovalProcessAddBlockButton', - properties: {}, - }; - return yield n.resource('uiSchemas').insert({ values: B }), t(S), B; - }), - ); - if (C) return ; - const g = {}; - return (d.forEach((S) => { - const B = a.instructions.get(S.type); - Object.assign(g, B.components); - }), {}); -} -export function Ro() { - const e = useContext(SchemaComponentContext), - [, t] = useState(uid()), - { workflow: n } = useFlowContext(); - return ( - a.name === 'comment') })]}>{{{}}} - ); -} -const Ie = createContext({}); -export function et(e) { - const [t, n] = useState(!1), - { setFormValueChanged: a } = useActionContext(); - return ( - {[ - , - {e.children}, - ]} - ); -} -export function ot({ data: e }) { - var v; - const t = useCompile(), - { nodes: n } = useFlowContext(), - { styles: a } = useStyles(), - { id: s, config: i } = e; - return ( - {i.branchMode - ?
{
{[ - b.REJECTED, - b.APPROVED, - ...(((v = e.config.actions) != null ? v : []).includes(b.RETURNED) ? [b.RETURNED] : []), - ].map((l) => - _jsx( - Branch, - { - from: e, - entry: n.find((d) => d.upstreamId === s && d.branchIndex === l), - branchIndex: l, - controller: {t(_[l].label)}, - end: l === b.RETURNED || (l === b.REJECTED && i.endOnReject), - }, - l, - ), - )}
}
- : null}
- ); -} -function tt({ label: e, tooltip: t }) { - const n = useCompile(); - return ( - <_Fragment>{[ - {n(e)}, - t && - {}, - ]} - ); -} -export function rt(e) { - return e.map((t) => { - const { label: n, tooltip: a, value: s } = t; - return { value: s, label: }; - }); -} -function nt(e, t = null) { - for (let n = e, a = t; n; n = n.upstream) { - if (t != null) return n; - a = n.branchIndex; - } - return null; -} -export function at(e, t = null, n) { - for (let a = e, s = t; a; a = a.upstream) { - const i = nt(a, s); - if (n(i, s)) return !0; - s = a.branchIndex; - } - return !1; -} -export function st() { - const e = useWorkflowExecuted(), - t = ArrayItems.useArray(), - [n, a] = useState(!1), - s = useCallback(() => { - t.field.push(''), a(!1); - }, [t.field]), - i = useCallback(() => { - t.field.push({ filter: {} }), a(!1); - }, [t.field]), - v = ; - return e - ? v - : {[ - , - , - ]}}>{v}; -} -export function it({ value: e, onChange: t }) { - const n = 0 < e && e < 1 ? '%' : e, - a = useCallback( - ({ target: i }) => { - i.value !== n && t(i.value === '%' ? 0.5 : i.value); - }, - [t], - ), - s = useCallback( - (i) => { - t(i / 100); - }, - [t], - ); - return ( -
{[ - {[ - {{[ - {useTranslation('Or')}, - , - ]}}, - {{[ - {useTranslation('And')}, - , - ]}}, - {{[ - {useTranslation('Voting')}, - , - ]}}, - ]}, - n === '%' - ? - : null, - ]}
- ); -} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/Dt.ApprovalBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/Dt.ApprovalBlock.tsx new file mode 100644 index 000000000..c7d8f9457 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/Dt.ApprovalBlock.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { ExtendCollectionsProvider, TableBlockProvider, useRecord } from '@nocobase/client'; +import { CollectionApprovalTodos } from '../../common/Cn.ApprovalTodos'; +import { CollectionApprovals } from '../approval-common/Cn.Approvals'; +import { CollectionFlowNodes } from '../approval-common/Cn.FlowNodes'; +import { CollectionWorkflows } from '../approval-common/Cn.Workflows'; + +export function ApprovalBlockDecorator({ children, ...props }) { + const { + collection, + action = 'list', + params = { + filter: {}, + }, + } = props; + + const record = useRecord(); + const config = { + collection, + resource: collection, + action, + params: { + pageSize: 20, + sort: ['-createdAt'], + ...params, + filter: record?.id ? { dataKey: `${record.id}`, ...params.filter } : { ...params.filter }, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }; + + return ( + + + {children} + + + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/VC.ApprovalBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/VC.ApprovalBlock.tsx new file mode 100644 index 000000000..8bc500dc9 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/VC.ApprovalBlock.tsx @@ -0,0 +1,74 @@ +import { TableOutlined } from '@ant-design/icons'; +import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '@nocobase/client'; +import { uid } from '@nocobase/utils/client'; +import React from 'react'; +import { NAMESPACE } from '../../locale'; +import { PathNameMap_ApprovalBlock } from './map'; + +// 创建区块-审批(发起/待办) +export const ApprovalBlockComponent = () => { + const schemaInitializerItem = useSchemaInitializerItem(); + const { insert } = useSchemaInitializer(); + return ( + } + {...schemaInitializerItem} + items={[ + { + type: 'item', + title: `{{t("Launch", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'ApprovalBlock.Launch', + collection: 'approvals', + params: { + appends: ['createdBy.nickname', 'workflow.title', 'workflow.enabled'], + except: ['data'], + }, + }, + { + type: 'item', + title: `{{t("Todos", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'ApprovalBlock.Todos', + collection: 'approvalRecords', + params: { + appends: [ + 'user.id', + 'user.nickname', + 'node.id', + 'node.title', + 'job.id', + 'job.status', + 'job.result', + 'workflow.id', + 'workflow.title', + 'workflow.enabled', + 'execution.id', + 'execution.status', + ], + }, + }, + ]} + onClick={({ item }) => { + const id = uid(); + insert({ + type: 'void', + name: id, + 'x-uid': id, + 'x-component': 'CardItem', + 'x-decorator': PathNameMap_ApprovalBlock.Decorator, + 'x-decorator-props': { + collection: item.collection, + action: 'listCentralized', + params: item.params, + }, + 'x-designer': 'TableBlockDesigner', + properties: { + block: { + type: 'void', + 'x-component': item['x-component'], + }, + }, + }); + }} + /> + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.ApprovalExecution.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.ApprovalExecution.tsx new file mode 100644 index 000000000..76c65f54a --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.ApprovalExecution.tsx @@ -0,0 +1,11 @@ +import { createContext, useContext } from 'react'; + +interface ApprovalExecution { + id: number; +} + +export const ContextApprovalExecution = createContext>({}); + +export function useContextApprovalExecution() { + return useContext(ContextApprovalExecution); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.FlowContext.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.FlowContext.tsx new file mode 100644 index 000000000..9681ba49e --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.FlowContext.tsx @@ -0,0 +1,10 @@ +import { FlowContext } from '@nocobase/plugin-workflow/client'; +import React, { useContext } from 'react'; + +export function useFlowContext() { + return useContext(FlowContext); +} + +export function FlowContextProvider({ workflow = undefined, children, value }) { + return {children}; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.SchemaComponent.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.SchemaComponent.tsx new file mode 100644 index 000000000..8bd8dc0c7 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/common/Pd.SchemaComponent.tsx @@ -0,0 +1,11 @@ +import { SchemaComponentContext, useSchemaComponentContext } from '@nocobase/client'; +import React from 'react'; + +export function SchemaComponentContextProvider({ designable, children }) { + const schemaComponentContext = useSchemaComponentContext(); + return ( + + {children} + + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/index.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/index.ts new file mode 100644 index 000000000..8045449f2 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/index.ts @@ -0,0 +1,37 @@ +import { Plugin } from '@nocobase/client'; +import PluginKitApprovalCommon from '../approval-common'; +import { tval } from '../../locale'; +import { ViewActionTodos } from './todos/VC.ViewActionTodos'; +import { ApprovalBlockComponent } from './VC.ApprovalBlock'; +import { ApprovalBlockTodos } from './todos/VC.ApprovalBlockTodos'; +import { ViewActionLaunch } from './launch/VC.ViewActionLaunch'; +import { ApprovalBlockDecorator } from './Dt.ApprovalBlock'; +import { ApprovalBlockLaunch } from './launch/VC.ApprovalBlockLaunch'; + +export default class PluginKitApprovalBlock extends Plugin { + async afterAdd() { + this.pm.add(PluginKitApprovalCommon); + } + + async beforeLoad() {} + + async load() { + this.app.addComponents({ + 'ApprovalBlock.Decorator': ApprovalBlockDecorator, + 'ApprovalBlock.BlockInitializer': ApprovalBlockComponent, + 'ApprovalBlock.Launch': ApprovalBlockLaunch, + 'ApprovalBlock.Todos': ApprovalBlockTodos, + 'ApprovalBlock.ViewActionLaunch': ViewActionLaunch, + 'ApprovalBlock.ViewActionTodos': ViewActionTodos, + }); + + this.app.schemaInitializerManager.get('page:addBlock').add('otherBlocks.approval', { + key: 'approvalBlock', + name: 'approvalBlock', + type: 'item', + title: tval('Approval'), + Component: 'ApprovalBlock.BlockInitializer', + icon: 'AuditOutlined', + }); + } +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/interface.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/interface.ts new file mode 100644 index 000000000..44c592011 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/interface.ts @@ -0,0 +1,15 @@ +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; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Dt.Record.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Dt.Record.tsx new file mode 100644 index 000000000..ffb6b7e9e --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Dt.Record.tsx @@ -0,0 +1,12 @@ +import { RecordProvider, useRecord } from '@nocobase/client'; +import React from 'react'; + +export function RecordDecorator(props) { + const { latestExecutionId } = useRecord(); + return ( + // @ts-ignore + + {props.children} + + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.ActionBar.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.ActionBar.tsx new file mode 100644 index 000000000..bf8f4ac4f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.ActionBar.tsx @@ -0,0 +1,20 @@ +import { useCurrentUserContext } from '@nocobase/client'; +import { useContextApprovalExecution } from '../common/Pd.ApprovalExecution'; +import { useApproval } from '../../approval-common/Pd.ApprovalData'; +import { APPROVAL_STATUS } from '../../../constants'; + +export function ActionBarProvider(props) { + const { data } = useCurrentUserContext(); + const { status, createdById, latestExecutionId } = useApproval(); + const approvalExecution = useContextApprovalExecution(); + + const isSameId = data.data.id === createdById; + const isSameExcutionId = latestExecutionId === approvalExecution.id; + const isExcutionDid = [APPROVAL_STATUS.DRAFT, APPROVAL_STATUS.RETURNED, APPROVAL_STATUS.SUBMITTED].includes(status); + + if (!isSameId || !isSameExcutionId || !isExcutionDid) { + return null; + } + + return props.children; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.ApplyActionStatus.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.ApplyActionStatus.tsx new file mode 100644 index 000000000..b49528795 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.ApplyActionStatus.tsx @@ -0,0 +1,28 @@ +import React, { useContext } from 'react'; +import { useCurrentUserContext } from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { createContext } from 'react'; +import { useApproval } from '../../approval-common/Pd.ApprovalData'; +import { APPROVAL_STATUS } from '../../../constants'; + +const ContextApprovalStatus = createContext(APPROVAL_STATUS.SUBMITTED); + +export function useContextApprovalStatus() { + return useContext(ContextApprovalStatus); +} + +export function ApplyActionStatusProvider(props) { + const { value, children } = props; + const { status, createdById } = useApproval(); + const { workflow } = useFlowContext(); + const { data } = useCurrentUserContext(); + const isSameId = data.data.id === createdById; + const isEnbled = workflow.enabled; + const isStatusDid = [APPROVAL_STATUS.DRAFT, APPROVAL_STATUS.RETURNED].includes(status); + + if (isSameId && isEnbled && isStatusDid) { + return {children}; + } + + return null; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.WithdrawAction.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.WithdrawAction.tsx new file mode 100644 index 000000000..4a259f14c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Pd.WithdrawAction.tsx @@ -0,0 +1,20 @@ +import { useCurrentUserContext } from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { APPROVAL_STATUS } from '../../../constants'; +import { useApproval } from '../../approval-common/Pd.ApprovalData'; + +export function WithdrawActionProvider({ children }) { + const { data } = useCurrentUserContext(); + const { status, createdById } = useApproval(); + const { workflow } = useFlowContext(); + + const isSameId = data.data.id === createdById; + const isEnabledWithdraw = workflow.enabled && workflow.config.withdrawable; + const isStatusSubmitted = APPROVAL_STATUS.SUBMITTED === status; + + if (isSameId && isEnabledWithdraw && isStatusSubmitted) { + return children; + } + + return null; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Sm.ApprovalBlockLaunch.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Sm.ApprovalBlockLaunch.tsx new file mode 100644 index 000000000..05fe3377a --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/Sm.ApprovalBlockLaunch.tsx @@ -0,0 +1,151 @@ +// import { PathNameMap_ApprovalBlock } from '../map'; +import { ApprovalBlock } from '../map'; +import { NAMESPACE } from '../../../locale'; +import { css } from '@nocobase/client'; + +export const SchemaApprovalBlockLaunch = { + type: 'void', + name: 'launch', + properties: { + actions: { + type: 'void', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 16, + }, + }, + properties: { + filter: { + type: 'void', + title: '{{ t("Filter") }}', + 'x-action': 'filter', + 'x-designer': 'Filter.Action.Designer', + 'x-component': 'Filter.Action', + 'x-use-component-props': 'useFilterActionProps', + 'x-component-props': { icon: 'FilterOutlined' }, + 'x-align': 'left', + }, + refresher: { + type: 'void', + title: '{{ t("Refresh") }}', + 'x-action': 'refresh', + 'x-component': 'Action', + 'x-use-component-props': 'useRefreshActionProps', + 'x-designer': 'Action.Designer', + 'x-component-props': { icon: 'ReloadOutlined' }, + 'x-align': 'right', + }, + apply: { + type: 'void', + title: `{{t("Apply new", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'ApplyButton', + 'x-align': 'right', + }, + }, + }, + table: { + type: 'array', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + tableLayout: 'fixed', + className: css` + .ant-table-cell { + text-align: center; + } + `, + }, + properties: { + action: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 60 }, + title: '{{t("Actions")}}', + properties: { + action: { + // 'x-component': PathNameMap_ApprovalBlock.ViewActionLaunch, + 'x-component': 'ApprovalBlock.ViewActionLaunch', + }, + }, + }, + id: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 80 }, + title: '{{t("ID")}}', + properties: { + id: { + type: 'number', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + workflow: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: 200, + }, + title: '{{t("Workflow", { ns: "workflow" })}}', + properties: { + workflow: { + 'x-component': 'WorkflowColumn', + 'x-read-pretty': true, + }, + }, + }, + status: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: 100, + }, + title: '{{t("Status", { ns: "workflow" })}}', + properties: { + status: { + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + createdBy: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: 140, + }, + title: `{{t("Initiator", { ns: "${NAMESPACE}" })}}`, + properties: { + createdBy: { + 'x-component': 'UserColumn', + 'x-read-pretty': true, + }, + }, + }, + createdAt: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: 160, + }, + properties: { + createdAt: { + type: 'string', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ApprovalBlockLaunch.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ApprovalBlockLaunch.tsx new file mode 100644 index 000000000..b4d1f3cb3 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ApprovalBlockLaunch.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { SchemaComponent } from '@nocobase/client'; +import { UserColumn } from '../../approval-common/approval-columns/column.user'; +import { WorkflowColumn } from '../../approval-common/approval-columns/column.workflow'; +import { NodeColumn } from '../../approval-common/approval-columns/column.node'; +import { SchemaApprovalBlockLaunch } from './Sm.ApprovalBlockLaunch'; +import { ApplyButton } from './apply-button/VC.ApplyButton'; + +// 审批-发起: 区块表格 +export const ApprovalBlockLaunch = () => { + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ViewActionLaunch.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ViewActionLaunch.tsx new file mode 100644 index 000000000..7c81302e8 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ViewActionLaunch.tsx @@ -0,0 +1,35 @@ +import { SchemaComponent, useRecord } from '@nocobase/client'; +import React from 'react'; +import { RecordDecorator } from './Dt.Record'; +import { ViewActionLaunchContent } from './VC.ViewActionLaunchContent'; + +// 审批-发起: 操作-查看 +export const ViewActionLaunch = ({ popoverComponent = 'Action.Drawer', popoverComponentProps = {} }) => { + const record = useRecord(); + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ViewActionLaunchContent.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ViewActionLaunchContent.tsx new file mode 100644 index 000000000..f33776857 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/VC.ViewActionLaunchContent.tsx @@ -0,0 +1,153 @@ +import React from 'react'; +import { + RemoteSchemaComponent, + SchemaComponent, + SchemaComponentProvider, + useFormBlockContext, + useRecord, + useRequest, +} from '@nocobase/client'; +import { DetailsBlockProvider, FlowContext } from '@nocobase/plugin-workflow/client'; +import { useForm } from '@nocobase/schema'; +import { Result, Spin } from 'antd'; +import { useContext } from 'react'; +import { NAMESPACE } from '../../../locale'; +import { FormBlockProvider } from '../../../common/Pd.FormBlock'; +import { ContextApprovalExecution } from '../common/Pd.ApprovalExecution'; +import { ApprovalContext } from '../../approval-common/Pd.ApprovalData'; +import { ContextWithActionEnabled } from '../../approval-common/Pd.WithActionEnabled'; +import { useFormBlockProps } from './hooks/useFormBlockProps'; +import { useSubmit } from './hooks/useSubmit'; +import { useWithdrawAction } from './hooks/useWithdrawAction'; +import { useDestroyAction } from './hooks/useDestroyAction'; +import { ActionBarProvider } from './Pd.ActionBar'; +import { WithdrawActionProvider } from './Pd.WithdrawAction'; +import { SchemaComponentContextProvider } from '../common/Pd.SchemaComponent'; +import { ApplyActionStatusProvider } from './Pd.ApplyActionStatus'; +import { ApprovalCommon } from '../../approval-common/map'; +import { FlowContextProvider } from '../common/Pd.FlowContext'; + +export const ViewActionLaunchContent = () => { + const { id } = useRecord(); + const { actionEnabled } = useContext(ContextWithActionEnabled); + const { loading, data } = useRequest( + { + resource: 'approvalExecutions', + action: 'get', + params: { + filterByTk: 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'], + }, + }, + { refreshDeps: [id] }, + ) as any; + // @ts-ignore + const approvalData = data?.data; + const { approval, execution, ...approvalValue } = approvalData || {}; + const { workflow } = approval || {}; + + if (loading) { + return ; + } + + if (!approvalData) { + return ; + } + + return ( + + + + + + + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionBar.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionBar.tsx new file mode 100644 index 000000000..a87128bff --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionBar.tsx @@ -0,0 +1,3 @@ +export function ActionBarProvider(props) { + return props.children; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionStatus.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionStatus.tsx new file mode 100644 index 000000000..3911144a7 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionStatus.tsx @@ -0,0 +1,13 @@ +import React, { useContext } from 'react'; +import { createContext } from 'react'; +import { APPROVAL_STATUS } from '../../../../constants'; + +const ContextApprovalStatus = createContext(APPROVAL_STATUS.SUBMITTED); + +export function useContextApprovalStatus() { + return useContext(ContextApprovalStatus); +} + +export function ApplyActionStatusProvider(props) { + return {props.children}; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionWithdraw.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionWithdraw.tsx new file mode 100644 index 000000000..7f21237e2 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/Pd.ActionWithdraw.tsx @@ -0,0 +1,3 @@ +export function WithdrawActionProvider() { + return null; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/VC.ApplyButton.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/VC.ApplyButton.tsx new file mode 100644 index 000000000..9ca672949 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/VC.ApplyButton.tsx @@ -0,0 +1,123 @@ +import { DownOutlined, PlusOutlined } from '@ant-design/icons'; +import { + ActionContextProvider, + CollectionProvider_deprecated, + RemoteSchemaComponent, + SchemaComponent, + SchemaComponentContext, + css, + parseCollectionName, + useAPIClient, +} from '@nocobase/client'; +import { Button, Dropdown } from 'antd'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { useTranslation } from '../../../../locale'; +import { useSubmit } from './hooks/useSubmit'; +import { useWithdrawAction } from './hooks/useWithdrawAction'; +import { ActionBarProvider } from './Pd.ActionBar'; +import { ApplyActionStatusProvider } from './Pd.ActionStatus'; +import { FlowContextProvider } from '../../common/Pd.FlowContext'; +import { WithdrawActionProvider } from './Pd.ActionWithdraw'; + +// 审批-发起: 发起按钮 +export const ApplyButton = () => { + const { t } = useTranslation(); + const context = useContext(SchemaComponentContext); + const apiClient = useAPIClient(); + const [visible, setVisible] = useState(false); + const [items, setItems] = useState([]); + const [schema, setSchema] = useState(null); + + useEffect(() => { + apiClient + .resource('workflows') + .listApprovalFlows({ filter: { 'config.centralized': true } }) + .then(({ data }) => { + setItems(data.data); + }) + .catch(console.error); + }, []); + + const onClick = useCallback( + ({ key }) => { + const targetItems = items.find((item) => +item.id === +key); + const { applyForm } = targetItems?.config ?? {}; + const [dataSource, name] = parseCollectionName(targetItems.config.collection); + + setSchema({ + type: 'void', + properties: { + [`drawer-${targetItems.id}`]: { + type: 'void', + title: targetItems.title, + 'x-decorator': 'FlowContextProvider', + 'x-decorator-props': { + workflow: targetItems, + }, + 'x-component': 'Action.Drawer', + 'x-component-props': { + className: css` + .ant-drawer-body { + background: var(--nb-box-bg); + } + `, + }, + properties: { + [applyForm]: { + type: 'void', + 'x-decorator': 'CollectionProvider_deprecated', + 'x-decorator-props': { + name, + dataSource, + }, + 'x-component': 'RemoteSchemaComponent', + 'x-component-props': { + uid: applyForm, + noForm: true, + }, + }, + }, + }, + }, + }); + setVisible(true); + }, + [items], + ); + return ( + + ({ + key: id, + label: title, + })), + onClick, + }} + disabled={!items.length} + > + + + + + + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/hooks/useSubmit.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/hooks/useSubmit.tsx new file mode 100644 index 000000000..eb82e10ed --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/hooks/useSubmit.tsx @@ -0,0 +1,49 @@ +import { + joinCollectionName, + useAPIClient, + useActionContext, + useBlockRequestContext, + useCollection_deprecated, +} from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { useField, useForm } from '@nocobase/schema'; +import { useContextApprovalStatus } from '../Pd.ActionStatus'; + +export function useSubmit() { + const from = useForm(); + const field = useField(); + const { setVisible } = useActionContext(); + const { __parent } = useBlockRequestContext(); + const collection = useCollection_deprecated(); + const contextWe = useContextApprovalStatus(); + const apiClient = useAPIClient(); + const { workflow } = useFlowContext(); + + return { + run() { + return () => { + try { + from.submit(), (field.data = field.data || {}); + field.data.loading = true; + apiClient.resource('approvals').create({ + values: { + collectionName: joinCollectionName(collection.dataSource, collection.name), + data: from.values, + status: contextWe, + workflowId: workflow.id, + }, + }); + setVisible(false); + from.reset(); + field.data.loading = false; + const service = __parent.service; + if (service) { + service.refresh(); + } + } catch (h) { + field.data && (field.data.loading = false); + } + }; + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/hooks/useWithdrawAction.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/hooks/useWithdrawAction.tsx new file mode 100644 index 000000000..988ea5edc --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/apply-button/hooks/useWithdrawAction.tsx @@ -0,0 +1,3 @@ +export function useWithdrawAction() { + return { run() {} }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useDestroyAction.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useDestroyAction.tsx new file mode 100644 index 000000000..f2322c030 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useDestroyAction.tsx @@ -0,0 +1,31 @@ +import { useAPIClient, useActionContext } from '@nocobase/client'; +import { useField } from '@nocobase/schema'; +import { useApproval } from '../../../approval-common/Pd.ApprovalData'; +import _ from 'lodash'; + +export function useDestroyAction() { + const field = useField(); + const { setVisible, setSubmitted } = useActionContext() as any; + const approval = useApproval(); + const apiClient = useAPIClient(); + + const run = () => { + return () => { + try { + _.set(field, ['data', 'loading'], true); + + apiClient.resource('approvals').destroy({ + filterByTk: approval.id, + }); + + setSubmitted(true); + setVisible(false); + } catch (err) { + _.set(field, ['data', 'loading'], false); + } + }; + }; + return { + run, + }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useFormBlockProps.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useFormBlockProps.tsx new file mode 100644 index 000000000..d164e8fcd --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useFormBlockProps.tsx @@ -0,0 +1,37 @@ +import { useCurrentUserContext } from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { useForm } from '@nocobase/schema'; +import { useEffect } from 'react'; +import { useContextApprovalExecution } from '../../common/Pd.ApprovalExecution'; +import { useApproval } from '../../../approval-common/Pd.ApprovalData'; +import { ApprovalStatusEnumDict } from '../../../../constants'; + +export function useFormBlockProps() { + const approval = useApproval() as any; + const approvalExecution = useContextApprovalExecution(); + const { workflow } = useFlowContext(); + const form = useForm(); + const { data } = useCurrentUserContext(); + + const { editable } = ApprovalStatusEnumDict[approval.status]; + + const needEditable = + editable && + approval?.latestExecutionId === approvalExecution.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/src/client/usage/approval-block/launch/hooks/useSubmit.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useSubmit.tsx new file mode 100644 index 000000000..b757bccd0 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useSubmit.tsx @@ -0,0 +1,45 @@ +import { useAPIClient, useActionContext } from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { useField, useForm } from '@nocobase/schema'; +import _ from 'lodash'; +import { useApproval } from '../../../approval-common/Pd.ApprovalData'; +import { useContextApprovalStatus } from '../Pd.ApplyActionStatus'; + +export function useSubmit() { + const from = useForm(); + const field = useField(); + const { setVisible, setSubmitted } = useActionContext() as any; + const { id } = useApproval(); + const { workflow } = useFlowContext(); + const contextApprovalStatus = useContextApprovalStatus(); + const apiClient = useAPIClient(); + + const run = () => { + return () => { + 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); + } + }; + }; + + return { + run, + }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useWithdrawAction.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useWithdrawAction.tsx new file mode 100644 index 000000000..ddaf567bc --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/launch/hooks/useWithdrawAction.tsx @@ -0,0 +1,30 @@ +import { useAPIClient, useActionContext } from '@nocobase/client'; +import { useField } from '@nocobase/schema'; +import { useApproval } from '../../../approval-common/Pd.ApprovalData'; + +export function useWithdrawAction() { + const field = useField(); + const { setVisible, setSubmitted } = useActionContext() as any; + const approval = useApproval(); + const api = useAPIClient(); + return { + run() { + return async () => { + try { + field.data = field.data ?? {}; + field.data.loading = true; + await api.resource('approvals').withdraw({ + filterByTk: approval.id, + }); + setSubmitted(true); + setVisible(false); + field.data.loading = false; + } catch (v) { + if (field.data) { + field.data.loading = false; + } + } + }; + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/map.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/map.tsx new file mode 100644 index 000000000..0a07e0f66 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/map.tsx @@ -0,0 +1,31 @@ +import { ViewActionTodos } from './todos/VC.ViewActionTodos'; +import { ApprovalBlockComponent } from './VC.ApprovalBlock'; +import { ApprovalBlockTodos } from './todos/VC.ApprovalBlockTodos'; +import { ViewActionLaunch } from './launch/VC.ViewActionLaunch'; +import { ApprovalBlockDecorator } from './Dt.ApprovalBlock'; + +import { ApprovalBlockLaunch } from './launch/VC.ApprovalBlockLaunch'; + +// 区块-审批 +export const ApprovalBlock = () => null; + +export const PathNameMap_ApprovalBlock = { + Decorator: 'ApprovalBlock.Decorator', + BlockInitializer: 'ApprovalBlock.BlockInitializer', + Launch: 'ApprovalBlock.Launch', + Todos: 'ApprovalBlock.Todos', + ViewActionLaunch: 'ApprovalBlock.ViewActionLaunch', + ViewActionTodos: 'ApprovalBlock.ViewActionTodos', +}; + +ApprovalBlock.Decorator = ApprovalBlockDecorator; + +ApprovalBlock.BlockInitializer = ApprovalBlockComponent; + +ApprovalBlock.Launch = ApprovalBlockLaunch; + +ApprovalBlock.Todos = ApprovalBlockTodos; + +ApprovalBlock.ViewActionLaunch = ViewActionLaunch; + +ApprovalBlock.ViewActionTodos = ViewActionTodos; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Dt.ApprovalFormBlock.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Dt.ApprovalFormBlock.tsx new file mode 100644 index 000000000..2953d4334 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Dt.ApprovalFormBlock.tsx @@ -0,0 +1,92 @@ +import { + BlockRequestContext_deprecated, + CollectionProvider_deprecated, + FormActiveFieldsProvider, + FormBlockContext, + FormV2, + RecordProvider, + SchemaComponent, + useAPIClient, + useAssociationNames, + useCurrentUserContext, + useDesignable, +} from '@nocobase/client'; +import { useFlowContext } from '@nocobase/plugin-workflow/client'; +import { RecursionField, createForm, useField, useFieldSchema } from '@nocobase/schema'; +import _ from 'lodash'; +import React, { Fragment, useContext, useMemo, useRef } from 'react'; +import { useContextApprovalExecutions } from './Pd.ApprovalExecutions'; + +export function ApprovalFormBlockDecorator(props) { + const approvalExecutions = useContextApprovalExecutions(); + const { job } = approvalExecutions; + const omitApproval = _.omit(approvalExecutions, ['approval', 'job', 'node', 'snapshot']); + const { execution, workflow } = useFlowContext(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const ref = useRef(null); + const { getAssociationAppends } = useAssociationNames(); + const { appends, updateAssociationValues } = getAssociationAppends(); + const { data } = useCurrentUserContext(); + const { findComponent } = useDesignable(); + const component = 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 request = useMemo( + () => ({ + params: params, + form: form, + field: field, + service: result, + updateAssociationValues: updateAssociationValues, + formBlockRef: ref, + }), + [field, form, params, result, updateAssociationValues], + ); + + if (!omitApproval.status && omitApproval.userId !== data.data?.id) { + return null; + } + + return ( + + {/* @ts-ignore */} + + + + + + +
+ +
+
+
+
+
+
+
+ ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ActionBarProvider.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ActionBarProvider.tsx new file mode 100644 index 000000000..f49b78dcc --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ActionBarProvider.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { ActionBarProvider as ClientActionBarProvider, useCompile } from '@nocobase/client'; +import { str2moment } from '@nocobase/utils/client'; +import { Space, Tag } from 'antd'; +import { approvalStatusConfigObj } from '../../../constants'; +import { useContextApprovalExecutions } from './Pd.ApprovalExecutions'; + +export function ActionBarProvider(props) { + const { status } = useContextApprovalExecutions(); + + if (status) { + return ; + } else { + return ; + } +} + +const ComponentUserInfo = () => { + const compile = useCompile(); + const { status, updatedAt, user } = useContextApprovalExecutions() as any; + const configObj = approvalStatusConfigObj[status]; + return ( + + {compile(configObj.label)} + + {user.nickname} + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ApprovalAction.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ApprovalAction.tsx new file mode 100644 index 000000000..47bab1613 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ApprovalAction.tsx @@ -0,0 +1,23 @@ +import React, { useContext } from 'react'; +import { createContext } from 'react'; +import { useContextApprovalExecutions } from './Pd.ApprovalExecutions'; + +export interface ApprovalAction { + status: number | null; +} + +const ContextApprovalAction = createContext>({}); + +export function useContextApprovalAction() { + return useContext(ContextApprovalAction); +} + +export function ApprovalActionProvider({ children, ...props }) { + const context = useContextApprovalExecutions(); + + if (context && context.status !== props.status) { + return null; + } + + return {children}; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ApprovalExecutions.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ApprovalExecutions.tsx new file mode 100644 index 000000000..c0de701d4 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Pd.ApprovalExecutions.tsx @@ -0,0 +1,22 @@ +import { createContext, useContext } from 'react'; +import { Approval } from '../interface'; + +interface ApprovalExecutions { + id: number; + approval: Approval; + + execution: any; + status: number; + + snapshot: any; + records: any[]; + + job?: any; + userId?: any; +} + +export const ContextApprovalExecutions = createContext>({}); + +export function useContextApprovalExecutions() { + return useContext(ContextApprovalExecutions); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Sm.ApprovalBlockTodos.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Sm.ApprovalBlockTodos.tsx new file mode 100644 index 000000000..b31298739 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/Sm.ApprovalBlockTodos.tsx @@ -0,0 +1,149 @@ +import { css } from '@nocobase/client'; +import { NAMESPACE } from '../../../locale'; + +export const SchemaApprovalBlockTodos = { + type: 'void', + name: 'todos', + properties: { + actions: { + type: 'void', + 'x-component': 'ActionBar', + 'x-component-props': { style: { marginBottom: 16 } }, + properties: { + filter: { + type: 'void', + title: '{{ t("Filter") }}', + 'x-action': 'filter', + 'x-designer': 'Filter.Action.Designer', + 'x-component': 'Filter.Action', + 'x-use-component-props': 'useFilterActionProps', + 'x-component-props': { icon: 'FilterOutlined' }, + 'x-align': 'left', + }, + refresher: { + type: 'void', + title: '{{ t("Refresh") }}', + 'x-action': 'refresh', + 'x-component': 'Action', + 'x-use-component-props': 'useRefreshActionProps', + 'x-designer': 'Action.Designer', + 'x-component-props': { icon: 'ReloadOutlined' }, + 'x-align': 'right', + }, + }, + }, + table: { + type: 'array', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + tableLayout: 'fixed', + className: css` + .ant-table-cell { + text-align: center; + } + `, + }, + properties: { + action: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 60 }, + title: '{{t("Actions")}}', + properties: { + action: { + 'x-component': 'ApprovalBlock.ViewActionTodos', + }, + }, + }, + approvalId: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: 80, + }, + title: '{{t("ID")}}', + properties: { + approvalId: { + type: 'number', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + }, + workflow: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: null, + }, + title: '{{t("Workflow", { ns: "workflow" })}}', + properties: { + workflow: { + 'x-component': 'WorkflowColumn', + 'x-read-pretty': true, + }, + }, + }, + node: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { + width: null, + }, + title: `{{t("Task node", { ns: "${NAMESPACE}" })}}`, + properties: { + node: { + 'x-component': 'NodeColumn', + 'x-read-pretty': true, + }, + }, + }, + status: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 100 }, + title: '{{t("Status", { ns: "workflow" })}}', + properties: { + status: { + 'x-component': 'ApprovalRecordStatusColumn', + 'x-read-pretty': true, + }, + }, + }, + user: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 140 }, + title: `{{t("Assignee", { ns: "${NAMESPACE}" })}}`, + properties: { + user: { + 'x-component': 'UserColumn', + 'x-read-pretty': true, + }, + }, + }, + createdAt: { + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-component': 'TableV2.Column', + 'x-component-props': { width: 160 }, + properties: { + createdAt: { + type: 'string', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ApprovalBlockTodos.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ApprovalBlockTodos.tsx new file mode 100644 index 000000000..3075725f6 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ApprovalBlockTodos.tsx @@ -0,0 +1,20 @@ +import { SchemaComponent } from '@nocobase/client'; +import React from 'react'; +import { NodeColumn } from '../../approval-common/approval-columns/column.node'; +import { ApprovalRecordStatusColumn } from '../../approval-common/approval-columns/column.status'; +import { UserColumn } from '../../approval-common/approval-columns/column.user'; +import { WorkflowColumn } from '../../approval-common/approval-columns/column.workflow'; +import { SchemaApprovalBlockTodos } from './Sm.ApprovalBlockTodos'; + +// 审批-待办 +export const ApprovalBlockTodos = () => ( + +); diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ViewActionTodos.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ViewActionTodos.tsx new file mode 100644 index 000000000..99d3c48dd --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ViewActionTodos.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { SchemaComponent, useRecord } from '@nocobase/client'; +import { ViewActionTodosContent } from './VC.ViewActionTodosContent'; + +// 审批-待办: 操作-查看 +export const ViewActionTodos = ({ popoverComponent = 'Action.Drawer', popoverComponentProps = {} }) => { + const record = useRecord(); + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ViewActionTodosContent.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ViewActionTodosContent.tsx new file mode 100644 index 000000000..eb4a8daba --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/VC.ViewActionTodosContent.tsx @@ -0,0 +1,150 @@ +// @ts-nocheck +import { + RemoteSchemaComponent, + SchemaComponent, + SchemaComponentProvider, + useFormBlockContext, + useRecord, + useRequest, +} from '@nocobase/client'; +import { DetailsBlockProvider, ExecutionContextProvider } from '@nocobase/plugin-workflow/client'; +import { Result, Spin } from 'antd'; +import _ from 'lodash'; +import React, { useContext } from 'react'; +import { ContextWithActionEnabled } from '../../approval-common/Pd.WithActionEnabled'; +import { ContextApprovalExecution } from '../common/Pd.ApprovalExecution'; +import { ContextApprovalExecutions } from './Pd.ApprovalExecutions'; +import { NAMESPACE, useTranslation } from '../../../locale'; +import { FormBlockProvider } from '../../../common/Pd.FormBlock'; +import { ApprovalContext } from '../../approval-common/Pd.ApprovalData'; +import { ApprovalCommon } from '../../approval-common/map'; +import { SchemaComponentContextProvider } from '../common/Pd.SchemaComponent'; +import { useApprovalDetailBlockProps } from './hooks/useApprovalDetailBlockProps'; +import { useApprovalFormBlockProps } from './hooks/useApprovalFormBlockProps'; +import { useSubmit } from './hooks/useSubmit'; +import { ApprovalFormBlockDecorator } from './Dt.ApprovalFormBlock'; +import { ActionBarProvider } from './Pd.ActionBarProvider'; +import { ApprovalActionProvider } from './Pd.ApprovalAction'; +// 审批-待办-查看: 内容 +export const ViewActionTodosContent = () => { + const { id } = useRecord(); + const { t } = useTranslation(); + const { actionEnabled } = useContext(ContextWithActionEnabled); + const { loading, data } = useRequest( + { + resource: 'approvalRecords', + action: 'get', + params: { + filterByTk: 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'], + }, + }, + { refreshDeps: [id] }, + ); + if (loading) return ; + if (!(data != null && data.data)) + return ; + const items = data.data; + const { approvalExecution, node, approval, workflow } = items; + const { nodes } = workflow; + const omitWorkflow = _.omit(workflow, ['nodes']); + const { execution } = items; + // return null; + return ( + + + + + + + + + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useApprovalDetailBlockProps.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useApprovalDetailBlockProps.tsx new file mode 100644 index 000000000..765593c7d --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useApprovalDetailBlockProps.tsx @@ -0,0 +1,14 @@ +import { useFormBlockContext } from '@nocobase/client'; +import { useEffect } from 'react'; +import { useContextApprovalExecutions } from '../Pd.ApprovalExecutions'; + +export function useApprovalDetailBlockProps() { + const { snapshot } = useContextApprovalExecutions(); + const { form } = useFormBlockContext(); + + useEffect(() => { + form.setValues(snapshot); + }, [form, snapshot]); + + return { form }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useApprovalFormBlockProps.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useApprovalFormBlockProps.tsx new file mode 100644 index 000000000..3bd0f1ea6 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useApprovalFormBlockProps.tsx @@ -0,0 +1,6 @@ +import { useFormBlockContext } from '@nocobase/client'; + +export function useApprovalFormBlockProps() { + const { form } = useFormBlockContext(); + return { form }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useSubmit.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useSubmit.tsx new file mode 100644 index 000000000..8ec88677f --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-block/todos/hooks/useSubmit.tsx @@ -0,0 +1,35 @@ +import { useAPIClient, useActionContext } from '@nocobase/client'; +import { useField, useForm } from '@nocobase/schema'; +import { useContextApprovalAction } from '../Pd.ApprovalAction'; +import { useContextApprovalExecutions } from '../Pd.ApprovalExecutions'; + +export function useSubmit() { + const field = useField(); + const api = useAPIClient(); + const form = useForm(); + const approvalExecutions = useContextApprovalExecutions(); + const { status } = useContextApprovalAction(); + const { setVisible, setSubmitted } = useActionContext() as any; + return { + run() { + return async () => { + try { + if (form.values.status) return; + await form.submit(), (field.data = field.data ?? {}); + field.data.loading = true; + await api.resource('approvalRecords').submit({ + filterByTk: approvalExecutions.id, + values: { ...form.values, status }, + }); + field.data.loading = false; + await form.reset(); + setSubmitted(true); + setVisible(false); + } catch (error) { + console.error(error); + field.data && (field.data.loading = false); + } + }; + }, + }; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.Approvals.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.Approvals.tsx new file mode 100644 index 000000000..81fb4b4ac --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.Approvals.tsx @@ -0,0 +1,63 @@ +import { NAMESPACE } from '../../locale'; +import { ApprovalStatusEnums } from '../../constants'; + +export const CollectionApprovals = { + title: `{{t("Approval applications", { ns: "${NAMESPACE}" })}}`, + name: 'approvals', + fields: [ + { + type: 'bigInt', + name: 'id', + interface: 'number', + uiSchema: { type: 'number', title: 'ID', 'x-component': 'InputNumber' }, + }, + { + type: 'belongsTo', + name: 'workflow', + target: 'workflows', + foreignKey: 'workflowId', + interface: 'm2o', + uiSchema: { + type: 'number', + title: '{{t("Workflow", { ns: "workflow" })}}', + 'x-component': 'RemoteSelect', + 'x-component-props': { fieldNames: { label: 'title', value: 'id' }, service: { resource: 'workflows' } }, + }, + }, + { + type: 'belongsTo', + name: 'createdBy', + target: 'users', + foreignKey: 'createdById', + interface: 'm2o', + uiSchema: { + type: 'number', + title: `{{t("Initiator", { ns: "${NAMESPACE}" })}}`, + 'x-component': 'RemoteSelect', + 'x-component-props': { fieldNames: { label: 'nickname', value: 'id' }, service: { resource: 'users' } }, + }, + }, + { + type: 'integer', + name: 'status', + interface: 'select', + uiSchema: { + type: 'number', + title: '{{t("Status", { ns: "workflow" })}}', + 'x-component': 'Select', + enum: ApprovalStatusEnums, + }, + }, + { + name: 'createdAt', + type: 'date', + interface: 'createdAt', + uiSchema: { + type: 'datetime', + title: '{{t("Created at")}}', + 'x-component': 'DatePicker', + 'x-component-props': { showTime: true }, + }, + }, + ], +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.FlowNodes.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.FlowNodes.tsx new file mode 100644 index 000000000..20bad3911 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.FlowNodes.tsx @@ -0,0 +1,28 @@ +import { NAMESPACE } from '../../locale'; + +export const CollectionFlowNodes = { + title: `{{t("Node", { ns: "${NAMESPACE}" })}}`, + name: 'flow_nodes', + fields: [ + { + type: 'bigInt', + name: 'id', + interface: 'm2o', + uiSchema: { + type: 'number', + title: 'ID', + 'x-component': 'RemoteSelect', + 'x-component-props': { + fieldNames: { label: 'title', value: 'id' }, + service: { resource: 'flow_nodes', params: { filter: { type: 'manual' } } }, + }, + }, + }, + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { type: 'string', title: '{{t("Title")}}', 'x-component': 'Input' }, + }, + ], +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.Workflows.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.Workflows.tsx new file mode 100644 index 000000000..af87ba5af --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Cn.Workflows.tsx @@ -0,0 +1,28 @@ +import { NAMESPACE } from '../../locale'; + +export const CollectionWorkflows = { + title: `{{t("Workflow", { ns: "${NAMESPACE}" })}}`, + name: 'workflows', + fields: [ + { + type: 'string', + name: 'title', + interface: 'input', + uiSchema: { title: '{{t("Name")}}', type: 'string', 'x-component': 'Input', required: true }, + }, + { + type: 'boolean', + name: 'enabled', + interface: 'select', + uiSchema: { + type: 'boolean', + title: '{{t("Status", { ns: "workflow" })}}', + 'x-component': 'Select', + enum: [ + { label: '{{t("On", { ns: "workflow" })}}', value: true }, + { label: '{{t("Off", { ns: "workflow" })}}', value: false }, + ], + }, + }, + ], +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Pd.ApprovalData.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Pd.ApprovalData.tsx new file mode 100644 index 000000000..e7c561dc7 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Pd.ApprovalData.tsx @@ -0,0 +1,14 @@ +import { useRecord } from '@nocobase/client'; +import React, { createContext, useContext } from 'react'; +import { Approval } from '../approval-block/interface'; + +export const ApprovalContext = createContext>({}); + +export function useApproval() { + return useContext(ApprovalContext); +} +export function ApprovalDataProvider(props) { + const { useData = useRecord } = props; + const recordData = useData(); + return {props.children}; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Pd.WithActionEnabled.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Pd.WithActionEnabled.tsx new file mode 100644 index 000000000..c55c50237 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/Pd.WithActionEnabled.tsx @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const ContextWithActionEnabled = createContext({ actionEnabled: false }); diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/VC.ApprovalProcess.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/VC.ApprovalProcess.tsx new file mode 100644 index 000000000..774fe99d6 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/VC.ApprovalProcess.tsx @@ -0,0 +1,116 @@ +import { createStyles, useCurrentUserContext } from '@nocobase/client'; +import { EXECUTION_STATUS } from '@nocobase/plugin-workflow/client'; +import { Space, Table } from 'antd'; +import _ from 'lodash'; +import React, { useMemo } from 'react'; +import { useApproval } from './Pd.ApprovalData'; +import { APPROVAL_ACTION_STATUS, APPROVAL_STATUS } from '../../constants'; +import { ContextWithActionEnabled } from './Pd.WithActionEnabled'; +import { usePluginTranslation, useTranslation } from '../../locale'; +import { getAntdTableColumns } from './process-columns/columns'; + +// 审批(发起/待办)区块-查看-审批处理 +export const ApprovalProcess = (props) => { + const { t } = usePluginTranslation(); + const approvalContext = useApproval(); + 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 { t } = useTranslation(); + 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: t('Apply') }, + user: { + ...approval.createdBy, + id: approval.createdById, + }, + status: curr.status ? APPROVAL_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_ACTION_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, + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.node.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.node.tsx new file mode 100644 index 000000000..091f7b925 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.node.tsx @@ -0,0 +1,17 @@ +import { observer, useField, Field } from '@nocobase/schema'; + +export const NodeColumn = observer( + () => { + const field = useField(); + const fieldValue = field?.value; + + if (fieldValue?.title) { + return fieldValue.title; + } else if (fieldValue?.id) { + return `#${fieldValue.id}`; + } + + return ''; + }, + { displayName: 'NodeColumn' }, +); diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.status.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.status.tsx new file mode 100644 index 000000000..5ca45703b --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.status.tsx @@ -0,0 +1,33 @@ +import { useCompile, useRecord } from '@nocobase/client'; +import { Field, observer, useField } from '@nocobase/schema'; +import { Tag } from 'antd'; +import _ from 'lodash'; +import React from 'react'; +import { APPROVAL_ACTION_STATUS, approvalStatusConfigObj } from '../../../constants'; +import { useTranslation } from '../../../locale'; + +export const ApprovalRecordStatusColumn = observer( + () => { + const { value } = useField(); + const record = useRecord(); + return ; + }, + { displayName: 'ApprovalRecordStatusColumn' }, +); + +// 审批-待办:状态列:值 +export const ColumnStatusComponent = (props) => { + const { value, record } = props; + const omitProps = _.omit(props, ['value', 'record']); + const compile = useCompile(); + const { t } = useTranslation(); + const { option = approvalStatusConfigObj[value] } = omitProps; + const { workflow, execution, job } = record ?? {}; + + return (!(workflow != null && workflow.enabled) || (execution != null && execution.stauts) || job?.status) && + [APPROVAL_ACTION_STATUS.ASSIGNED, APPROVAL_ACTION_STATUS.PENDING].includes(value) ? ( + {t('Unprocessed')} + ) : ( + {compile(option.label)} + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.user.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.user.tsx new file mode 100644 index 000000000..3ba459e79 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.user.tsx @@ -0,0 +1,16 @@ +import { observer, useField, Field } from '@nocobase/schema'; + +export const UserColumn = observer( + () => { + const field = useField(); + const fieldValue = field?.value; + + if (fieldValue?.nickname) { + return fieldValue.nickname; + } else if (fieldValue?.id !== undefined) { + return fieldValue.id; + } + return ''; + }, + { displayName: 'UserColumn' }, +); diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.workflow.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.workflow.tsx new file mode 100644 index 000000000..ca1e4a556 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/approval-columns/column.workflow.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { css } from '@nocobase/client'; +import { observer, useField, Field } from '@nocobase/schema'; +import { useTranslation } from '../../../locale'; + +export const WorkflowColumn = observer( + () => { + const { t } = useTranslation(); + const { value } = useField(); + const title = value?.title || `#${value?.id}`; + + if (value?.enabled) { + return title; + } else { + return ( + + {title} + + ); + } + }, + { displayName: 'WorkflowColumn' }, +); diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/index.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/index.ts new file mode 100644 index 000000000..27838ed60 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/index.ts @@ -0,0 +1,12 @@ +import { Plugin } from '@nocobase/client'; +import { ApprovalCommon } from './map'; + +export default class PluginKitApprovalCommon extends Plugin { + async afterAdd() {} + async beforeLoad() {} + async load() { + this.app.addComponents({ + ApprovalCommon, + }); + } +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/map.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/map.ts new file mode 100644 index 000000000..e40cf1ac5 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/map.ts @@ -0,0 +1,26 @@ +import { ApprovalDataProvider } from './Pd.ApprovalData'; +import { ApprovalProcess } from './VC.ApprovalProcess'; + +// 通用,审批 +export const ApprovalCommon = () => null; + +ApprovalCommon.PathNameMap = { + Provider: { + ApprovalDataProvider: 'ApprovalCommon.Provider.ApprovalDataProvider', + }, + ViewComponent: { + ApprovalProcess: 'ApprovalCommon.ViewComponent.ApprovalProcess', + }, +}; + +ApprovalCommon.Decorator = null; + +ApprovalCommon.Provider = { + ApprovalDataProvider, +}; + +ApprovalCommon.BlockInitializer = null; + +ApprovalCommon.ViewComponent = { + ApprovalProcess, +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/columns.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/columns.ts new file mode 100644 index 000000000..713a6be93 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/columns.ts @@ -0,0 +1,36 @@ +import { renderColumnDetail } from './render.detail'; +import { renderColumnStatus } from './render.status'; +import { renderColumnTaskNode } from './render.taskNode'; + +export const getAntdTableColumns = ({ t, styles }) => { + return [ + { + title: t('Task node'), + dataIndex: ['node', 'title'], + onCell(taskNode) { + return { + rowSpan: taskNode.groupCount ?? 0, + }; + }, + render: renderColumnTaskNode, + }, + { + title: t('User'), + dataIndex: ['user', 'nickname'], + width: 120, + }, + { + title: t('Status', { ns: 'workflow' }), + dataIndex: 'status', + render: renderColumnStatus, + width: 120, + }, + { + title: t('Details'), + dataIndex: 'updatedAt', + render: renderColumnDetail, + width: 200, + className: styles.columnDetail, + }, + ]; +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.detail.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.detail.tsx new file mode 100644 index 000000000..754bf5468 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.detail.tsx @@ -0,0 +1,51 @@ +import { DatePicker, InputReadPretty, RecordProvider, useCurrentUserContext } from '@nocobase/client'; +import React, { useContext } from 'react'; +import { ContextWithActionEnabled } from '../Pd.WithActionEnabled'; +import { ViewActionLaunch } from '../../approval-block/launch/VC.ViewActionLaunch'; +import { ViewActionTodos } from '../../approval-block/todos/VC.ViewActionTodos'; + +// Component开头的是,不知道起什么名字合适的 +export function renderColumnDetail(e, props, exist) { + return exist ? : ; +} + +// Exist +const ColumnDetailExist = (props) => { + const { data } = useCurrentUserContext(); + const { actionEnabled } = useContext(ContextWithActionEnabled); + return ( + <> + {props.status ? ( + <> + {[ + props.comment ? : null, + , + ]} + + ) : null} + {actionEnabled && props.userId === data?.data.id ? ( + // @ts-ignore + + + + ) : null} + + ); +}; + +// Not Exist +const ColumnDetailNotExist = (props) => { + const { data } = useCurrentUserContext(); + const { actionEnabled } = useContext(ContextWithActionEnabled); + return ( + <> + + {actionEnabled && props.user.id === data?.data.id ? ( + // @ts-ignore + + + + ) : null} + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.status.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.status.tsx new file mode 100644 index 000000000..cec7305a7 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.status.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { useCompile } from '@nocobase/client'; +import { Tag } from 'antd'; +import { approvalStatusConfigObj } from '../../../constants'; +import { ColumnStatusComponent } from '../approval-columns/column.status'; +import { ApprovalStatusEnumDict } from '../../../constants'; +import { APPROVAL_STATUS } from '../../../constants'; + +export function renderColumnStatus(value, record, exist) { + // return null; + const compile = useCompile(); + if (!exist) { + const approvalStatusItem = + ApprovalStatusEnumDict[ + record.status === APPROVAL_STATUS.DRAFT ? APPROVAL_STATUS.DRAFT : APPROVAL_STATUS.SUBMITTED + ]; + return {compile(approvalStatusItem.label)}; + } + + const option = approvalStatusConfigObj[value]; + return ; +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.taskNode.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.taskNode.tsx new file mode 100644 index 000000000..9f9374050 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-common/process-columns/render.taskNode.tsx @@ -0,0 +1,58 @@ +import { css, useCompile } from '@nocobase/client'; +import { Progress, Tag } from 'antd'; +import React from 'react'; +import { + APPROVAL_ACTION_STATUS, + JobStatusEnums, + VoteCategoryEnums, + approvalStatusConfigObj, + voteOption, +} from '../../../constants'; + +// 审批处理: 任务节点值 +export function renderColumnTaskNode(text, { node, job, groupCount, statusCount }, group) { + const compile = useCompile(); + const { branchMode, negotiation } = node?.config ? node : ({} as any); + const enums = VoteCategoryEnums[voteOption(negotiation)]; + const tag = branchMode ? approvalStatusConfigObj[job.result] : JobStatusEnums[job?.status]; + // return null; + return ( + <> + {text} + {group && groupCount > 1 ? ( +
+ + {typeof enums.label == 'function' ? enums.label(negotiation) : compile(enums.label)} + + {statusCount ? ( + + ) : null} +
+ ) : null} + + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Dt.RecordApprovals.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Dt.RecordApprovals.tsx new file mode 100644 index 000000000..f697a5b4c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Dt.RecordApprovals.tsx @@ -0,0 +1,56 @@ +import { + CollectionProvider_deprecated, + ExtendCollectionsProvider, + List, + joinCollectionName, + useCollection_deprecated, + useRecord, +} from '@nocobase/client'; +import React, { useMemo } from 'react'; +import { CollectionApprovals } from '../approval-common/Cn.Approvals'; +import { CollectionApprovalTodos } from '../../common/Cn.ApprovalTodos'; +import { CollectionFlowNodes } from '../approval-common/Cn.FlowNodes'; +import { CollectionWorkflows } from '../approval-common/Cn.Workflows'; + +export function RecordApprovalsDecorator({ params, children }) { + const collection = useCollection_deprecated(); + const record = useRecord(); + const request = useMemo( + () => ({ + collection: 'approvals', + resource: 'approvals', + action: 'list', + params: { + sort: ['-createdAt'], + ...params, + filter: { + ...params?.filter, + dataKey: `${record[collection.filterTargetKey]}`, + collectionName: joinCollectionName(collection.dataSource, collection.name), + }, + appends: [ + 'workflow', + 'approvalExecutions', + 'createdBy.nickname', + 'records', + 'records.node.title', + 'records.node.config', + 'records.job', + 'records.user.nickname', + ], + except: ['data', 'approvalExecutions.snapshot', 'records.snapshot'], + }, + dragSort: false, + }), + [params, record, collection], + ); + return ( + + + {children} + + + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Iz.RecordApprovals.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Iz.RecordApprovals.tsx new file mode 100644 index 000000000..5ba9a2041 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Iz.RecordApprovals.tsx @@ -0,0 +1,34 @@ +import { AuditOutlined } from '@ant-design/icons'; +import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '@nocobase/client'; +import { uid } from '@nocobase/utils/client'; +import React from 'react'; +import { ApprovalRecordBlock } from './map'; + +// 初始化器,创建区块-相关审批 +export function RecordApprovalsInitializer() { + const schemaInitializerItem = useSchemaInitializerItem(); + const { insert } = useSchemaInitializer(); + return ( + } + {...schemaInitializerItem} + onClick={() => { + const id = uid(); + insert({ + type: 'void', + name: id, + 'x-uid': id, + 'x-decorator': ApprovalRecordBlock.PathNameMap.Decorator, + 'x-component': 'CardItem', + 'x-designer': 'List.Designer', + properties: { + block: { + type: 'void', + 'x-component': ApprovalRecordBlock.PathNameMap.ViewComponent, + }, + }, + }); + }} + /> + ); +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Sm.RecordApprovals.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Sm.RecordApprovals.ts new file mode 100644 index 000000000..c9e70d4c6 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/Sm.RecordApprovals.ts @@ -0,0 +1,103 @@ +import { NAMESPACE } from '../../locale'; +import { ApprovalCommon } from '../approval-common/map'; + +export const SchemaRecordApprovals = { + type: 'void', + name: 'recordApprovals', + properties: { + actions: { + type: 'void', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 16, + }, + }, + properties: { + refresher: { + type: 'void', + title: '{{ t("Refresh") }}', + 'x-action': 'refresh', + 'x-component': 'Action', + 'x-use-component-props': 'useRefreshActionProps', + 'x-designer': 'Action.Designer', + 'x-component-props': { icon: 'ReloadOutlined' }, + 'x-align': 'right', + }, + }, + }, + list: { + type: 'array', + 'x-component': 'List', + 'x-component-props': { locale: { emptyText: `{{ t("No data yet", { ns: "${NAMESPACE}" }) }}` } }, + properties: { + item: { + type: 'object', + 'x-component': 'List.Item', + 'x-use-component-props': 'useListItemProps', + 'x-read-pretty': true, + properties: { + grid: { + type: 'void', + 'x-component': 'Space', + 'x-component-props': { direction: 'vertical' }, + properties: { + row: { + type: 'void', + 'x-component': 'Space', + 'x-component-props': { size: 'large', wrap: true }, + properties: { + id: { + type: 'number', + title: '{{t("ID")}}', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 'x-read-pretty': true, + }, + workflow: { + type: 'string', + title: '{{t("Workflow", { ns: "workflow" })}}', + 'x-decorator': 'FormItem', + 'x-component': 'WorkflowColumn', + 'x-read-pretty': true, + }, + status: { + type: 'number', + title: `{{t("Current status", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + createdBy: { + type: 'string', + title: `{{t("Initiator", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'UserColumn', + 'x-read-pretty': true, + }, + createdAt: { + type: 'string', + title: '{{t("Created at")}}', + 'x-decorator': 'FormItem', + 'x-component': 'CollectionField', + 'x-read-pretty': true, + }, + }, + }, + processRow: { + type: 'void', + // 'x-decorator': 'ApprovalBlock.ApprovalDataProvider', + 'x-decorator': ApprovalCommon.PathNameMap.Provider.ApprovalDataProvider, + 'x-component': ApprovalCommon.PathNameMap.ViewComponent.ApprovalProcess, + 'x-component-props': { + actionEnabled: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/VC.RecordApprovals.tsx b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/VC.RecordApprovals.tsx new file mode 100644 index 000000000..41c5f83a6 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/VC.RecordApprovals.tsx @@ -0,0 +1,20 @@ +import { SchemaComponent } from '@nocobase/client'; +import React from 'react'; +import { ApprovalRecordStatusColumn } from '../approval-common/approval-columns/column.status'; +import { UserColumn } from '../approval-common/approval-columns/column.user'; +import { WorkflowColumn } from '../approval-common/approval-columns/column.workflow'; +import { SchemaRecordApprovals } from './Sm.RecordApprovals'; + +// 视图组件,创建区块-相关审批 +export const RecordApprovals = () => { + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/index.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/index.ts new file mode 100644 index 000000000..db370d829 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/index.ts @@ -0,0 +1,27 @@ +import { Plugin } from '@nocobase/client'; +import { tval } from '../../locale'; +import PluginKitApprovalCommon from '../approval-common'; +import { RecordApprovalsDecorator } from './Dt.RecordApprovals'; +import { RecordApprovalsInitializer } from './Iz.RecordApprovals'; +import { RecordApprovals } from './VC.RecordApprovals'; + +export default class PluginKitApprovalRecordBlock extends Plugin { + async afterAdd() { + this.pm.add(PluginKitApprovalCommon); + } + async beforeLoad() {} + async load() { + this.app.addComponents({ + 'ApprovalRecordBlock.Decorator': RecordApprovalsDecorator, + 'ApprovalRecordBlock.BlockInitializer': RecordApprovalsInitializer, + 'ApprovalRecordBlock.ViewComponent': RecordApprovals, + }); + + this.app.schemaInitializerManager.get('popup:common:addBlock').add('otherBlocks.approvalRecordBlock', { + type: 'item', + name: 'approvalRecordBlock', + title: tval('Related approvals'), + Component: 'ApprovalRecordBlock.BlockInitializer', + }); + } +} diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/map.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/map.ts new file mode 100644 index 000000000..7b2bfdf1c --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/approval-record-block/map.ts @@ -0,0 +1,44 @@ +import { RecordApprovalsDecorator } from './Dt.RecordApprovals'; +import { RecordApprovalsInitializer } from './Iz.RecordApprovals'; +import { RecordApprovals } from './VC.RecordApprovals'; + +// 区块-相关审批 +export const ApprovalRecordBlock = () => null; + +// Think: 这里其实可以从框架层面定义, 现在是取名字, +// 可以改为, name和component的对象.让开发体验更友好. +// 目前只是适应框架现在的定义,这样处理 +// 名称映射,声明而不是生成,提供ts较好的路径提示信息,便于修改,以及避免遍历计算带来的性能损耗 +ApprovalRecordBlock.PathNameMap = { + Decorator: 'ApprovalRecordBlock.Decorator', + BlockInitializer: 'ApprovalRecordBlock.BlockInitializer', + ViewComponent: 'ApprovalRecordBlock.ViewComponent', +}; + +ApprovalRecordBlock.Decorator = RecordApprovalsDecorator; + +ApprovalRecordBlock.BlockInitializer = RecordApprovalsInitializer; + +ApprovalRecordBlock.ViewComponent = RecordApprovals; + +// ApprovalRecordBlock.PathNameMap = getPathNameMap(ApprovalRecordBlock); +// 改为如下形式, 需要改addComponent的机制, 以及加载组件的机制 +// 以下代码为 demo +// export const ApprovalRecordBlock1 = { +// Decorator: { +// name: 'ApprovalRecordBlock.Decorator', +// component: RecordApprovalsDecorator, +// }, +// Provider: { +// name: '', +// component: null, +// }, +// BlockInitializer: { +// name: 'ApprovalRecordBlock.BlockInitializer', +// component: RecordApprovalsInitializer, +// }, +// ViewComponent: { +// name: 'ApprovalRecordBlock.ViewComponent', +// component: RecordApprovals, +// }, +// }; diff --git a/packages/plugins/@hera/plugin-approval/src/client/usage/index.ts b/packages/plugins/@hera/plugin-approval/src/client/usage/index.ts new file mode 100644 index 000000000..cd6feb854 --- /dev/null +++ b/packages/plugins/@hera/plugin-approval/src/client/usage/index.ts @@ -0,0 +1,12 @@ +import { Plugin } from '@nocobase/client'; +import PluginKitApprovalRecordBlock from './approval-record-block'; +import PluginKitApprovalBlock from './approval-block'; + +export default class PluginKitApprovalUsage extends Plugin { + async afterAdd() { + this.pm.add(PluginKitApprovalRecordBlock); + this.pm.add(PluginKitApprovalBlock); + } + async beforeLoad() {} + async load() {} +} diff --git a/packages/plugins/@hera/plugin-approval/src/locale/en-US.json b/packages/plugins/@hera/plugin-approval/src/locale/en-US.json index dae92b20b..fda380321 100644 --- a/packages/plugins/@hera/plugin-approval/src/locale/en-US.json +++ b/packages/plugins/@hera/plugin-approval/src/locale/en-US.json @@ -2,7 +2,7 @@ "Approval": "Approval", "Approvals": "Approvals", "Approval blocks": "Approval blocks", - "Initiations": "Initiations", + "Launch": "Launch", "Todos": "Todos", "Approval applications": "Approval applications", "Approval todos": "Approval todos", diff --git a/packages/plugins/@hera/plugin-approval/src/locale/index.ts b/packages/plugins/@hera/plugin-approval/src/locale/index.ts deleted file mode 100644 index 01c25ff7e..000000000 --- a/packages/plugins/@hera/plugin-approval/src/locale/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { i18n} from '@nocobase/client'; -import { useTranslation } from 'react-i18next'; - -export const NAMESPACE = "@hera/plugin-approval"; -export function lang(key: string, options = {}) { - return i18n.t(key, { ...options, ns: NAMESPACE }); -} -export function usePluginTranslation() { - return useTranslation(NAMESPACE); -} diff --git a/packages/plugins/@hera/plugin-approval/src/locale/zh-CN.json b/packages/plugins/@hera/plugin-approval/src/locale/zh-CN.json index 18f6ca051..51e2b1b0a 100644 --- a/packages/plugins/@hera/plugin-approval/src/locale/zh-CN.json +++ b/packages/plugins/@hera/plugin-approval/src/locale/zh-CN.json @@ -3,7 +3,7 @@ "Approval": "审批", "Approvals": "审批", "Approval blocks": "审批区块", - "Initiations": "发起", + "Launch": "发起", "Todos": "待办", "Approval applications": "审批申请", "Approval todos": "审批待办",