From b9e79d23dcf4988f6f1e09f1bb828276132e94e6 Mon Sep 17 00:00:00 2001 From: Singh Date: Thu, 1 Feb 2024 12:26:58 +0100 Subject: [PATCH 01/13] first draft of HALForm --- app/src/app/UXPinGeneratedCode.jsx | 61 ++++++++++ app/src/app/app.tsx | 12 +- lib/src/components/HalForm/HalForm.tsx | 162 +++++++++++++++++++++++++ lib/src/index.ts | 7 +- 4 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 app/src/app/UXPinGeneratedCode.jsx create mode 100644 lib/src/components/HalForm/HalForm.tsx diff --git a/app/src/app/UXPinGeneratedCode.jsx b/app/src/app/UXPinGeneratedCode.jsx new file mode 100644 index 0000000..d347606 --- /dev/null +++ b/app/src/app/UXPinGeneratedCode.jsx @@ -0,0 +1,61 @@ +/** + * Info: + * 1. 'import' statements + * 2. Component Definition + * 3. 'type' attribute in DXC Components are added manually to the generated code (this is a violation of intrinsic prop assignment) + * therefore we need to solve this before anything else + * */ + +import { + DxcBox, + DxcDateInput, + DxcFlex, + DxcInset, + DxcNumberInput, + DxcSelect, +} from "@dxc-technology/halstack-react"; + +import { HalForm } from "@dxc-technology/halstack-react-hal"; + +const authHeaders = {}; + +const apiEndpoint = ""; + +const UXPinGeneratedCode = () => ( + + + + + + + + + + + + + + + +); + +export default UXPinGeneratedCode; diff --git a/app/src/app/app.tsx b/app/src/app/app.tsx index c94b7fe..9412a5e 100644 --- a/app/src/app/app.tsx +++ b/app/src/app/app.tsx @@ -1,10 +1,12 @@ -import { HalTable, HalAutocomplete } from '@dxc-technology/halstack-react-hal'; import { DxcApplicationLayout, - DxcInset, - DxcHeading, DxcFlex, + DxcHeading, + DxcInset, } from '@dxc-technology/halstack-react'; +import { HalAutocomplete, HalTable } from '@dxc-technology/halstack-react-hal'; + +import UXPinGeneratedCode from "./UXPinGeneratedCode"; function App() { return ( @@ -12,6 +14,10 @@ function App() { + + + + ) => void; + selfManagedSave?: boolean; +} + +interface InputProps { + formState: Record; + onChange: (e: ChangeEvent) => void; + name: string; + type?: string; + value: string; + onBlur?: any; +} + +const HalForm: React.FC = ({ + children, + apiEndpoint, + authHeaders, + onSubmit, + selfManagedSave, +}) => { + const [formState, setFormState] = useState>({}); + const [updatedFormState, setUpdatedFormState] = useState>({}); + const [apiData, requestStatus, requestError, resourceInteractions] = useHalResource({ + url: apiEndpoint, + headers: authHeaders, + }); + + useEffect(() => { + const values: Record = {}; + const extractFormValues = (children: React.ReactNode) => { + Children.forEach(children, (child, index) => { + if (React.isValidElement(child)) { + const { props } = child; + if (props.children){ + extractFormValues(props.children); + } + if (!props.children && props.name) { + values[props.name] = apiData.getProperty(props.name).value ?? null; + } + } + }); + }; + + requestStatus === 'resolved' && extractFormValues(children); + setFormState(values);; + }, [children, apiData, requestStatus]); + + const handleSubmit = async (e: React.FormEvent) => { + /** + * For future set up of action buttons + */ + e.preventDefault(); + if (onSubmit) { + onSubmit(updatedFormState); + } + }; + + const obtainProps = (child: any) => { + /** + * POC quality, this will be driven by native DXC Assure CDK Types and Data Schema Types + * + * + * + * Type checking is not the right implementation as type is an intrinsic property of html input element + * it must not be overwritten + * + * we need to find a better way finding out about the CDK Component type + */ + const properties: any = {}; + if (child.props.type === 'DxcTextInput') { + properties.onBlur = async () => { + if (selfManagedSave && resourceInteractions.update) { + await resourceInteractions.update(updatedFormState); + setUpdatedFormState({}); + } + }; + } + if (child.props.type === 'DxcDateInput') { + properties.onChange = async (e: any) => { + if (selfManagedSave && resourceInteractions.update) { + const { name } = child.props; + const { value } = e; + setFormState({ ...formState, [name]: value }); + await resourceInteractions.update({ [name]: value }); + setUpdatedFormState({}); + } + }; + } + if (child.props.type === 'DxcSelect') { + properties.onChange = async (e: any) => { + const { name } = child.props; + const { value } = e; + setFormState({ ...formState, [name]: value }); + if (selfManagedSave && resourceInteractions.update) { + await resourceInteractions.update({ [name]: value }); + setUpdatedFormState({}); + } + }; + const schemaDataProperties: any = apiData.getSchemaProperties(); + const schemaOfChild = + schemaDataProperties && + schemaDataProperties.find((obj: any) => obj.key === child.props.name); + properties.options = + schemaOfChild && + schemaOfChild.oneOf && + schemaOfChild.oneOf.map((one: any) => { + return { label: one.title, value: one.enum[0] }; + }); + } + return properties; + }; + + const processChildren = (children: React.ReactNode) => { + return Children.map(children, (child) => { + if (React.isValidElement(child)) { + let processedChild; + if (child.props.name) { + const inputProps: InputProps = { + onChange: (e: any) => { + const { name } = child.props; + const { value } = e; + setFormState({ ...formState, [name]: value }); + setUpdatedFormState({ ...updatedFormState, [name]: value }); + }, + name: child.props.name, + type: child.props.type, + value: formState[child.props.name] || '', + ...obtainProps(child), + }; + processedChild = React.cloneElement(child, inputProps); + } + + if (child.props.children) { + const processedGrandchildren: any = processChildren(child.props.children); + return React.cloneElement(child, {}, processedGrandchildren); + } + + return processedChild; + } + + return child; + }); + }; + + return ( + <> + {requestStatus === 'resolved' && ( +
{processChildren(children)}
+ )}{' '} + + ); +}; + +export default HalForm; diff --git a/lib/src/index.ts b/lib/src/index.ts index b2b886a..ecd12b4 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -1,5 +1,6 @@ -import useHalResource from './hooks/useHalResource'; -import HalTable from './components/HalTable'; import HalAutocomplete from './components/HalAutocomplete'; +import HalForm from './components/HalForm/HalForm'; +import HalTable from './components/HalTable'; +import useHalResource from './hooks/useHalResource'; -export { HalTable, HalAutocomplete, useHalResource }; +export { HalTable, HalAutocomplete, useHalResource, HalForm }; From 2a9a2c94ddacfe7bdc8e866d378e2fa6623c44ad Mon Sep 17 00:00:00 2001 From: Singh Date: Fri, 2 Feb 2024 13:37:23 +0100 Subject: [PATCH 02/13] reading from Assure Halstack CDK Types --- app/src/app/UXPinGeneratedCode.jsx | 37 +++-- lib/src/components/HalForm/HalForm.tsx | 145 ++---------------- .../HalForm/useHalFormChildrenProps.tsx | 142 +++++++++++++++++ 3 files changed, 173 insertions(+), 151 deletions(-) create mode 100644 lib/src/components/HalForm/useHalFormChildrenProps.tsx diff --git a/app/src/app/UXPinGeneratedCode.jsx b/app/src/app/UXPinGeneratedCode.jsx index d347606..d0a67a1 100644 --- a/app/src/app/UXPinGeneratedCode.jsx +++ b/app/src/app/UXPinGeneratedCode.jsx @@ -1,9 +1,8 @@ /** * Info: * 1. 'import' statements - * 2. Component Definition - * 3. 'type' attribute in DXC Components are added manually to the generated code (this is a violation of intrinsic prop assignment) - * therefore we need to solve this before anything else + * 2. Component Definition + * are added manually to the generated code * */ import { @@ -13,19 +12,34 @@ import { DxcInset, DxcNumberInput, DxcSelect, + DxcTextInput, } from "@dxc-technology/halstack-react"; import { HalForm } from "@dxc-technology/halstack-react-hal"; const authHeaders = {}; -const apiEndpoint = ""; +const apiEndpoint = ""; const UXPinGeneratedCode = () => ( + + + + ( placeholder={true} name="contract:signature_date" format="yyyy-MM-dd" - type="DxcDateInput" /> - - - - +
diff --git a/lib/src/components/HalForm/HalForm.tsx b/lib/src/components/HalForm/HalForm.tsx index f5e9c2b..fd8026e 100644 --- a/lib/src/components/HalForm/HalForm.tsx +++ b/lib/src/components/HalForm/HalForm.tsx @@ -1,24 +1,15 @@ -import React, { ChangeEvent, Children, ReactNode, useEffect, useState } from 'react'; +import React, { ReactNode } from "react"; -import useHalResource from './../../hooks/useHalResource'; +import useHalFormChildrenProps from "./useHalFormChildrenProps"; interface HalFormProps { children: ReactNode; apiEndpoint: string; authHeaders?: any; - onSubmit?: (formState: Record) => void; + onSubmit?: (formState: Record, onlyUpdatedFields: Record) => void; selfManagedSave?: boolean; } -interface InputProps { - formState: Record; - onChange: (e: ChangeEvent) => void; - name: string; - type?: string; - value: string; - onBlur?: any; -} - const HalForm: React.FC = ({ children, apiEndpoint, @@ -26,137 +17,23 @@ const HalForm: React.FC = ({ onSubmit, selfManagedSave, }) => { - const [formState, setFormState] = useState>({}); - const [updatedFormState, setUpdatedFormState] = useState>({}); - const [apiData, requestStatus, requestError, resourceInteractions] = useHalResource({ - url: apiEndpoint, - headers: authHeaders, - }); - - useEffect(() => { - const values: Record = {}; - const extractFormValues = (children: React.ReactNode) => { - Children.forEach(children, (child, index) => { - if (React.isValidElement(child)) { - const { props } = child; - if (props.children){ - extractFormValues(props.children); - } - if (!props.children && props.name) { - values[props.name] = apiData.getProperty(props.name).value ?? null; - } - } - }); - }; - - requestStatus === 'resolved' && extractFormValues(children); - setFormState(values);; - }, [children, apiData, requestStatus]); - + const { processChildren, formState, onlyUpdatedFields, requestStatus: apiRequestStatus } = useHalFormChildrenProps( + children, + apiEndpoint, + authHeaders, + selfManagedSave + ); const handleSubmit = async (e: React.FormEvent) => { /** * For future set up of action buttons */ e.preventDefault(); if (onSubmit) { - onSubmit(updatedFormState); + onSubmit(formState, onlyUpdatedFields); } }; - const obtainProps = (child: any) => { - /** - * POC quality, this will be driven by native DXC Assure CDK Types and Data Schema Types - * - * - * - * Type checking is not the right implementation as type is an intrinsic property of html input element - * it must not be overwritten - * - * we need to find a better way finding out about the CDK Component type - */ - const properties: any = {}; - if (child.props.type === 'DxcTextInput') { - properties.onBlur = async () => { - if (selfManagedSave && resourceInteractions.update) { - await resourceInteractions.update(updatedFormState); - setUpdatedFormState({}); - } - }; - } - if (child.props.type === 'DxcDateInput') { - properties.onChange = async (e: any) => { - if (selfManagedSave && resourceInteractions.update) { - const { name } = child.props; - const { value } = e; - setFormState({ ...formState, [name]: value }); - await resourceInteractions.update({ [name]: value }); - setUpdatedFormState({}); - } - }; - } - if (child.props.type === 'DxcSelect') { - properties.onChange = async (e: any) => { - const { name } = child.props; - const { value } = e; - setFormState({ ...formState, [name]: value }); - if (selfManagedSave && resourceInteractions.update) { - await resourceInteractions.update({ [name]: value }); - setUpdatedFormState({}); - } - }; - const schemaDataProperties: any = apiData.getSchemaProperties(); - const schemaOfChild = - schemaDataProperties && - schemaDataProperties.find((obj: any) => obj.key === child.props.name); - properties.options = - schemaOfChild && - schemaOfChild.oneOf && - schemaOfChild.oneOf.map((one: any) => { - return { label: one.title, value: one.enum[0] }; - }); - } - return properties; - }; - - const processChildren = (children: React.ReactNode) => { - return Children.map(children, (child) => { - if (React.isValidElement(child)) { - let processedChild; - if (child.props.name) { - const inputProps: InputProps = { - onChange: (e: any) => { - const { name } = child.props; - const { value } = e; - setFormState({ ...formState, [name]: value }); - setUpdatedFormState({ ...updatedFormState, [name]: value }); - }, - name: child.props.name, - type: child.props.type, - value: formState[child.props.name] || '', - ...obtainProps(child), - }; - processedChild = React.cloneElement(child, inputProps); - } - - if (child.props.children) { - const processedGrandchildren: any = processChildren(child.props.children); - return React.cloneElement(child, {}, processedGrandchildren); - } - - return processedChild; - } - - return child; - }); - }; - - return ( - <> - {requestStatus === 'resolved' && ( -
{processChildren(children)}
- )}{' '} - - ); + return
{apiRequestStatus === 'resolved' && processChildren(children)}
; }; export default HalForm; diff --git a/lib/src/components/HalForm/useHalFormChildrenProps.tsx b/lib/src/components/HalForm/useHalFormChildrenProps.tsx new file mode 100644 index 0000000..a16c6ed --- /dev/null +++ b/lib/src/components/HalForm/useHalFormChildrenProps.tsx @@ -0,0 +1,142 @@ +import { ChangeEvent, Children, useState } from "react"; +import React, { useEffect } from "react"; + +import DxcDateInput from "@dxc-technology/halstack-react/date-input/DateInput"; +import DxcSelect from "@dxc-technology/halstack-react/select/Select"; +import useHalResource from "lib/src/hooks/useHalResource"; + +/** + * State management is not Production quality + * This is only for POC + * If this project gets approved this will be first item to be refactored + */ + +interface InputProps { + formState: Record; + onChange: (e: ChangeEvent) => void; + name: string; + type?: string; + value: string; + onBlur?: any; +} + +const useHalFormChildrenProps = ( + children: React.ReactNode, + apiEndpoint: string, + authHeaders: any, + selfManagedSave?: boolean +) => { + const [formState, setFormState] = useState>({}); + const [onlyUpdatedFields, setOnlyUpdatedFields] = useState>({}); + const [apiData, requestStatus, requestError, resourceInteractions] = useHalResource({ + url: apiEndpoint, + headers: authHeaders, + }); + + useEffect(() => { + const values: Record = {}; + const extractFormValues = (children: React.ReactNode) => { + Children.forEach(children, (child) => { + if (React.isValidElement(child)) { + const { props } = child; + if (props.children) { + extractFormValues(props.children); + } + if (!props.children && props.name) { + values[props.name] = apiData.getProperty(props.name).value ?? null; + } + } + }); + }; + + requestStatus === "resolved" && extractFormValues(children); + setFormState(values); + }, [children, apiData, requestStatus]); + + const obtainRenderProps = (child: any) => { + const properties: any = { + onChange: (e: any) => { + const { name } = child.props; + const { value } = e; + setFormState({ ...formState, [name]: value }); + setOnlyUpdatedFields({ ...onlyUpdatedFields, [name]: value }); + }, + value: formState[child.props.name] || "", + }; + + switch (child.type) { + case DxcDateInput: + properties.onChange = async (e: any) => { + if (selfManagedSave && resourceInteractions.update) { + const { name } = child.props; + const { value } = e; + setFormState({ ...formState, [name]: value }); + await resourceInteractions.update({ [name]: value }); + setOnlyUpdatedFields({}); + } + }; + break; + case DxcSelect: + { + const schemaDataProperties: any = apiData.getSchemaProperties(); + if (schemaDataProperties) { + const schemaOfChild: any = schemaDataProperties.find( + (obj: any) => obj.key === child.props.name + ); + if (schemaOfChild && schemaOfChild.oneOf) { + properties.options = schemaOfChild.oneOf.map((one: any) => { + return { label: one.title, value: one.enum[0] }; + }); + } + } + + properties.onChange = async (e: any) => { + const { name } = child.props; + const { value } = e; + setFormState({ ...formState, [name]: value }); + if (selfManagedSave && resourceInteractions.update) { + await resourceInteractions.update({ [name]: value }); + setOnlyUpdatedFields({}); + } + }; + } + + break; + default: + properties.onBlur = async () => { + if (selfManagedSave && resourceInteractions.update) { + await resourceInteractions.update(onlyUpdatedFields); + setOnlyUpdatedFields({}); + } + }; + break; + } + + return properties; + }; + const processChildren = (children: React.ReactNode) => { + return Children.map(children, (child) => { + if (React.isValidElement(child)) { + + if (child.props.children) { + const processedGrandchildren: any = processChildren(child.props.children); + return React.cloneElement(child, {}, processedGrandchildren); + } + + + const inputProps: InputProps = { + ...obtainRenderProps(child), + }; + + + + return React.cloneElement(child, inputProps); + } + + return child; + }); + }; + return ({formState, onlyUpdatedFields, processChildren, requestStatus, requestError}); +}; + +export default useHalFormChildrenProps; From c158ae788b5d94150ed9bf2a3a76a2e3fbb4c597 Mon Sep 17 00:00:00 2001 From: Singh Date: Tue, 20 Feb 2024 10:43:13 +0100 Subject: [PATCH 03/13] HalForm displays API interation errors --- app/src/app/UXPinGeneratedCode.jsx | 113 +++++++++++------- lib/src/components/HalForm/HalForm.tsx | 34 +++++- .../HalForm/useHalFormChildrenProps.tsx | 99 +++++++++++---- 3 files changed, 174 insertions(+), 72 deletions(-) diff --git a/app/src/app/UXPinGeneratedCode.jsx b/app/src/app/UXPinGeneratedCode.jsx index d0a67a1..aee34c1 100644 --- a/app/src/app/UXPinGeneratedCode.jsx +++ b/app/src/app/UXPinGeneratedCode.jsx @@ -6,59 +6,88 @@ * */ import { - DxcBox, DxcDateInput, DxcFlex, - DxcInset, - DxcNumberInput, + DxcGrid, + DxcRadioGroup, DxcSelect, DxcTextInput, + DxcTextarea } from "@dxc-technology/halstack-react"; import { HalForm } from "@dxc-technology/halstack-react-hal"; -const authHeaders = {}; +const authHeaders = { + "x-auth-username": "DSINGH", + "x-api-key": "48SmqcLpec3t1TO8EMzaDaamMz25pDZ469NFux41", +}; -const apiEndpoint = ""; +const apiEndpoint = + "https://diaas-dev.gtaia-test-domain.net/std-dev-lux-alt-13111/insurance/persons/ID-wJsQCHTKS"; const UXPinGeneratedCode = () => ( - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); export default UXPinGeneratedCode; diff --git a/lib/src/components/HalForm/HalForm.tsx b/lib/src/components/HalForm/HalForm.tsx index fd8026e..f7851d0 100644 --- a/lib/src/components/HalForm/HalForm.tsx +++ b/lib/src/components/HalForm/HalForm.tsx @@ -1,3 +1,4 @@ +import { DxcAlert, DxcFlex } from "@dxc-technology/halstack-react"; import React, { ReactNode } from "react"; import useHalFormChildrenProps from "./useHalFormChildrenProps"; @@ -5,8 +6,11 @@ import useHalFormChildrenProps from "./useHalFormChildrenProps"; interface HalFormProps { children: ReactNode; apiEndpoint: string; - authHeaders?: any; - onSubmit?: (formState: Record, onlyUpdatedFields: Record) => void; + authHeaders?: Record; + onSubmit?: ( + formState: Record, + onlyUpdatedFields: Record + ) => void; selfManagedSave?: boolean; } @@ -17,7 +21,14 @@ const HalForm: React.FC = ({ onSubmit, selfManagedSave, }) => { - const { processChildren, formState, onlyUpdatedFields, requestStatus: apiRequestStatus } = useHalFormChildrenProps( + const { + processChildren, + formState, + onlyUpdatedFields, + requestStatus: apiRequestStatus, + requestError, + apiUpdateError, + } = useHalFormChildrenProps( children, apiEndpoint, authHeaders, @@ -33,7 +44,20 @@ const HalForm: React.FC = ({ } }; - return
{apiRequestStatus === 'resolved' && processChildren(children)}
; + return ( + <> + {apiUpdateError?.body?.messages?.map((err: any) => ( + + + + ))} + +
+ {processChildren(children)} +
+
+ + ); }; -export default HalForm; +export default React.memo(HalForm); diff --git a/lib/src/components/HalForm/useHalFormChildrenProps.tsx b/lib/src/components/HalForm/useHalFormChildrenProps.tsx index a16c6ed..ecdab76 100644 --- a/lib/src/components/HalForm/useHalFormChildrenProps.tsx +++ b/lib/src/components/HalForm/useHalFormChildrenProps.tsx @@ -1,9 +1,7 @@ import { ChangeEvent, Children, useState } from "react"; import React, { useEffect } from "react"; -import DxcDateInput from "@dxc-technology/halstack-react/date-input/DateInput"; -import DxcSelect from "@dxc-technology/halstack-react/select/Select"; -import useHalResource from "lib/src/hooks/useHalResource"; +import useHalResource from "../../hooks/useHalResource"; /** * State management is not Production quality @@ -11,14 +9,25 @@ import useHalResource from "lib/src/hooks/useHalResource"; * If this project gets approved this will be first item to be refactored */ -interface InputProps { +type InputProps = { formState: Record; onChange: (e: ChangeEvent) => void; name: string; type?: string; value: string; onBlur?: any; -} +}; + +type errorType = { + status?: string; + message?: string; + body?: { + _outcome?: any; + messages?: any; + }; +}; + +type SchemaType = "date" | "select" | "text" | "number"; const useHalFormChildrenProps = ( children: React.ReactNode, @@ -28,13 +37,14 @@ const useHalFormChildrenProps = ( ) => { const [formState, setFormState] = useState>({}); const [onlyUpdatedFields, setOnlyUpdatedFields] = useState>({}); + const [apiUpdateError, setAPIUpdateError] = useState({}); const [apiData, requestStatus, requestError, resourceInteractions] = useHalResource({ url: apiEndpoint, headers: authHeaders, }); - useEffect(() => { - const values: Record = {}; + useEffect(() => { + const values: Record = {...formState, ...onlyUpdatedFields}; const extractFormValues = (children: React.ReactNode) => { Children.forEach(children, (child) => { if (React.isValidElement(child)) { @@ -42,16 +52,48 @@ const useHalFormChildrenProps = ( if (props.children) { extractFormValues(props.children); } - if (!props.children && props.name) { + + if (!props.children && props.name && apiData) { values[props.name] = apiData.getProperty(props.name).value ?? null; } } }); }; - requestStatus === "resolved" && extractFormValues(children); + extractFormValues(children); setFormState(values); - }, [children, apiData, requestStatus]); + }, [apiData]); + + const schemaType = (child: any) => { + const schemaDataProperties: any = apiData?.getSchemaProperties(); + if (schemaDataProperties) { + const schemaOfChild: any = schemaDataProperties.find( + (obj: any) => obj.key === child.props.name + ); + if (schemaOfChild?.oneOf) { + return "select"; + } + if (schemaOfChild?.format === "date") { + return "date"; + } + if (schemaOfChild?.format === "number") { + return "number"; + } + if (schemaOfChild?.format === "integer") { + return "integer"; + } + return "text"; + } + }; + + const updateHandler = async (payload: any) => { + try { + await resourceInteractions.update(payload); + setAPIUpdateError({}); + } catch (error: any) { + setAPIUpdateError(error); + } + }; const obtainRenderProps = (child: any) => { const properties: any = { @@ -64,26 +106,26 @@ const useHalFormChildrenProps = ( value: formState[child.props.name] || "", }; - switch (child.type) { - case DxcDateInput: + switch (schemaType(child)) { + case "date": properties.onChange = async (e: any) => { if (selfManagedSave && resourceInteractions.update) { const { name } = child.props; const { value } = e; setFormState({ ...formState, [name]: value }); - await resourceInteractions.update({ [name]: value }); + await updateHandler({ [name]: value }); setOnlyUpdatedFields({}); } }; break; - case DxcSelect: + case "select": { const schemaDataProperties: any = apiData.getSchemaProperties(); if (schemaDataProperties) { const schemaOfChild: any = schemaDataProperties.find( (obj: any) => obj.key === child.props.name ); - if (schemaOfChild && schemaOfChild.oneOf) { + if (schemaOfChild?.oneOf) { properties.options = schemaOfChild.oneOf.map((one: any) => { return { label: one.title, value: one.enum[0] }; }); @@ -92,10 +134,14 @@ const useHalFormChildrenProps = ( properties.onChange = async (e: any) => { const { name } = child.props; - const { value } = e; + let { value } = e; + if (!value) { + // this is due to inconsistent change event param + value = e; + } setFormState({ ...formState, [name]: value }); if (selfManagedSave && resourceInteractions.update) { - await resourceInteractions.update({ [name]: value }); + await updateHandler({ [name]: value }); setOnlyUpdatedFields({}); } }; @@ -105,7 +151,7 @@ const useHalFormChildrenProps = ( default: properties.onBlur = async () => { if (selfManagedSave && resourceInteractions.update) { - await resourceInteractions.update(onlyUpdatedFields); + await updateHandler(onlyUpdatedFields); setOnlyUpdatedFields({}); } }; @@ -116,19 +162,15 @@ const useHalFormChildrenProps = ( }; const processChildren = (children: React.ReactNode) => { return Children.map(children, (child) => { - if (React.isValidElement(child)) { - + if (React.isValidElement(child)) { if (child.props.children) { const processedGrandchildren: any = processChildren(child.props.children); return React.cloneElement(child, {}, processedGrandchildren); } - - const inputProps: InputProps = { + const inputProps: InputProps = { ...obtainRenderProps(child), }; - - return React.cloneElement(child, inputProps); } @@ -136,7 +178,14 @@ const useHalFormChildrenProps = ( return child; }); }; - return ({formState, onlyUpdatedFields, processChildren, requestStatus, requestError}); + return { + formState, + onlyUpdatedFields, + processChildren, + requestStatus, + requestError, + apiUpdateError, + }; }; export default useHalFormChildrenProps; From 6dfce1956b5b8893f9a1abe8f92d62b44c817772 Mon Sep 17 00:00:00 2001 From: Singh Date: Tue, 20 Feb 2024 10:47:25 +0100 Subject: [PATCH 04/13] HalForm displays API interation errors --- app/src/app/UXPinGeneratedCode.jsx | 122 ++++++++++++----------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/app/src/app/UXPinGeneratedCode.jsx b/app/src/app/UXPinGeneratedCode.jsx index aee34c1..197d967 100644 --- a/app/src/app/UXPinGeneratedCode.jsx +++ b/app/src/app/UXPinGeneratedCode.jsx @@ -1,7 +1,7 @@ /** * Info: * 1. 'import' statements - * 2. Component Definition + * 2. Component Definition * are added manually to the generated code * */ @@ -12,82 +12,64 @@ import { DxcRadioGroup, DxcSelect, DxcTextInput, - DxcTextarea + DxcTextarea, } from "@dxc-technology/halstack-react"; import { HalForm } from "@dxc-technology/halstack-react-hal"; -const authHeaders = { - "x-auth-username": "DSINGH", - "x-api-key": "48SmqcLpec3t1TO8EMzaDaamMz25pDZ469NFux41", -}; +const authHeaders = {}; -const apiEndpoint = - "https://diaas-dev.gtaia-test-domain.net/std-dev-lux-alt-13111/insurance/persons/ID-wJsQCHTKS"; +const apiEndpoint = ""; const UXPinGeneratedCode = () => ( - - - - - - - - - - - - - - - + + + + + + + + + + + + ); export default UXPinGeneratedCode; From 25395f6f3e6a760e98f9c86093d74fc03b27f789 Mon Sep 17 00:00:00 2001 From: Singh Date: Wed, 21 Feb 2024 10:17:41 +0100 Subject: [PATCH 05/13] HalForm Optimizing the auto save --- .../HalForm/useHalFormChildrenProps.tsx | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/src/components/HalForm/useHalFormChildrenProps.tsx b/lib/src/components/HalForm/useHalFormChildrenProps.tsx index ecdab76..fb16319 100644 --- a/lib/src/components/HalForm/useHalFormChildrenProps.tsx +++ b/lib/src/components/HalForm/useHalFormChildrenProps.tsx @@ -27,15 +27,13 @@ type errorType = { }; }; -type SchemaType = "date" | "select" | "text" | "number"; - const useHalFormChildrenProps = ( children: React.ReactNode, apiEndpoint: string, authHeaders: any, selfManagedSave?: boolean ) => { - const [formState, setFormState] = useState>({}); + const [formFieldState, setFormFieldState] = useState>({}); const [onlyUpdatedFields, setOnlyUpdatedFields] = useState>({}); const [apiUpdateError, setAPIUpdateError] = useState({}); const [apiData, requestStatus, requestError, resourceInteractions] = useHalResource({ @@ -43,8 +41,12 @@ const useHalFormChildrenProps = ( headers: authHeaders, }); - useEffect(() => { - const values: Record = {...formState, ...onlyUpdatedFields}; + const setFormState = (newState: Record) => { + setFormFieldState((prevState: Record) => ({ ...prevState, ...newState })); + }; + + useEffect(() => { + const values: Record = { ...formFieldState, ...onlyUpdatedFields }; const extractFormValues = (children: React.ReactNode) => { Children.forEach(children, (child) => { if (React.isValidElement(child)) { @@ -52,7 +54,7 @@ const useHalFormChildrenProps = ( if (props.children) { extractFormValues(props.children); } - + if (!props.children && props.name && apiData) { values[props.name] = apiData.getProperty(props.name).value ?? null; } @@ -64,7 +66,7 @@ const useHalFormChildrenProps = ( setFormState(values); }, [apiData]); - const schemaType = (child: any) => { + const schemaType = (child: any) => { const schemaDataProperties: any = apiData?.getSchemaProperties(); if (schemaDataProperties) { const schemaOfChild: any = schemaDataProperties.find( @@ -87,23 +89,27 @@ const useHalFormChildrenProps = ( }; const updateHandler = async (payload: any) => { - try { - await resourceInteractions.update(payload); - setAPIUpdateError({}); - } catch (error: any) { - setAPIUpdateError(error); + if (payload && Object.keys(payload).length) { + try { + await resourceInteractions.update(payload); + setOnlyUpdatedFields({}); + setAPIUpdateError({}); + } catch (error: any) { + setAPIUpdateError(error); + } } }; const obtainRenderProps = (child: any) => { const properties: any = { + key: child.props.name, + value: formFieldState[child.props.name] || "", onChange: (e: any) => { const { name } = child.props; const { value } = e; - setFormState({ ...formState, [name]: value }); + setFormState({ [name]: value }); setOnlyUpdatedFields({ ...onlyUpdatedFields, [name]: value }); }, - value: formState[child.props.name] || "", }; switch (schemaType(child)) { @@ -112,9 +118,8 @@ const useHalFormChildrenProps = ( if (selfManagedSave && resourceInteractions.update) { const { name } = child.props; const { value } = e; - setFormState({ ...formState, [name]: value }); + setFormState({ [name]: value }); await updateHandler({ [name]: value }); - setOnlyUpdatedFields({}); } }; break; @@ -139,10 +144,9 @@ const useHalFormChildrenProps = ( // this is due to inconsistent change event param value = e; } - setFormState({ ...formState, [name]: value }); + setFormState({ [name]: value }); if (selfManagedSave && resourceInteractions.update) { await updateHandler({ [name]: value }); - setOnlyUpdatedFields({}); } }; } @@ -152,7 +156,6 @@ const useHalFormChildrenProps = ( properties.onBlur = async () => { if (selfManagedSave && resourceInteractions.update) { await updateHandler(onlyUpdatedFields); - setOnlyUpdatedFields({}); } }; break; @@ -179,7 +182,7 @@ const useHalFormChildrenProps = ( }); }; return { - formState, + formState: formFieldState, onlyUpdatedFields, processChildren, requestStatus, From 903e2f215f8234bc07c8dcd26a181c8b0eafcce5 Mon Sep 17 00:00:00 2001 From: Singh Date: Wed, 21 Feb 2024 14:44:54 +0100 Subject: [PATCH 06/13] HalForm Optimizing the auto save --- .../HalForm/useHalFormChildrenProps.tsx | 13 +- lib/src/components/HalForm/useHalOptions.tsx | 216 ++++++++++++++++++ 2 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 lib/src/components/HalForm/useHalOptions.tsx diff --git a/lib/src/components/HalForm/useHalFormChildrenProps.tsx b/lib/src/components/HalForm/useHalFormChildrenProps.tsx index fb16319..e61d764 100644 --- a/lib/src/components/HalForm/useHalFormChildrenProps.tsx +++ b/lib/src/components/HalForm/useHalFormChildrenProps.tsx @@ -1,6 +1,7 @@ import { ChangeEvent, Children, useState } from "react"; import React, { useEffect } from "react"; +import { onOptions } from "./useHalOptions"; import useHalResource from "../../hooks/useHalResource"; /** @@ -36,17 +37,19 @@ const useHalFormChildrenProps = ( const [formFieldState, setFormFieldState] = useState>({}); const [onlyUpdatedFields, setOnlyUpdatedFields] = useState>({}); const [apiUpdateError, setAPIUpdateError] = useState({}); + const [apiOptions, setAPIOptions] = useState>({}); const [apiData, requestStatus, requestError, resourceInteractions] = useHalResource({ url: apiEndpoint, headers: authHeaders, }); - const setFormState = (newState: Record) => { + const setFormState = (newState: Record) => { setFormFieldState((prevState: Record) => ({ ...prevState, ...newState })); }; useEffect(() => { const values: Record = { ...formFieldState, ...onlyUpdatedFields }; + const options: Record = {...apiOptions }; const extractFormValues = (children: React.ReactNode) => { Children.forEach(children, (child) => { if (React.isValidElement(child)) { @@ -57,6 +60,7 @@ const useHalFormChildrenProps = ( if (!props.children && props.name && apiData) { values[props.name] = apiData.getProperty(props.name).value ?? null; + options[props.name] = onOptions(apiData.resourceRepresentation).getProperty(props.name) ?? null; } } }); @@ -64,6 +68,7 @@ const useHalFormChildrenProps = ( extractFormValues(children); setFormState(values); + setAPIOptions(options); }, [apiData]); const schemaType = (child: any) => { @@ -104,6 +109,12 @@ const useHalFormChildrenProps = ( const properties: any = { key: child.props.name, value: formFieldState[child.props.name] || "", + min: apiOptions[child.props.name]?.min, + max: apiOptions[child.props.name]?.max, + disabled: !apiOptions[child.props.name]?.canPatch?.(), + optional: !apiOptions[child.props.name]?.isRequired, + minLength: apiOptions[child.props.name]?.minLength, + maxLength: apiOptions[child.props.name]?.maxLength, onChange: (e: any) => { const { name } = child.props; const { value } = e; diff --git a/lib/src/components/HalForm/useHalOptions.tsx b/lib/src/components/HalForm/useHalOptions.tsx new file mode 100644 index 0000000..e1206d5 --- /dev/null +++ b/lib/src/components/HalForm/useHalOptions.tsx @@ -0,0 +1,216 @@ +/* eslint-disable no-prototype-builtins */ + +// on GET, we have _options, on OPTIONS, we got the option detail directly on response +const getOptionFromResponse: (response: any) => { links: any[]; title: string; properties: any; required: string[] } = ( + response +) => (response?.hasOwnProperty('_options') ? response['_options'] : response); + +type MethodType = 'GET' | 'PATCH' | 'DELETE' | 'POST' | string; + +interface MethodInterface { + href: string; + mediaType: string; + method: MethodType; + schema?: any; + rel: string; + title: string; +} + +type PropertyType = +| string +| string[] +| number +| number[] +| boolean +| undefined +| null; + +interface OptionsProperty { + + /** + * + * Return true if the property is patchable + */ + canPatch: () => boolean; + visible: boolean; + minLength: number; + maxLength: number; + min: number; + max: number; + + type: string; + + /** + * + */ + isRequired: boolean; + + /** + * If the property is a complex list + */ + isOneOf: boolean; + + /** + * Return the list of values of the oneOf property + */ + getValuesOfOneOf: () => T[]; + + /** + * Return the label for a given value + * @param value - value of the property + * @returns - + */ + getLabelOfOneOf: (value: PropertyType) => string; +}; + +export interface OnOptionsReturn { + + /** + * Check if the method is allow + * @param method - method type + * @returns true if its allowed + */ + canMethod: (method: MethodType) => boolean; + + /** + * Check if the POST method is allow + * @returns true if its allowed + */ + canPost: () => boolean; + + /** + * Check if the DELETE method is allow + * @returns true if its allowed + */ + canDelete: () => boolean; + + /** + * Check if the PATCH method is allow + * @returns true if its allowed + */ + canPatch: () => boolean; + + /** + * Get the method detail of a rel + * @param rel - rel to check, most of the time it's the inquery + * @returns method detail + */ + getMethodByRel: (rel: string) => MethodInterface; + + /** + * Get the options for a property or a sub property of a complexlist + * @param property - property name + * @param subProperty - sub property name for complexlist only + * @returns property options + */ + getProperty: (property: string, subProperty?: string) => OptionsProperty; +} + +export const onOptions = (response: any, forCollection = false): OnOptionsReturn => { + const options = getOptionFromResponse(response); + + const getMethod = (method: MethodType): MethodInterface => options['links']?.find((item: any) => item.method === method); + const getMethodByRel = (rel: string): MethodInterface => options['links']?.find((item: any) => item.rel === rel); + const getMethodSchema = (method: MethodType): any => getMethod(method)?.schema; + const canMethod = (method: MethodType): boolean => !!getMethod(method); + const canPost = () => canMethod('POST'); + const canPatch = () => canMethod('PATCH'); + const canDelete = () => canMethod('DELETE'); + + const getProperty = (property: string, subProperty?: string): OptionsProperty => { + // subProperty in case of complex list + const targetProperty = subProperty ?? property; + + // return the property object depends if the property is a complex list or not + // base can be schema or options + const getOptionOrSchemaProperties = (base: any) => { + + /** + * For Normal/Root properties + */ + if (!subProperty) { + return base.properties; + } + + /** + * For ComplexList or SimpleList properties + */ + + if (!forCollection) { + return base.properties?.[property]?.items.items ?? {}; + } + + /** + * For Collection properties + */ + const baseProperties = base?.properties?._links?.properties?.item?.properties?.[property].properties; + return baseProperties?.oneOf?.[0] ?? baseProperties; + }; + + const propertiesOptions = getOptionOrSchemaProperties(options); + + // complexList attributes are in [property].items.items + const canPatchProperty = () => { + if (!canPatch()) { + return false; + } + + const schema = getMethodSchema('PATCH'); + // Check is the property can be patchable + return schema ? getOptionOrSchemaProperties(schema).hasOwnProperty(targetProperty) : false; + }; + + const isRequired = !!(options?.required?.indexOf(property) >= 0); + const isVisible = propertiesOptions.hasOwnProperty(targetProperty); + + // If it's not visible, that means this property doesn't exist, so we return an empty object + const propertyOption = isVisible ? propertiesOptions[targetProperty] : {}; + + const isOneOf: boolean = propertyOption.hasOwnProperty('oneOf') || propertyOption.enum?.length > 0; + const { minLength, maxLength, minimum, maximum, type } = propertyOption; + const getValuesOfOneOf = () => { + if (!isOneOf) { + return []; + } + + if (propertyOption?.['oneOf']) { + return propertyOption?.['oneOf'].map(({ enum: [value], title }: { enum: any[]; title: string }) => ({ + value, + label: title + })); + } + else { + return propertyOption?.['enum'].map((enumItem: any) => ({ + value: enumItem, + label: enumItem.toString() + })); + } + }; + + // Get the label of a oneOf, if the value doesn't exist, it will return undefined + const getLabelOfOneOf = (givenValue: any) => getValuesOfOneOf().find(({ value }: { value: any }) => value === givenValue)?.label; + + return { + canPatch: canPatchProperty, + visible: isVisible, + minLength, + maxLength, + min: minimum, + max: maximum, + type, + isRequired, + isOneOf, + getValuesOfOneOf, + getLabelOfOneOf + }; + }; + + return { + canMethod, + canPost, + canDelete, + canPatch, + getMethodByRel, + getProperty + }; +}; From 3a25799a895a60e1dfebab46f92168965b42cf9d Mon Sep 17 00:00:00 2001 From: Singh Date: Wed, 21 Feb 2024 14:52:37 +0100 Subject: [PATCH 07/13] HalForm Optimizing the auto save --- app/src/app/UXPinGeneratedCode.jsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/app/UXPinGeneratedCode.jsx b/app/src/app/UXPinGeneratedCode.jsx index 197d967..012a278 100644 --- a/app/src/app/UXPinGeneratedCode.jsx +++ b/app/src/app/UXPinGeneratedCode.jsx @@ -17,9 +17,13 @@ import { import { HalForm } from "@dxc-technology/halstack-react-hal"; -const authHeaders = {}; +const authHeaders = { + "x-auth-username": "pgdimitr", + "x-api-key": "48SmqcLpec3t1TO8EMzaDaamMz25pDZ469NFux41", +}; -const apiEndpoint = ""; +const apiEndpoint = + "https://diaas-dev.gtaia-test-domain.net/std-dev-lux-alt-13111/insurance/persons/ID-wJsQCFmjS"; const UXPinGeneratedCode = () => ( @@ -27,12 +31,7 @@ const UXPinGeneratedCode = () => ( Date: Mon, 26 Feb 2024 11:52:20 +0100 Subject: [PATCH 08/13] improving the options support --- app/src/app/UXPinGeneratedCodeForPnC.jsx | 47 +++++ lib/src/components/HalForm/HalForm.tsx | 2 - .../{useHalOptions.tsx => HalOptions.tsx} | 2 + .../HalForm/useHalFormChildrenProps.tsx | 173 +++++++----------- package-lock.json | 2 +- package.json | 2 +- 6 files changed, 120 insertions(+), 108 deletions(-) create mode 100644 app/src/app/UXPinGeneratedCodeForPnC.jsx rename lib/src/components/HalForm/{useHalOptions.tsx => HalOptions.tsx} (99%) diff --git a/app/src/app/UXPinGeneratedCodeForPnC.jsx b/app/src/app/UXPinGeneratedCodeForPnC.jsx new file mode 100644 index 0000000..4f2bd90 --- /dev/null +++ b/app/src/app/UXPinGeneratedCodeForPnC.jsx @@ -0,0 +1,47 @@ +/** + * Info: + * 1. 'import' statements + * 2. Component Definition + * are added manually to the generated code + * */ + +import { + DxcDateInput, + DxcFlex, + DxcGrid, + DxcRadioGroup, + DxcSelect, + DxcTextInput, + DxcTextarea, +} from "@dxc-technology/halstack-react"; + +import { HalForm } from "@dxc-technology/halstack-react-hal"; + +const authHeaders = { + Accept: 'application/hal+json, application/json', + 'Accept-Language': 'en', + Authorization: 'Bearer eyJraWQiOiI0QnJ3U2I5bncxZzlmeU5ZdkdmR0VkdStKdVMxSkNodzZcL3FxWWViQ1ljWT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJlYzQzNGUwNS1hMjdjLTQwYTQtYWYxOS02NDIyZTJiYzliYmQiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtd2VzdC0xLmFtYXpvbmF3cy5jb21cL3VzLXdlc3QtMV9remlZeFdrVVEiLCJjbGllbnRfaWQiOiIzcWNyNmJ2anIxMmQyaDM0ZWpvaThrczEzbiIsIm9yaWdpbl9qdGkiOiJkOGQzMDIyOC02NTYyLTRmNDItYTkwNC0xY2EzNDc2NTBmYTYiLCJldmVudF9pZCI6ImY5YWNkNzc0LTc5YjAtNDFhNS1iNmM0LWIyMjUxNjAyNTYyNiIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE3MDg1MjM4MTMsImV4cCI6MTcwODUyNzQxMywiaWF0IjoxNzA4NTIzODEzLCJqdGkiOiJiZWRjMmFhNy01ZGMxLTQ1NzYtYmM0ZS01NjYzNmJiOGE5MjEiLCJ1c2VybmFtZSI6InB0dXNlciJ9.Ak8VDqe00CVxZtibR3csZ-VMuw2OB5NnjQRM2AC2rC-rnn2NT1Ha0M0oU9P16oBm8ZjZmN2mH1z4g1S-loGwBT9BKw4jdoFdXSuotyIyYXk-Zhu6StoTnypzpFHFpL1eQNnLg0ThaZLZC2s910DxEyPHjgi16_DZ5EGERZAKsLn6l4HJFFe5S0U0FU6IiByzwyGvSFcqQspHgElPQqs9rCcayg5fIE-EaWWDURc-gGZ-BGvC561ZePE4TqR9BTpCm-uu53I3Ydf1ydnZ0jP0kkCLRVntThM_bulkImw9HRDaOL0rBf8SzN0iA2ivy1pWKs4dVC-jzHcsDriqgaRqmQ', + 'Content-Type': 'application/json', + 'X-Csc-User-Id': 'ptuser' +}; + +const apiEndpoint = + "https://alb-external.sandbox-1012.hub-51.sandbox.assure.dxc.com/pointin-wc-quotes/policies/V0NWMDAxOTI0NjAwMDAwNTAwU0M="; + +const UXPinGeneratedCodeForPnC = () => ( + + + + + + + + +); + +export default UXPinGeneratedCodeForPnC; diff --git a/lib/src/components/HalForm/HalForm.tsx b/lib/src/components/HalForm/HalForm.tsx index f7851d0..40b6888 100644 --- a/lib/src/components/HalForm/HalForm.tsx +++ b/lib/src/components/HalForm/HalForm.tsx @@ -25,8 +25,6 @@ const HalForm: React.FC = ({ processChildren, formState, onlyUpdatedFields, - requestStatus: apiRequestStatus, - requestError, apiUpdateError, } = useHalFormChildrenProps( children, diff --git a/lib/src/components/HalForm/useHalOptions.tsx b/lib/src/components/HalForm/HalOptions.tsx similarity index 99% rename from lib/src/components/HalForm/useHalOptions.tsx rename to lib/src/components/HalForm/HalOptions.tsx index e1206d5..3d47e0e 100644 --- a/lib/src/components/HalForm/useHalOptions.tsx +++ b/lib/src/components/HalForm/HalOptions.tsx @@ -109,6 +109,8 @@ export interface OnOptionsReturn { export const onOptions = (response: any, forCollection = false): OnOptionsReturn => { const options = getOptionFromResponse(response); + + const getMethod = (method: MethodType): MethodInterface => options['links']?.find((item: any) => item.method === method); const getMethodByRel = (rel: string): MethodInterface => options['links']?.find((item: any) => item.rel === rel); const getMethodSchema = (method: MethodType): any => getMethod(method)?.schema; diff --git a/lib/src/components/HalForm/useHalFormChildrenProps.tsx b/lib/src/components/HalForm/useHalFormChildrenProps.tsx index e61d764..83e5faa 100644 --- a/lib/src/components/HalForm/useHalFormChildrenProps.tsx +++ b/lib/src/components/HalForm/useHalFormChildrenProps.tsx @@ -1,8 +1,8 @@ import { ChangeEvent, Children, useState } from "react"; import React, { useEffect } from "react"; -import { onOptions } from "./useHalOptions"; -import useHalResource from "../../hooks/useHalResource"; +import { HalApiCaller } from "@dxc-technology/halstack-client"; +import { onOptions } from "./HalOptions"; /** * State management is not Production quality @@ -37,66 +37,72 @@ const useHalFormChildrenProps = ( const [formFieldState, setFormFieldState] = useState>({}); const [onlyUpdatedFields, setOnlyUpdatedFields] = useState>({}); const [apiUpdateError, setAPIUpdateError] = useState({}); - const [apiOptions, setAPIOptions] = useState>({}); - const [apiData, requestStatus, requestError, resourceInteractions] = useHalResource({ - url: apiEndpoint, - headers: authHeaders, - }); + const [apiOptions, setAPIOptions] = useState>({}); - const setFormState = (newState: Record) => { + const setFormState = (newState: Record) => { setFormFieldState((prevState: Record) => ({ ...prevState, ...newState })); }; useEffect(() => { const values: Record = { ...formFieldState, ...onlyUpdatedFields }; - const options: Record = {...apiOptions }; - const extractFormValues = (children: React.ReactNode) => { + HalApiCaller.get({ + url: apiEndpoint, + headers: { ...authHeaders }, + }).then((response: any) => { + extractFormValues(children, response); + setFormState(values); + }); + const extractFormValues = (children: React.ReactNode, response: any) => { Children.forEach(children, (child) => { if (React.isValidElement(child)) { const { props } = child; if (props.children) { - extractFormValues(props.children); + return extractFormValues(props.children, response); } + if (props.name) { + values[props.name] = response.halResource.resourceRepresentation[props.name] ?? null; + } + } + }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - if (!props.children && props.name && apiData) { - values[props.name] = apiData.getProperty(props.name).value ?? null; - options[props.name] = onOptions(apiData.resourceRepresentation).getProperty(props.name) ?? null; + useEffect(() => { + const options: Record = { ...apiOptions }; + HalApiCaller.options({ + url: apiEndpoint, + headers: { ...authHeaders }, + }).then((response: any) => { + const processedOptions = onOptions({ _options: response.halResource.resourceRepresentation }); + extractOptions(children, processedOptions); + setAPIOptions(options); + }); + const extractOptions = (children: React.ReactNode, processedOptions: any) => { + Children.forEach(children, (child) => { + if (React.isValidElement(child)) { + const { props } = child; + if (props.children) { + return extractOptions(props.children, processedOptions); + } + if (props.name) { + options[props.name] = processedOptions.getProperty(props.name) ?? null; } } }); }; - extractFormValues(children); - setFormState(values); - setAPIOptions(options); - }, [apiData]); - - const schemaType = (child: any) => { - const schemaDataProperties: any = apiData?.getSchemaProperties(); - if (schemaDataProperties) { - const schemaOfChild: any = schemaDataProperties.find( - (obj: any) => obj.key === child.props.name - ); - if (schemaOfChild?.oneOf) { - return "select"; - } - if (schemaOfChild?.format === "date") { - return "date"; - } - if (schemaOfChild?.format === "number") { - return "number"; - } - if (schemaOfChild?.format === "integer") { - return "integer"; - } - return "text"; - } - }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const updateHandler = async (payload: any) => { - if (payload && Object.keys(payload).length) { + if (selfManagedSave && payload && Object.keys(payload).length) { try { - await resourceInteractions.update(payload); + await HalApiCaller.patch({ + url: apiEndpoint, + headers: { ...authHeaders }, + body: { ...payload }, + }); setOnlyUpdatedFields({}); setAPIUpdateError({}); } catch (error: any) { @@ -106,70 +112,34 @@ const useHalFormChildrenProps = ( }; const obtainRenderProps = (child: any) => { + if (!child?.props?.name) { + return {}; + } const properties: any = { key: child.props.name, - value: formFieldState[child.props.name] || "", - min: apiOptions[child.props.name]?.min, - max: apiOptions[child.props.name]?.max, - disabled: !apiOptions[child.props.name]?.canPatch?.(), - optional: !apiOptions[child.props.name]?.isRequired, - minLength: apiOptions[child.props.name]?.minLength, - maxLength: apiOptions[child.props.name]?.maxLength, + value: formFieldState?.[child.props.name] || "", + min: apiOptions?.[child.props.name]?.min, + max: apiOptions?.[child.props.name]?.max, + disabled: !apiOptions?.[child.props.name]?.canPatch?.(), + optional: !apiOptions?.[child.props.name]?.isRequired, + minLength: apiOptions?.[child.props.name]?.minLength, + maxLength: apiOptions?.[child.props.name]?.maxLength, onChange: (e: any) => { const { name } = child.props; - const { value } = e; + let { value } = e; + if (!value) { + // this is due to inconsistent change event param + value = e; + } setFormState({ [name]: value }); setOnlyUpdatedFields({ ...onlyUpdatedFields, [name]: value }); }, + onBlur: async () => { + await updateHandler(onlyUpdatedFields); + }, }; - - switch (schemaType(child)) { - case "date": - properties.onChange = async (e: any) => { - if (selfManagedSave && resourceInteractions.update) { - const { name } = child.props; - const { value } = e; - setFormState({ [name]: value }); - await updateHandler({ [name]: value }); - } - }; - break; - case "select": - { - const schemaDataProperties: any = apiData.getSchemaProperties(); - if (schemaDataProperties) { - const schemaOfChild: any = schemaDataProperties.find( - (obj: any) => obj.key === child.props.name - ); - if (schemaOfChild?.oneOf) { - properties.options = schemaOfChild.oneOf.map((one: any) => { - return { label: one.title, value: one.enum[0] }; - }); - } - } - - properties.onChange = async (e: any) => { - const { name } = child.props; - let { value } = e; - if (!value) { - // this is due to inconsistent change event param - value = e; - } - setFormState({ [name]: value }); - if (selfManagedSave && resourceInteractions.update) { - await updateHandler({ [name]: value }); - } - }; - } - - break; - default: - properties.onBlur = async () => { - if (selfManagedSave && resourceInteractions.update) { - await updateHandler(onlyUpdatedFields); - } - }; - break; + if (apiOptions?.[child.props.name]?.isOneOf) { + properties.options = apiOptions?.[child.props.name]?.getValuesOfOneOf(); } return properties; @@ -181,23 +151,18 @@ const useHalFormChildrenProps = ( const processedGrandchildren: any = processChildren(child.props.children); return React.cloneElement(child, {}, processedGrandchildren); } - const inputProps: InputProps = { ...obtainRenderProps(child), }; - return React.cloneElement(child, inputProps); } - return child; }); }; return { formState: formFieldState, onlyUpdatedFields, - processChildren, - requestStatus, - requestError, + processChildren, apiUpdateError, }; }; diff --git a/package-lock.json b/package-lock.json index 2765142..7eceaa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@halstack-react-hal/source", "version": "0.0.0", "dependencies": { - "@dxc-technology/halstack-client": "^1.5.0", + "@dxc-technology/halstack-client": "^1.5.1", "@nx/next": "16.10.0", "@swc/helpers": "~0.5.2", "next": "^13.5.5", diff --git a/package.json b/package.json index 0d873e5..51f6cf8 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "private": true, "dependencies": { - "@dxc-technology/halstack-client": "^1.5.0", + "@dxc-technology/halstack-client": "^1.5.1", "@nx/next": "16.10.0", "@swc/helpers": "~0.5.2", "next": "^13.5.5", From 0d2d7f6ac01b78fb6e200faaf49f16540acc781e Mon Sep 17 00:00:00 2001 From: Singh Date: Mon, 26 Feb 2024 11:54:37 +0100 Subject: [PATCH 09/13] improving the options support --- app/src/app/UXPinGeneratedCode.jsx | 5 +-- app/src/app/UXPinGeneratedCodeForPnC.jsx | 47 ------------------------ 2 files changed, 2 insertions(+), 50 deletions(-) delete mode 100644 app/src/app/UXPinGeneratedCodeForPnC.jsx diff --git a/app/src/app/UXPinGeneratedCode.jsx b/app/src/app/UXPinGeneratedCode.jsx index 012a278..75e17ee 100644 --- a/app/src/app/UXPinGeneratedCode.jsx +++ b/app/src/app/UXPinGeneratedCode.jsx @@ -18,12 +18,11 @@ import { import { HalForm } from "@dxc-technology/halstack-react-hal"; const authHeaders = { - "x-auth-username": "pgdimitr", - "x-api-key": "48SmqcLpec3t1TO8EMzaDaamMz25pDZ469NFux41", + }; const apiEndpoint = - "https://diaas-dev.gtaia-test-domain.net/std-dev-lux-alt-13111/insurance/persons/ID-wJsQCFmjS"; + ""; const UXPinGeneratedCode = () => ( diff --git a/app/src/app/UXPinGeneratedCodeForPnC.jsx b/app/src/app/UXPinGeneratedCodeForPnC.jsx deleted file mode 100644 index 4f2bd90..0000000 --- a/app/src/app/UXPinGeneratedCodeForPnC.jsx +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Info: - * 1. 'import' statements - * 2. Component Definition - * are added manually to the generated code - * */ - -import { - DxcDateInput, - DxcFlex, - DxcGrid, - DxcRadioGroup, - DxcSelect, - DxcTextInput, - DxcTextarea, -} from "@dxc-technology/halstack-react"; - -import { HalForm } from "@dxc-technology/halstack-react-hal"; - -const authHeaders = { - Accept: 'application/hal+json, application/json', - 'Accept-Language': 'en', - Authorization: 'Bearer eyJraWQiOiI0QnJ3U2I5bncxZzlmeU5ZdkdmR0VkdStKdVMxSkNodzZcL3FxWWViQ1ljWT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJlYzQzNGUwNS1hMjdjLTQwYTQtYWYxOS02NDIyZTJiYzliYmQiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtd2VzdC0xLmFtYXpvbmF3cy5jb21cL3VzLXdlc3QtMV9remlZeFdrVVEiLCJjbGllbnRfaWQiOiIzcWNyNmJ2anIxMmQyaDM0ZWpvaThrczEzbiIsIm9yaWdpbl9qdGkiOiJkOGQzMDIyOC02NTYyLTRmNDItYTkwNC0xY2EzNDc2NTBmYTYiLCJldmVudF9pZCI6ImY5YWNkNzc0LTc5YjAtNDFhNS1iNmM0LWIyMjUxNjAyNTYyNiIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE3MDg1MjM4MTMsImV4cCI6MTcwODUyNzQxMywiaWF0IjoxNzA4NTIzODEzLCJqdGkiOiJiZWRjMmFhNy01ZGMxLTQ1NzYtYmM0ZS01NjYzNmJiOGE5MjEiLCJ1c2VybmFtZSI6InB0dXNlciJ9.Ak8VDqe00CVxZtibR3csZ-VMuw2OB5NnjQRM2AC2rC-rnn2NT1Ha0M0oU9P16oBm8ZjZmN2mH1z4g1S-loGwBT9BKw4jdoFdXSuotyIyYXk-Zhu6StoTnypzpFHFpL1eQNnLg0ThaZLZC2s910DxEyPHjgi16_DZ5EGERZAKsLn6l4HJFFe5S0U0FU6IiByzwyGvSFcqQspHgElPQqs9rCcayg5fIE-EaWWDURc-gGZ-BGvC561ZePE4TqR9BTpCm-uu53I3Ydf1ydnZ0jP0kkCLRVntThM_bulkImw9HRDaOL0rBf8SzN0iA2ivy1pWKs4dVC-jzHcsDriqgaRqmQ', - 'Content-Type': 'application/json', - 'X-Csc-User-Id': 'ptuser' -}; - -const apiEndpoint = - "https://alb-external.sandbox-1012.hub-51.sandbox.assure.dxc.com/pointin-wc-quotes/policies/V0NWMDAxOTI0NjAwMDAwNTAwU0M="; - -const UXPinGeneratedCodeForPnC = () => ( - - - - - - - - -); - -export default UXPinGeneratedCodeForPnC; From fe278623f83dd3117440c7b6ca5a6d95d08588ce Mon Sep 17 00:00:00 2001 From: Singh Date: Mon, 26 Feb 2024 12:51:25 +0100 Subject: [PATCH 10/13] improving the options support --- lib/src/components/HalForm/HalStackClientModule.d.ts | 3 +++ lib/src/components/HalForm/useHalFormChildrenProps.tsx | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 lib/src/components/HalForm/HalStackClientModule.d.ts diff --git a/lib/src/components/HalForm/HalStackClientModule.d.ts b/lib/src/components/HalForm/HalStackClientModule.d.ts new file mode 100644 index 0000000..0879eae --- /dev/null +++ b/lib/src/components/HalForm/HalStackClientModule.d.ts @@ -0,0 +1,3 @@ +// temp +// types must come from halstack-client library +declare module "@dxc-technology/halstack-client"; \ No newline at end of file diff --git a/lib/src/components/HalForm/useHalFormChildrenProps.tsx b/lib/src/components/HalForm/useHalFormChildrenProps.tsx index 83e5faa..6569a0e 100644 --- a/lib/src/components/HalForm/useHalFormChildrenProps.tsx +++ b/lib/src/components/HalForm/useHalFormChildrenProps.tsx @@ -115,6 +115,7 @@ const useHalFormChildrenProps = ( if (!child?.props?.name) { return {}; } + const isOneOf: boolean = apiOptions?.[child.props.name]?.isOneOf || false; const properties: any = { key: child.props.name, value: formFieldState?.[child.props.name] || "", @@ -133,12 +134,17 @@ const useHalFormChildrenProps = ( } setFormState({ [name]: value }); setOnlyUpdatedFields({ ...onlyUpdatedFields, [name]: value }); + if (isOneOf){ + updateHandler({ [name]: value }); + } }, onBlur: async () => { - await updateHandler(onlyUpdatedFields); + if (!isOneOf){ + await updateHandler(onlyUpdatedFields); + } }, }; - if (apiOptions?.[child.props.name]?.isOneOf) { + if (isOneOf) { properties.options = apiOptions?.[child.props.name]?.getValuesOfOneOf(); } From c0d5f10cf3856b45a99896478bd8ae2b0e526901 Mon Sep 17 00:00:00 2001 From: DSingh Date: Mon, 20 Jan 2025 12:51:01 +0100 Subject: [PATCH 11/13] minor fixes --- lib/src/components/HalForm/HalForm.tsx | 24 +++++-------------- .../HalForm/useHalFormChildrenProps.tsx | 7 +++--- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/lib/src/components/HalForm/HalForm.tsx b/lib/src/components/HalForm/HalForm.tsx index 40b6888..fd1ddaf 100644 --- a/lib/src/components/HalForm/HalForm.tsx +++ b/lib/src/components/HalForm/HalForm.tsx @@ -7,10 +7,7 @@ interface HalFormProps { children: ReactNode; apiEndpoint: string; authHeaders?: Record; - onSubmit?: ( - formState: Record, - onlyUpdatedFields: Record - ) => void; + onSubmit?: (formState: Record, onlyUpdatedFields: Record) => void; selfManagedSave?: boolean; } @@ -21,12 +18,7 @@ const HalForm: React.FC = ({ onSubmit, selfManagedSave, }) => { - const { - processChildren, - formState, - onlyUpdatedFields, - apiUpdateError, - } = useHalFormChildrenProps( + const { processChildren, formState, onlyUpdatedFields, apiUpdateError } = useHalFormChildrenProps( children, apiEndpoint, authHeaders, @@ -43,18 +35,14 @@ const HalForm: React.FC = ({ }; return ( - <> + {apiUpdateError?.body?.messages?.map((err: any) => ( - + ))} - -
- {processChildren(children)} -
-
- +
{processChildren(children)}
+
); }; diff --git a/lib/src/components/HalForm/useHalFormChildrenProps.tsx b/lib/src/components/HalForm/useHalFormChildrenProps.tsx index 6569a0e..e3928bc 100644 --- a/lib/src/components/HalForm/useHalFormChildrenProps.tsx +++ b/lib/src/components/HalForm/useHalFormChildrenProps.tsx @@ -25,6 +25,7 @@ type errorType = { body?: { _outcome?: any; messages?: any; + message?: any; }; }; @@ -106,7 +107,7 @@ const useHalFormChildrenProps = ( setOnlyUpdatedFields({}); setAPIUpdateError({}); } catch (error: any) { - setAPIUpdateError(error); + setAPIUpdateError({body: {messages: error?.response?.data?.messages? [...error.response.data.messages] : [error?.response?.data?.message]}}); } } }; @@ -134,11 +135,11 @@ const useHalFormChildrenProps = ( } setFormState({ [name]: value }); setOnlyUpdatedFields({ ...onlyUpdatedFields, [name]: value }); - if (isOneOf){ + if (isOneOf || e.date){ updateHandler({ [name]: value }); } }, - onBlur: async () => { + onBlur: async (e: any) => { if (!isOneOf){ await updateHandler(onlyUpdatedFields); } From f2e12e14ae7343cd8c4086ddc31918d5f6ae74c2 Mon Sep 17 00:00:00 2001 From: dsingh45 Date: Mon, 20 Jan 2025 13:05:11 +0100 Subject: [PATCH 12/13] Delete app/src/app/UXPinGeneratedCode.jsx --- app/src/app/UXPinGeneratedCode.jsx | 73 ------------------------------ 1 file changed, 73 deletions(-) delete mode 100644 app/src/app/UXPinGeneratedCode.jsx diff --git a/app/src/app/UXPinGeneratedCode.jsx b/app/src/app/UXPinGeneratedCode.jsx deleted file mode 100644 index 75e17ee..0000000 --- a/app/src/app/UXPinGeneratedCode.jsx +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Info: - * 1. 'import' statements - * 2. Component Definition - * are added manually to the generated code - * */ - -import { - DxcDateInput, - DxcFlex, - DxcGrid, - DxcRadioGroup, - DxcSelect, - DxcTextInput, - DxcTextarea, -} from "@dxc-technology/halstack-react"; - -import { HalForm } from "@dxc-technology/halstack-react-hal"; - -const authHeaders = { - -}; - -const apiEndpoint = - ""; - -const UXPinGeneratedCode = () => ( - - - - - - - - - - - - -); - -export default UXPinGeneratedCode; From 11726729c05c88f9af11d93f89db5947179a44d4 Mon Sep 17 00:00:00 2001 From: dsingh45 Date: Mon, 20 Jan 2025 13:05:55 +0100 Subject: [PATCH 13/13] Update app.tsx --- app/src/app/app.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/app/app.tsx b/app/src/app/app.tsx index cb3c040..0c09d75 100644 --- a/app/src/app/app.tsx +++ b/app/src/app/app.tsx @@ -1,10 +1,10 @@ +import { HalTable, HalAutocomplete } from "@dxc-technology/halstack-react-hal"; import { DxcApplicationLayout, - DxcFlex, - DxcHeading, DxcInset, + DxcHeading, + DxcFlex, } from "@dxc-technology/halstack-react"; -import { HalAutocomplete, HalTable } from "@dxc-technology/halstack-react-hal"; function App() { return ( @@ -75,4 +75,4 @@ function App() { ); } -export default App; \ No newline at end of file +export default App;