fix: bulk update keys should obtained when clicked (#1040)

Reviewed-on: daoyoucloud/tachybase#1040
This commit is contained in:
sealday 2024-05-23 16:59:41 +08:00
parent 057e8d4aa8
commit b1dfce33cd
22 changed files with 335 additions and 388 deletions

View File

@ -1,15 +1,23 @@
import { Field, Form } from '@tachybase/schema';
import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@tachybase/schema';
import { untracked } from '@tachybase/schema';
import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react';
import {
Field,
Form,
SchemaExpressionScopeContext,
untracked,
useField,
useFieldSchema,
useForm,
} from '@tachybase/schema';
import { isURL, parse } from '@tachybase/utils/client';
import { App, message } from 'antd';
import _ from 'lodash';
import get from 'lodash/get';
import omit from 'lodash/omit';
import { ChangeEvent, useCallback, useContext, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useReactToPrint } from 'react-to-print';
import {
AssociationFilter,
useCollectionRecord,
@ -20,7 +28,7 @@ import {
useTableBlockContext,
} from '../..';
import { useAPIClient, useRequest } from '../../api-client';
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../collection-manager';
import { useCollection_deprecated, useCollectionManager_deprecated } from '../../collection-manager';
import { useFilterBlock } from '../../filter-provider/FilterProvider';
import { mergeFilter, transformToFilter } from '../../filter-provider/utils';
import { useRecord } from '../../record-provider';
@ -592,123 +600,6 @@ export const useCustomizeUpdateActionProps = () => {
};
};
export const useCustomizeBulkUpdateActionProps = () => {
const { field, resource, __parent, service } = useBlockRequestContext();
const expressionScope = useContext(SchemaExpressionScopeContext);
const actionSchema = useFieldSchema();
const tableBlockContext = useTableBlockContext();
const { rowKey } = tableBlockContext;
const selectedRecordKeys =
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
const navigate = useNavigate();
const compile = useCompile();
const { t } = useTranslation();
const actionField = useField();
const { modal } = App.useApp();
const variables = useVariables();
const record = useRecord();
const { name, getField } = useCollection_deprecated();
const localVariables = useLocalVariables();
return {
async onClick() {
const {
assignedValues: originalAssignedValues = {},
onSuccess,
updateMode,
} = actionSchema?.['x-action-settings'] ?? {};
actionField.data = field.data || {};
actionField.data.loading = true;
const assignedValues = {};
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
const value = originalAssignedValues[key];
const collectionField = getField(key);
if (process.env.NODE_ENV !== 'production') {
if (!collectionField) {
throw new Error(`useCustomizeBulkUpdateActionProps: field "${key}" not found in collection "${name}"`);
}
}
if (isVariable(value)) {
const result = await variables?.parseVariable(value, localVariables);
if (result) {
assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
}
} else if (value != null && value !== '') {
assignedValues[key] = value;
}
});
await Promise.all(waitList);
modal.confirm({
title: t('Bulk update'),
content: updateMode === 'selected' ? t('Update selected data?') : t('Update all data?'),
async onOk() {
const { filter } = service.params?.[0] ?? {};
const updateData: { filter?: any; values: any; forceUpdate: boolean } = {
values: { ...assignedValues },
filter,
forceUpdate: false,
};
if (updateMode === 'selected') {
if (!selectedRecordKeys?.length) {
message.error(t('Please select the records to be updated'));
actionField.data.loading = false;
return;
}
updateData.filter = { $and: [{ [rowKey || 'id']: { $in: selectedRecordKeys } }] };
}
if (!updateData.filter) {
updateData.forceUpdate = true;
}
try {
await resource.update(updateData);
} catch (error) {
/* empty */
} finally {
actionField.data.loading = false;
}
service?.refresh?.();
if (!(resource instanceof TableFieldResource)) {
__parent?.service?.refresh?.();
}
if (!onSuccess?.successMessage) {
return;
}
if (onSuccess?.manualClose) {
modal.success({
title: compile(onSuccess?.successMessage),
onOk: async () => {
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
if (isURL(onSuccess.redirectTo)) {
window.location.href = onSuccess.redirectTo;
} else {
navigate(onSuccess.redirectTo);
}
}
},
});
} else {
message.success(compile(onSuccess?.successMessage));
if (onSuccess?.redirecting && onSuccess?.redirectTo) {
if (isURL(onSuccess.redirectTo)) {
window.location.href = onSuccess.redirectTo;
} else {
navigate(onSuccess.redirectTo);
}
}
}
},
async onCancel() {
actionField.data.loading = false;
},
});
},
};
};
export const useCustomizeRequestActionProps = () => {
const apiClient = useAPIClient();
const navigate = useNavigate();

View File

@ -1,6 +1,7 @@
import React, { createContext, ReactNode, useCallback, useContext, useRef } from 'react';
import { MenuProps } from 'antd';
import _ from 'lodash';
import React, { createContext, ReactNode, useCallback, useContext, useRef } from 'react';
type Item = MenuProps['items'][0] & {
/** 在清空数组时,如果该字段为 true 则保留该选项 */
@ -35,7 +36,7 @@ export const useMenuItem = () => {
const renderItems = useRef<() => JSX.Element>(null);
const shouldRerender = useRef(false);
const Component = useCallback(({ limitCount }) => {
const Component = useCallback(({ limitCount }: { limitCount?: number }) => {
if (!shouldRerender.current) {
return null;
}

View File

@ -1,25 +1,25 @@
import { css } from '@emotion/css';
import React from 'react';
import { useToken } from '../style';
import { createStyles } from '../style';
const useStyles = createStyles(({ css, token }) => {
return {
powerBy: css`
text-align: center;
color: ${token.colorTextDescription};
a {
color: ${token.colorTextDescription};
&:hover {
color: ${token.colorText};
}
}
`,
};
});
export const PoweredByV2 = () => {
const { token } = useToken();
const { styles } = useStyles();
const date = new Date();
const year = date.getFullYear();
return (
<div
className={css`
text-align: center;
color: ${token.colorTextDescription};
a {
color: ${token.colorTextDescription};
&:hover {
color: ${token.colorText};
}
}
`}
>
©2023-{year} TachyBase Team
</div>
);
return <div className={styles.powerBy}>©2023-{year} TachyBase Team</div>;
};

View File

@ -1,4 +1,5 @@
import React, { createContext, useContext } from 'react';
import { useCollection_deprecated } from '../collection-manager';
import { CollectionRecordProvider } from '../data-source';
import { useCurrentUserContext } from '../user';
@ -11,12 +12,13 @@ RecordIndexContext.displayName = 'RecordIndexContext';
/**
* @deprecated use `CollectionRecordProvider` instead
*/
export const RecordProvider: React.FC<{
export const RecordProvider = (props: {
record: any;
parent?: any;
isNew?: boolean;
collectionName?: string;
}> = (props) => {
children: React.ReactNode;
}) => {
const { record, children, parent, isNew } = props;
const { name: __collectionName } = useCollection_deprecated();
const value = { ...record };
@ -31,11 +33,11 @@ export const RecordProvider: React.FC<{
);
};
export const RecordSimpleProvider: React.FC<{ value: Record<string, any>; children: React.ReactNode }> = (props) => {
export const RecordSimpleProvider = (props: { value: Record<string, any>; children: React.ReactNode }) => {
return <RecordContext_deprecated.Provider {...props} />;
};
export const RecordIndexProvider: React.FC<{ index: any }> = (props) => {
export const RecordIndexProvider = (props: { index: any; children: React.ReactNode }) => {
const { index, children } = props;
return <RecordIndexContext.Provider value={index}>{children}</RecordIndexContext.Provider>;
};

View File

@ -1,20 +1,25 @@
import { css } from '@emotion/css';
import dayjs from 'dayjs';
import React, { useState } from 'react';
import { connect, mapProps } from '@tachybase/schema';
import { useBoolean } from 'ahooks';
import { Input, Radio, Space } from 'antd';
import React, { useState } from 'react';
import { createStyles } from 'antd-style';
import dayjs from 'dayjs';
import { useToken } from '../../';
const date = dayjs();
const spaceCSS = css`
width: 100%;
& > .ant-space-item {
flex: 1;
}
`;
const useStyles = createStyles(({ css }) => {
return {
space: css`
width: 100%;
& > .ant-space-item {
flex: 1;
}
`,
};
});
export const DateFormatCom = (props?) => {
const date = dayjs();
return (
@ -44,6 +49,7 @@ const DateTimeFormatPreview = ({ content }) => {
};
const InternalExpiresRadio = (props) => {
const { styles } = useStyles();
const { onChange, defaultValue, formats, timeFormat } = props;
const [isCustom, { setFalse, setTrue }] = useBoolean(props.value && !formats.includes(props.value));
const targetValue = props.value && !formats.includes(props.value) ? props.value : defaultValue;
@ -58,7 +64,7 @@ const InternalExpiresRadio = (props) => {
}
};
return (
<Space className={spaceCSS}>
<Space className={styles.space}>
<Radio.Group value={isCustom ? 'custom' : props.value} onChange={onSelectChange}>
<Space direction="vertical">
{props.options.map((v) => {

View File

@ -1,13 +1,28 @@
import { css } from '@emotion/css';
import { observer, useFieldSchema } from '@tachybase/schema';
import React, { useEffect, useMemo } from 'react';
import { observer, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { useCompile } from '../../schema-component';
import { Variable } from '.././../schema-component';
import { useCurrentFormVariable } from '../VariableInput/hooks/useFormVariable';
import { useCurrentObjectVariable } from '../VariableInput/hooks/useIterationVariable';
const useStyles = createStyles(({ css }) => {
return {
variableInput: css`
min-width: 400px;
margin-right: 15;
.ant-input {
width: 100% !important;
}
`,
};
});
export const ChildDynamicComponent = observer(
(props: { rootCollection: string; onChange; value; default; collectionField }) => {
const { styles } = useStyles();
const { rootCollection, onChange, value, collectionField } = props;
const fieldSchema = useFieldSchema();
const { currentFormSettings } = useCurrentFormVariable({
@ -28,19 +43,7 @@ export const ChildDynamicComponent = observer(
useEffect(() => {
onChange(fieldSchema.default);
}, []);
return (
<Variable.Input
value={value}
onChange={(v) => onChange(v)}
scope={scope}
style={{ minWidth: '400px', marginRight: 15 }}
className={css`
.ant-input {
width: 100% !important;
}
`}
/>
);
return <Variable.Input value={value} onChange={onChange} scope={scope} className={styles.variableInput} />;
},
{ displayName: 'ChildDynamicComponent' },
);

View File

@ -1,21 +1,50 @@
import { CloseCircleOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { TreeSelect } from '@tachybase/components';
import { observer } from '@tachybase/schema';
import { uid } from '@tachybase/schema';
import { Select, Space } from 'antd';
import React, { useCallback, useContext, useMemo, useState } from 'react';
import { TreeSelect } from '@tachybase/components';
import { observer, uid } from '@tachybase/schema';
import { CloseCircleOutlined } from '@ant-design/icons';
import { Select, Space } from 'antd';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../..';
import { DynamicComponent } from './DynamicComponent';
import { ValueDynamicComponent } from './ValueDynamicComponent';
import { LinkageLogicContext, RemoveActionContext } from './context';
import { DynamicComponent } from './DynamicComponent';
import { ActionType } from './type';
import { useValues } from './useValues';
import { ValueDynamicComponent } from './ValueDynamicComponent';
const useStyles = createStyles(({ css }) => {
return {
container: css`
.ant-space {
display: inline-block;
}
`,
item: css`
.ant-space {
display: inline-block;
}
.ant-space-item {
max-width: 95%;
display: inline-block;
margin: 2px;
vertical-align: top;
}
`,
select: css`
min-width: 120px;
`,
tree: css`
min-width: 160px;
`,
};
});
export const FormFieldLinkageRuleAction = observer(
(props: any) => {
const { value, options, collectionName } = props;
const { styles } = useStyles();
const { t } = useTranslation();
const compile = useCompile();
const remove = useContext(RemoveActionContext);
@ -31,34 +60,13 @@ export const FormFieldLinkageRuleAction = observer(
} = useValues(options);
return (
<LinkageLogicContext.Provider value={uid()}>
<div
style={{ marginBottom: 8 }}
className={css`
.ant-space {
display: inline-block;
}
`}
>
<Space
className={css`
.ant-space {
display: inline-block;
}
.ant-space-item {
max-width: 95%;
display: inline-block;
margin: 2px;
vertical-align: top;
}
`}
>
<div style={{ marginBottom: 8 }} className={styles.container}>
<Space className={styles.item}>
<TreeSelect
// @ts-ignore
role="button"
data-testid="select-linkage-property-field"
className={css`
min-width: 160px;
`}
className={styles.tree}
fieldNames={{
label: 'title',
value: 'name',
@ -80,9 +88,7 @@ export const FormFieldLinkageRuleAction = observer(
data-testid="select-linkage-action-field"
popupMatchSelectWidth={false}
value={operator}
className={css`
min-width: 120px;
`}
className={styles.select}
options={compile(operators)}
onChange={(value) => {
setOperator(value);

View File

@ -1,41 +1,38 @@
import { css } from '@emotion/css';
import { Form } from '@tachybase/schema';
import { observer, useFieldSchema } from '@tachybase/schema';
import React, { useMemo } from 'react';
import { observer, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { withDynamicSchemaProps } from '../../application/hoc/withDynamicSchemaProps';
import { FormBlockContext } from '../../block-provider';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { useCollectionParentRecordData } from '../../data-source/collection-record/CollectionRecordProvider';
import { RecordProvider } from '../../record-provider';
import { SchemaComponent, useProps } from '../../schema-component';
import { DynamicComponentProps } from '../../schema-component/antd/filter/DynamicComponent';
import { FilterContext } from '../../schema-component/antd/filter/context';
import { VariableOption, VariablesContextType } from '../../variables/types';
import { VariableInput, getShouldChange } from '../VariableInput/VariableInput';
import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
import { DynamicComponentProps } from '../../schema-component/antd/filter/DynamicComponent';
import { getShouldChange, VariableInput } from '../VariableInput/VariableInput';
import { EnableLinkage } from './components/EnableLinkage';
import { ArrayCollapse } from './components/LinkageHeader';
import { withDynamicSchemaProps } from '../../application/hoc/withDynamicSchemaProps';
interface usePropsReturn {
options: any;
defaultValues: any[];
collectionName: string;
form: Form;
variables: VariablesContextType;
localVariables: VariableOption | VariableOption[];
record: Record<string, any>;
/**
* create update
*/
formBlockType: 'create' | 'update';
}
import { LinkageRuleActionGroup } from './LinkageRuleActionGroup';
interface Props {
dynamicComponent: any;
}
const useStyles = createStyles(({ css }) => {
return {
filter: css`
position: relative;
width: 100%;
margin-left: 10px;
`,
};
});
export const FormLinkageRules = withDynamicSchemaProps(
observer((props: Props) => {
const { styles } = useStyles();
const fieldSchema = useFieldSchema();
const {
options,
@ -91,11 +88,7 @@ export const FormLinkageRules = withDynamicSchemaProps(
'x-use-component-props': () => {
return {
options,
className: css`
position: relative;
width: 100%;
margin-left: 10px;
`,
className: styles.filter,
};
},
'x-component-props': {

View File

@ -1,10 +1,30 @@
import { css } from '@emotion/css';
import React, {
createContext,
FC,
ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
// @ts-ignore
useTransition as useReactTransition,
useState,
} from 'react';
import { ArrayCollapse, ArrayItems, FormItem, FormLayout, Input } from '@tachybase/components';
import { Field, GeneralField, createForm } from '@tachybase/schema';
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@tachybase/schema';
import { uid } from '@tachybase/schema';
import {
createForm,
Field,
GeneralField,
ISchema,
Schema,
SchemaOptionsContext,
uid,
useField,
useFieldSchema,
useForm,
} from '@tachybase/schema';
import { error } from '@tachybase/utils/client';
import type { DropdownProps } from 'antd';
import {
Alert,
App,
@ -20,44 +40,37 @@ import {
ModalFuncProps,
Space,
Switch,
type DropdownProps,
} from 'antd';
import { createStyles } from 'antd-style';
import _, { cloneDeep, get, set } from 'lodash';
import React, {
FC,
ReactNode,
createContext,
useCallback,
useContext,
useEffect,
useMemo,
// @ts-ignore
useTransition as useReactTransition,
useState,
} from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { Router } from 'react-router-dom';
import {
APIClientProvider,
ActionContextProvider,
APIClientProvider,
ApplicationContext,
AssociationOrCollectionProvider,
CollectionFieldOptions_deprecated,
CollectionRecordProvider,
createDesignable,
DataSourceApplicationProvider,
Designable,
findFormBlock,
FormDialog,
FormProvider,
RemoteSchemaComponent,
SchemaComponent,
SchemaComponentContext,
SchemaComponentOptions,
createDesignable,
findFormBlock,
useAPIClient,
useApp,
useBlockRequestContext,
useCollection_deprecated,
useCollectionManager_deprecated,
useCollectionRecord,
useCollection_deprecated,
useCompile,
useDataBlockProps,
useDesignable,
@ -67,8 +80,6 @@ import {
useRecord,
useSchemaSettingsItem,
useSortFields,
useApp,
ApplicationContext,
} from '..';
import {
BlockRequestContext_deprecated,
@ -78,8 +89,8 @@ import {
useTableBlockContext,
} from '../block-provider';
import {
FormActiveFieldsProvider,
findFilterTargets,
FormActiveFieldsProvider,
updateFilterTargets,
useFormActiveFields,
} from '../block-provider/hooks';
@ -142,8 +153,31 @@ interface SchemaSettingsProviderProps {
template?: any;
collectionName?: any;
designer?: any;
children: React.ReactNode;
}
const useStyles = createStyles(({ css }) => {
return {
menu: css`
.ant-dropdown-menu-item-group-list {
max-height: 300px;
overflow-y: auto;
}
`,
modal: css`
// screen > 576px
@media (min-width: 576px) {
min-width: 520px;
}
// screen <= 576px
@media (max-width: 576px) {
min-width: 320px;
}
`,
};
});
export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => {
const { children, fieldSchema, ...others } = props;
const { getTemplateBySchema } = useSchemaTemplateManager();
@ -158,6 +192,7 @@ export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (pr
export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = (props) => {
const { title, dn, ...others } = props;
const { styles } = useStyles();
const [visible, setVisible] = useState(false);
const { Component, getMenuItems } = useMenuItem();
const [, startTransition] = useReactTransition();
@ -180,12 +215,7 @@ export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = (props) =>
<Dropdown
open={visible}
onOpenChange={changeMenu}
overlayClassName={css`
.ant-dropdown-menu-item-group-list {
max-height: 300px;
overflow-y: auto;
}
`}
overlayClassName={styles.menu}
menu={{ items, style: { maxHeight: dropdownMaxHeight, overflowY: 'auto' } }}
>
<div data-testid={props['data-testid']}>{typeof title === 'string' ? <span>{title}</span> : title}</div>
@ -978,6 +1008,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
...others
} = props;
const options = useContext(SchemaOptionsContext);
const { styles } = useStyles();
const collection = useCollection_deprecated();
const apiClient = useAPIClient();
const { theme } = useGlobalTheme();
@ -1025,20 +1056,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
association={association}
>
<SchemaComponentOptions scope={options.scope} components={options.components}>
<FormLayout
layout={'vertical'}
className={css`
// screen > 576px
@media (min-width: 576px) {
min-width: 520px;
}
// screen <= 576px
@media (max-width: 576px) {
min-width: 320px;
}
`}
>
<FormLayout layout={'vertical'} className={styles.modal}>
<APIClientProvider apiClient={apiClient}>
<ConfigProvider locale={locale}>
<SchemaComponent components={components} scope={scope} schema={schema} />
@ -1474,12 +1492,6 @@ export function SchemaSettingsEnableChildCollections(props) {
);
}
export const defaultInputStyle = css`
& > .nb-form-item {
flex: 1;
}
`;
export const findParentFieldSchema = (fieldSchema: Schema) => {
let parent = fieldSchema.parent;
while (parent) {

View File

@ -1,12 +1,36 @@
import { css } from '@emotion/css';
import { ISchema, Schema, useField } from '@tachybase/schema';
import React from 'react';
import { ISchema, Schema, useField } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import { useCollectionManager_deprecated, useDesignable } from '..';
import { DateFormatCom, ExpiresRadio } from './DateFormat/ExpiresRadio';
import { SchemaSettingsModalItem } from './SchemaSettings';
const useStyles = createStyles(({ css }) => {
return {
radio: css`
.ant-radio-wrapper {
display: flex;
margin: 5px 0px;
}
`,
margin: css`
margin-bottom: 0px;
`,
redRadio: css`
color: red;
.ant-radio-wrapper {
display: flex;
margin: 5px 0px;
}
`,
};
});
export const SchemaSettingsDateFormat = function DateFormatConfig(props: { fieldSchema: Schema }) {
const { styles } = useStyles();
const { fieldSchema } = props;
const field = useField();
const { dn } = useDesignable();
@ -36,12 +60,7 @@ export const SchemaSettingsDateFormat = function DateFormatConfig(props: { field
'x-decorator': 'FormItem',
'x-decorator-props': {},
'x-component-props': {
className: css`
.ant-radio-wrapper {
display: flex;
margin: 5px 0px;
}
`,
className: styles.radio,
defaultValue: 'dddd',
formats: ['MMMM Do YYYY', 'YYYY-MM-DD', 'MM/DD/YY', 'YYYY/MM/DD', 'DD/MM/YYYY'],
},
@ -94,18 +113,10 @@ export const SchemaSettingsDateFormat = function DateFormatConfig(props: { field
'x-component': ExpiresRadio,
'x-decorator': 'FormItem',
'x-decorator-props': {
className: css`
margin-bottom: 0px;
`,
className: styles.margin,
},
'x-component-props': {
className: css`
color: red;
.ant-radio-wrapper {
display: flex;
margin: 5px 0px;
}
`,
className: styles.redRadio,
defaultValue: 'h:mm a',
formats: ['hh:mm:ss a', 'HH:mm:ss'],
timeFormat: true,

View File

@ -1,14 +1,17 @@
import { Field } from '@tachybase/schema';
import { ISchema, Schema, useField, useFieldSchema } from '@tachybase/schema';
import _ from 'lodash';
import React, { useCallback, useMemo } from 'react';
import { ArrayCollapse, FormLayout } from '@tachybase/components';
import { Field, ISchema, Schema, useField, useFieldSchema } from '@tachybase/schema';
import { createStyles } from 'antd-style';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import {
FormProvider,
SchemaComponent,
useActionContext,
useCollectionManager_deprecated,
useCollection_deprecated,
useCollectionManager_deprecated,
useDesignable,
useRecord,
} from '..';
@ -17,18 +20,23 @@ import { useCollectionFilterOptionsV2 } from '../collection-manager/action-hooks
import { FlagProvider, useFlag } from '../flag-provider';
import { useLocalVariables, useVariables } from '../variables';
import { isVariable } from '../variables/utils/isVariable';
import { VariableInput, getShouldChange } from './VariableInput/VariableInput';
import { findParentFieldSchema, getFieldDefaultValue, SchemaSettingsModalItem } from './SchemaSettings';
import { Option } from './VariableInput/type';
import { formatVariableScop } from './VariableInput/utils/formatVariableScop';
import {
findParentFieldSchema,
defaultInputStyle,
getFieldDefaultValue,
SchemaSettingsModalItem,
} from './SchemaSettings';
import { ArrayCollapse, FormLayout } from '@tachybase/components';
import { getShouldChange, VariableInput } from './VariableInput/VariableInput';
const useStyles = createStyles(({ css }) => {
return {
defaultInput: css`
& > .nb-form-item {
flex: 1;
}
`,
};
});
export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props: { fieldSchema?: Schema }) {
const { styles } = useStyles();
const currentSchema = useFieldSchema();
const fieldSchema = props?.fieldSchema ?? currentSchema;
const field: Field = useField();
@ -112,7 +120,7 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
contextCollectionName: isAllowContextVariable && tableCtx.collection,
schema: collectionField?.uiSchema,
targetFieldSchema: fieldSchema,
className: defaultInputStyle,
className: styles.defaultInput,
form,
record,
returnScope,
@ -191,6 +199,7 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
tableCtx.collection,
targetField,
variables,
styles,
]);
const handleSubmit: (values: any) => void = useCallback(

View File

@ -1,8 +1,9 @@
import { css } from '@emotion/css';
import { ISchema, Schema, useField, useForm } from '@tachybase/schema';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ISchema, Schema, useField, useForm } from '@tachybase/schema';
import { Select } from 'antd';
import { useTranslation } from 'react-i18next';
import { useCollectionManager_deprecated, useDesignable } from '..';
import { SchemaSettingsModalItem } from './SchemaSettings';

View File

@ -1,4 +1,5 @@
import { theme } from 'antd';
import { CustomToken } from '../global-theme';
interface Result extends ReturnType<typeof theme.useToken> {

View File

@ -1,10 +1,12 @@
import { UserOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { error } from '@tachybase/utils/client';
import { App, Dropdown, Menu, MenuProps } from 'antd';
import React, { createContext, useCallback, useMemo as useEffect, useState } from 'react';
import { error } from '@tachybase/utils/client';
import { UserOutlined } from '@ant-design/icons';
import { App, Dropdown, Menu, MenuProps } from 'antd';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useACLRoleContext, useAPIClient, useCurrentUserContext, useToken } from '..';
import { useChangePassword } from './ChangePassword';
import { useCurrentUserSettingsMenu } from './CurrentUserSettingsMenuProvider';
@ -164,22 +166,37 @@ export const SettingsMenu: React.FC<{
export const DropdownVisibleContext = createContext(null);
DropdownVisibleContext.displayName = 'DropdownVisibleContext';
const useStyles = createStyles(({ css, token }) => {
return {
dropdown: css`
display: inline-block;
vertical-align: top;
width: 46px;
height: 46px;
&:hover {
background: rgba(255, 255, 255, 0.1) !important;
}
`,
button: css`
max-width: 160px;
overflow: hidden;
display: inline-block;
line-height: 12px;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
padding: '16px';
color: ${token.colorTextHeaderMenu};
`,
};
});
export const CurrentUser = () => {
const [visible, setVisible] = useState(false);
const { token } = useToken();
const { styles } = useStyles();
return (
<div
className={css`
display: inline-block;
vertical-align: top;
width: 46px;
height: 46px;
&:hover {
background: rgba(255, 255, 255, 0.1) !important;
}
`}
>
<div className={styles.dropdown}>
<DropdownVisibleContext.Provider value={{ visible, setVisible }}>
<Dropdown
open={visible}
@ -190,18 +207,7 @@ export const CurrentUser = () => {
return <SettingsMenu />;
}}
>
<span
data-testid="user-center-button"
className={css`
max-width: 160px;
overflow: hidden;
display: inline-block;
line-height: 12px;
white-space: nowrap;
text-overflow: ellipsis;
`}
style={{ cursor: 'pointer', padding: '16px', color: token.colorTextHeaderMenu }}
>
<span data-testid="user-center-button" className={styles.button}>
<UserOutlined />
</span>
</Dropdown>

View File

@ -1,13 +1,24 @@
import { useTranslation } from 'react-i18next';
import { css } from '@emotion/css';
import { useAPIClient } from '../api-client';
import React, { useEffect, useRef, useState } from 'react';
import { useForm } from '@tachybase/schema';
import { useEffect, useRef, useState } from 'react';
import { Button, Input, message } from 'antd';
import React from 'react';
import { createStyles } from 'antd-style';
import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../api-client';
const useStyles = createStyles(({ css }) => {
return {
field: css`
display: flex;
gap: 0.5em;
`,
};
});
export default function VerificationCode({ targetFieldName = 'phone', actionType, value, onChange }) {
const { t } = useTranslation();
const { styles } = useStyles();
const api = useAPIClient();
const form = useForm();
@ -48,12 +59,7 @@ export default function VerificationCode({ targetFieldName = 'phone', actionType
}
return (
<fieldset
className={css`
display: flex;
gap: 0.5em;
`}
>
<fieldset className={styles.field}>
<Input value={value} onChange={onChange} placeholder={t('Verification code')} />
<Button onClick={onGetCode} disabled={count > 0}>
{count > 0 ? t('Retry after {{count}} seconds', { count }) : t('Send code')}

View File

@ -1,15 +1,15 @@
import { useCompile, useCollectionManager_deprecated, useCollectionDataSource } from '@tachybase/client';
import { lang, tval } from '../../locale';
import { useCollectionDataSource, useCollectionManager_deprecated, useCompile } from '@tachybase/client';
import {
Trigger,
CollectionBlockInitializer,
getCollectionFieldOptions,
FieldsSelect,
RadioWithTooltip,
CheckboxGroupWithTooltip,
CollectionBlockInitializer,
FieldsSelect,
getCollectionFieldOptions,
RadioWithTooltip,
Trigger,
} from '@tachybase/plugin-workflow/client';
import { lang, tval } from '../../locale';
const enum ACTION_TYPES {
CREATE = 'create',
UPDATE = 'update',

View File

@ -7,6 +7,7 @@ import {
useSchemaToolbar,
} from '@tachybase/client';
import { useFieldSchema } from '@tachybase/schema';
import { useTranslation } from 'react-i18next';
const schemaSettingsItems: SchemaSettingsItemType[] = [

View File

@ -1,5 +1,6 @@
import { BlockInitializer, useSchemaInitializerItem } from '@tachybase/client';
import React from 'react';
import { BlockInitializer, useSchemaInitializerItem } from '@tachybase/client';
import { tval } from '../../locale';
export const BulkWorkflowActionInitializer = () => {

View File

@ -1,7 +0,0 @@
import { BlockInitializer, useSchemaInitializerItem } from '@tachybase/client';
import React from 'react';
export const CustomizeActionInitializer = () => {
const itemConfig = useSchemaInitializerItem();
return <BlockInitializer {...itemConfig} item={itemConfig} />;
};

View File

@ -1,21 +1,21 @@
import { Plugin, useCollection } from '@tachybase/client';
import { tval } from '../../locale';
import { BulkWorkflowActionInitializer } from './BulkUpdateActionInitializer';
import { bulkWorkflowActionSettings } from './BulkUpdateAction.Settings';
import { CustomizeActionInitializer } from './CustomizeActionInitializer';
import { useCustomizeBulkWorkflowActionProps } from './utils';
import PluginWorkflow from '@tachybase/plugin-workflow/client';
import { tval } from '../../locale';
import { ApiTrigger } from './ApiTrigger';
import { bulkWorkflowActionSettings } from './BulkUpdateAction.Settings';
import { BulkWorkflowActionInitializer } from './BulkUpdateActionInitializer';
import { useCustomizeBulkWorkflowActionProps } from './utils';
export class PluginWorkflowBulk extends Plugin {
async load() {
this.app.addComponents({ CustomizeActionInitializer });
this.app.addComponents({ BulkWorkflowActionInitializer });
this.app.addScopes({ useCustomizeBulkWorkflowActionProps });
this.app.schemaSettingsManager.add(bulkWorkflowActionSettings);
const initializerData = {
title: tval('Bulk workflow'),
Component: BulkWorkflowActionInitializer,
Component: 'BulkWorkflowActionInitializer',
name: 'bulkWorkflow',
useVisible() {
const collection = useCollection();

View File

@ -1,4 +1,4 @@
import { SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
import { useContext } from 'react';
import {
isVariable,
transformVariableValue,
@ -11,9 +11,11 @@ import {
useTableBlockContext,
useVariables,
} from '@tachybase/client';
import { SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
import { App } from 'antd';
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from '../../locale';
export const useCustomizeBulkWorkflowActionProps = () => {

View File

@ -1,7 +1,7 @@
import { SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
import { useContext } from 'react';
import {
TableFieldResource,
isVariable,
TableFieldResource,
transformVariableValue,
useBlockRequestContext,
useCollection_deprecated,
@ -11,10 +11,12 @@ import {
useTableBlockContext,
useVariables,
} from '@tachybase/client';
import { SchemaExpressionScopeContext, useField, useFieldSchema } from '@tachybase/schema';
import { isURL } from '@tachybase/utils/client';
import { App, message } from 'antd';
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useBulkUpdateTranslation } from './locale';
export const useCustomizeBulkUpdateActionProps = () => {
@ -23,8 +25,6 @@ export const useCustomizeBulkUpdateActionProps = () => {
const actionSchema = useFieldSchema();
const tableBlockContext = useTableBlockContext();
const { rowKey } = tableBlockContext;
const selectedRecordKeys =
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
const navigate = useNavigate();
const compile = useCompile();
const { t } = useBulkUpdateTranslation();
@ -44,6 +44,8 @@ export const useCustomizeBulkUpdateActionProps = () => {
} = actionSchema?.['x-action-settings'] ?? {};
actionField.data = field.data || {};
actionField.data.loading = true;
const selectedRecordKeys =
tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
const assignedValues = {};
const waitList = Object.keys(originalAssignedValues).map(async (key) => {