From b4bc92d464a010166d9ca4371cdf96b767d87226 Mon Sep 17 00:00:00 2001 From: Ayobami Akingbade Date: Mon, 25 Dec 2023 03:37:00 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(entity-forms):=20implement=20r?= =?UTF-8?q?ight=20actions=20on=20schema=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SchemaForm/_RenderFormInput.tsx | 4 +++ src/frontend/components/SchemaForm/index.tsx | 1 + src/frontend/hooks/data/data.store.ts | 28 ++++++++++++----- src/frontend/views/data/Create/index.tsx | 30 ++++++------------- .../views/data/Create/run-initial-scripts.ts | 16 ++++++++++ src/frontend/views/data/Update/index.tsx | 9 ++---- src/frontend/views/data/_BaseEntityForm.tsx | 19 ++++++------ .../data/buildAppliedSchemaFormConfig.ts | 1 + src/frontend/views/data/portal/index.ts | 5 +++- src/frontend/views/data/portal/main.ts | 4 +++ src/shared/form-schemas/types.ts | 4 +++ 11 files changed, 75 insertions(+), 46 deletions(-) create mode 100644 src/frontend/views/data/Create/run-initial-scripts.ts diff --git a/src/frontend/components/SchemaForm/_RenderFormInput.tsx b/src/frontend/components/SchemaForm/_RenderFormInput.tsx index 837560ede..d99551ed8 100644 --- a/src/frontend/components/SchemaForm/_RenderFormInput.tsx +++ b/src/frontend/components/SchemaForm/_RenderFormInput.tsx @@ -14,6 +14,7 @@ import { FormFileInput } from "frontend/design-system/components/Form/FormFileIn import { FormSelectButton } from "frontend/design-system/components/Form/FormSelectButton"; import { FormRichTextArea } from "frontend/design-system/components/Form/FormRichTextArea"; import { FieldInputProps, FieldMetaState } from "react-final-form"; +import { ISharedFormInput } from "frontend/design-system/components/Form/_types"; interface IProps { type: keyof typeof FIELD_TYPES_CONFIG_MAP; @@ -28,6 +29,7 @@ interface IProps { label: string; placeholder?: string; description?: string; + rightActions?: ISharedFormInput["rightActions"]; } export function RenderFormInput({ @@ -40,6 +42,7 @@ export function RenderFormInput({ disabled, description, placeholder, + rightActions, }: IProps) { const formProps = { label, @@ -47,6 +50,7 @@ export function RenderFormInput({ disabled, placeholder: placeholder || label, description, + rightActions, ...renderProps, }; diff --git a/src/frontend/components/SchemaForm/index.tsx b/src/frontend/components/SchemaForm/index.tsx index 7f11105fc..4cfd2ce11 100644 --- a/src/frontend/components/SchemaForm/index.tsx +++ b/src/frontend/components/SchemaForm/index.tsx @@ -115,6 +115,7 @@ export function SchemaForm>({ (validation) => validation.validationType === "required" )} + rightActions={bag?.rightActions?.(form)} placeholder={bag.placeholder} description={bag.description} apiSelections={bag.apiSelections} diff --git a/src/frontend/hooks/data/data.store.ts b/src/frontend/hooks/data/data.store.ts index 217192473..97ea022c4 100644 --- a/src/frontend/hooks/data/data.store.ts +++ b/src/frontend/hooks/data/data.store.ts @@ -137,20 +137,32 @@ export const useEntityDataReference = (entity: string, entityId: string) => { defaultData: "", }); }; -export function useEntityDataCreationMutation(entity: string) { +export function useEntityDataCreationMutation( + entity: string, + option?: { + hideSuccessMessage?: boolean; + onSuccessActionWithFormData?: (id: string) => void; + } +) { const entityCrudConfig = useEntityCrudConfig(entity); const router = useRouter(); const apiMutateOptions = useWaitForResponseMutationOptions< Record >({ endpoints: DATA_MUTATION_ENDPOINTS_TO_CLEAR(entity), - smartSuccessMessage: ({ id }) => ({ - message: entityCrudConfig.MUTATION_LANG.CREATE, - action: { - label: entityCrudConfig.MUTATION_LANG.VIEW_DETAILS, - action: () => router.push(NAVIGATION_LINKS.ENTITY.DETAILS(entity, id)), - }, - }), + onSuccessActionWithFormData: ({ id }) => { + option?.onSuccessActionWithFormData(id); + }, + smartSuccessMessage: option?.hideSuccessMessage + ? undefined + : ({ id }) => ({ + message: entityCrudConfig.MUTATION_LANG.CREATE, + action: { + label: entityCrudConfig.MUTATION_LANG.VIEW_DETAILS, + action: () => + router.push(NAVIGATION_LINKS.ENTITY.DETAILS(entity, id)), + }, + }), }); return useMutation( diff --git a/src/frontend/views/data/Create/index.tsx b/src/frontend/views/data/Create/index.tsx index b1ebe304e..d5cef9d8d 100644 --- a/src/frontend/views/data/Create/index.tsx +++ b/src/frontend/views/data/Create/index.tsx @@ -7,32 +7,17 @@ import { AppLayout } from "frontend/_layouts/app"; import { useEntityCrudConfig, useEntitySlug, - useHiddenEntityColumns, } from "frontend/hooks/entity/entity.config"; import { useEntityDataCreationMutation } from "frontend/hooks/data/data.store"; import { useRouteParams } from "frontend/lib/routing/useRouteParam"; import { useEntityConfiguration } from "frontend/hooks/configuration/configuration.store"; -import { evalJavascriptStringSafely } from "frontend/lib/script-runner"; import { EntityActionTypes, useEntityActionMenuItems, } from "../../entity/constants"; import { BaseEntityForm } from "../_BaseEntityForm"; - -const runInitialValuesScript = ( - initialValuesScript: string -): Record => { - if (!initialValuesScript) { - return {}; - } - - const response = evalJavascriptStringSafely<{}>(initialValuesScript, {}); - - if (typeof response !== "object") { - return {}; - } - return response; -}; +import { runInitialValuesScript } from "./run-initial-scripts"; +import { PortalEntityFormComponent } from "../portal"; export function EntityCreate() { const routeParams = useRouteParams(); @@ -53,8 +38,6 @@ export function EntityCreate() { permission: META_USER_PERMISSIONS.NO_PERMISSION_REQUIRED, }); - const hiddenCreateColumns = useHiddenEntityColumns("create", entity); - const { backLink } = useNavigationStack(); const entityFormExtension = useEntityConfiguration( @@ -79,12 +62,17 @@ export function EntityCreate() { crudAction="create" resetForm buttonText={entityCrudConfig.FORM_LANG.CREATE} - initialValues={{ ...scriptInitialValues, ...routeParams }} + initialValuesData={{ + data: { ...scriptInitialValues, ...routeParams }, + error: entityFormExtension.error, + isLoading: entityFormExtension.isLoading, + isRefetching: false, + }} onSubmit={entityDataCreationMutation.mutateAsync} - hiddenColumns={hiddenCreateColumns} /> + ); } diff --git a/src/frontend/views/data/Create/run-initial-scripts.ts b/src/frontend/views/data/Create/run-initial-scripts.ts new file mode 100644 index 000000000..8c833ecbe --- /dev/null +++ b/src/frontend/views/data/Create/run-initial-scripts.ts @@ -0,0 +1,16 @@ +import { evalJavascriptStringSafely } from "frontend/lib/script-runner"; + +export const runInitialValuesScript = ( + initialValuesScript: string +): Record => { + if (!initialValuesScript) { + return {}; + } + + const response = evalJavascriptStringSafely<{}>(initialValuesScript, {}); + + if (typeof response !== "object") { + return {}; + } + return response; +}; diff --git a/src/frontend/views/data/Update/index.tsx b/src/frontend/views/data/Update/index.tsx index 373f9c6f0..b7b3b693b 100644 --- a/src/frontend/views/data/Update/index.tsx +++ b/src/frontend/views/data/Update/index.tsx @@ -9,7 +9,6 @@ import { useEntityCrudConfig, useEntityId, useEntitySlug, - useHiddenEntityColumns, } from "frontend/hooks/entity/entity.config"; import { useEntityDataDetails, @@ -21,6 +20,7 @@ import { } from "../../entity/constants"; import { BaseEntityForm } from "../_BaseEntityForm"; import { useDataUpdateActions } from "./portal"; +import { PortalEntityFormComponent } from "../portal"; export function EntityUpdate() { const entityId = useEntityId(); @@ -49,8 +49,6 @@ export function EntityUpdate() { const userHasPermission = useUserHasPermission(); - const hiddenUpdateColumns = useHiddenEntityColumns("update", entity); - const dataDetails = useEntityDataDetails({ entity, entityId }); const { backLink } = useNavigationStack(); @@ -73,12 +71,11 @@ export function EntityUpdate() { icon="save" buttonText={entityCrudConfig.FORM_LANG.UPDATE} onSubmit={entityDataUpdationMutation.mutateAsync} - hiddenColumns={hiddenUpdateColumns} - initialValues={dataDetails.data} - additionalDataState={dataDetails} + initialValuesData={dataDetails} /> + ); } diff --git a/src/frontend/views/data/_BaseEntityForm.tsx b/src/frontend/views/data/_BaseEntityForm.tsx index 4374cb190..84c158269 100644 --- a/src/frontend/views/data/_BaseEntityForm.tsx +++ b/src/frontend/views/data/_BaseEntityForm.tsx @@ -16,6 +16,7 @@ import { useEntityFieldSelections, useProcessedEntityFieldTypes, useEntityFieldValidations, + useHiddenEntityColumns, } from "frontend/hooks/entity/entity.config"; import { useMemo } from "react"; import { ViewStateMachine } from "frontend/components/ViewStateMachine"; @@ -29,10 +30,8 @@ import { usePortalExtendEntityFormConfig } from "./portal"; type IProps = { entity: string; - initialValues?: Record; + initialValuesData?: DataStateKeys>; crudAction: "create" | "update"; - hiddenColumns: DataStateKeys; - additionalDataState?: DataStateKeys; allOptional?: boolean; onSubmit: (data: Record) => Promise; resetForm?: true; @@ -42,14 +41,12 @@ type IProps = { export function BaseEntityForm({ entity, - initialValues, + initialValuesData, crudAction, allOptional, icon, resetForm, buttonText, - additionalDataState, - hiddenColumns, onSubmit, }: IProps) { const entityValidationsMap = useEntityFieldValidations(entity); @@ -61,6 +58,7 @@ export function BaseEntityForm({ "entity_columns_types", entity ); + const hiddenColumns = useHiddenEntityColumns(crudAction, entity); const extendEntityFormConfig = usePortalExtendEntityFormConfig( entity, @@ -78,7 +76,7 @@ export function BaseEntityForm({ const error = entityFieldTypesMap.error || hiddenColumns.error || - additionalDataState?.error || + initialValuesData?.error || entityFormExtension.error || entityToOneReferenceFields.error || entityFields.error; @@ -91,7 +89,7 @@ export function BaseEntityForm({ entity === SLUG_LOADING_VALUE || entityFieldTypesMap.isLoading || extendEntityFormConfig === "loading" || - additionalDataState?.isLoading; + initialValuesData?.isLoading; const viewState = useEntityViewStateMachine({ isLoading, @@ -114,6 +112,7 @@ export function BaseEntityForm({ ).map(({ name }) => name); const fieldsInitialValues = useMemo(() => { + const initialValues = initialValuesData?.data; if (!initialValues) { return initialValues; } @@ -128,7 +127,7 @@ export function BaseEntityForm({ return [field, value]; }) ); - }, [initialValues, hiddenColumns]); + }, [initialValuesData, hiddenColumns]); const formSchemaConfig = { entityToOneReferenceFields: entityToOneReferenceFields.data, @@ -169,7 +168,7 @@ export function BaseEntityForm({ icon={icon} initialValues={fieldsInitialValues} fields={ - typeof extendEntityFormConfig === "string" + extendEntityFormConfig === "loading" ? formConfig : extendEntityFormConfig(formConfig) } diff --git a/src/frontend/views/data/buildAppliedSchemaFormConfig.ts b/src/frontend/views/data/buildAppliedSchemaFormConfig.ts index 590ab6886..2bcc9e32d 100644 --- a/src/frontend/views/data/buildAppliedSchemaFormConfig.ts +++ b/src/frontend/views/data/buildAppliedSchemaFormConfig.ts @@ -40,6 +40,7 @@ export const buildAppliedSchemaFormConfig = ( entityFieldTypes[field] === "reference" ? { listUrl: ENTITY_LIST_PATH(entityToOneReferenceFields[field]), + entity: entityToOneReferenceFields[field], referenceUrl: (value: string) => ENTITY_REFERENCE_PATH({ entity: entityToOneReferenceFields[field], diff --git a/src/frontend/views/data/portal/index.ts b/src/frontend/views/data/portal/index.ts index 0f0a2fec5..9060c4dd8 100644 --- a/src/frontend/views/data/portal/index.ts +++ b/src/frontend/views/data/portal/index.ts @@ -1 +1,4 @@ -export { usePortalExtendEntityFormConfig } from "./main"; +export { + usePortalExtendEntityFormConfig, + PortalEntityFormComponent, +} from "./main"; diff --git a/src/frontend/views/data/portal/main.ts b/src/frontend/views/data/portal/main.ts index 5648071b7..b229db61a 100644 --- a/src/frontend/views/data/portal/main.ts +++ b/src/frontend/views/data/portal/main.ts @@ -16,3 +16,7 @@ export const usePortalExtendEntityFormConfig = ( return formConfig; }; }; + +export function PortalEntityFormComponent() { + return null; +} diff --git a/src/shared/form-schemas/types.ts b/src/shared/form-schemas/types.ts index 83377dad2..5eabe2395 100644 --- a/src/shared/form-schemas/types.ts +++ b/src/shared/form-schemas/types.ts @@ -1,3 +1,5 @@ +import { FormApi } from "final-form"; +import { ISharedFormInput } from "frontend/design-system/components/Form/_types"; import { GridSpanSizes, IColorableSelection } from "shared/types/ui"; import { FIELD_TYPES_CONFIG_MAP } from "shared/validations"; import { IFieldValidationItem } from "shared/validations/types"; @@ -6,6 +8,7 @@ export interface ISchemaFormConfig { selections?: IColorableSelection[]; apiSelections?: { listUrl: string; + entity?: string; referenceUrl?: (value: string) => string; }; type: keyof typeof FIELD_TYPES_CONFIG_MAP; @@ -13,6 +16,7 @@ export interface ISchemaFormConfig { placeholder?: string; description?: string; span?: GridSpanSizes; + rightActions?: (form: FormApi) => ISharedFormInput["rightActions"]; validations: IFieldValidationItem[]; }