diff --git a/app/components/Application/ApplicationDetailsCardItemCustomFields.tsx b/app/components/Application/ApplicationDetailsCardItemCustomFields.tsx index d99f77321b..d30250237a 100644 --- a/app/components/Application/ApplicationDetailsCardItemCustomFields.tsx +++ b/app/components/Application/ApplicationDetailsCardItemCustomFields.tsx @@ -12,192 +12,181 @@ import FuelField from 'containers/Forms/FuelField'; import EmissionCategoryRowIdField from 'containers/Forms/EmissionCategoryRowIdField'; import NaicsField from 'containers/Forms/NaicsField'; -const customFields = ( - showDiff: boolean, - diffPathArray: any[], - diffArray: any[], - handleEnums: (...args: any[]) => any, - previousIsEmpty: boolean, - setHasErrors: (...args: any[]) => any -) => { - const setErrorIcon: (...args: any[]) => object = (props) => { - if (props?.errorSchema?.__errors || props.rawErrors) { - setHasErrors(true); - return ; - } - - return undefined; - }; - - const CUSTOM_FIELDS: Record> = { - TitleField: (props) =>

{props.title}

, - StringField: (props) => { - const errorIcon: object = setErrorIcon(props); - - const {idSchema, formData} = props; - const id = idSchema?.$id; - let prevValue; - let hasDiff = false; - if (showDiff) { - hasDiff = diffPathArray.includes(id.replace(/^[^_]*_/g, '')); - prevValue = - diffArray[diffPathArray.indexOf(id.replace(/^[^_]*_/g, ''))]; +// Some formData values are numbers that map to enums, this function uses the number values to return the enum names stored in the schema +const getDisplayValue = (schema, value) => { + if (schema.enum && schema.enumNames) { + // TODO: needs a fix on jsonschema types (missing enumNames) + const enumIndex = schema.enum.indexOf(value); + if (enumIndex === -1) return null; + return schema.enumNames[enumIndex]; + } + + return value; +}; - if ((hasDiff || previousIsEmpty) && prevValue !== formData) { - prevValue = handleEnums(props, false, prevValue); - const currentValue = handleEnums(props, true, prevValue); +const ErrorIcon: React.FunctionComponent = (props) => { + if (props?.errorSchema?.__errors || props.rawErrors) { + return ; + } - return ( - <> - - {prevValue ?? [No Data Entered]} - -  --->  - - {currentValue ?? [No Data Entered]} - - - ); - } - } + return null; +}; - if (formData === null || formData === undefined || formData === '') +const CUSTOM_FIELDS: Record> = { + TitleField: (props) =>

{props.title}

, + StringField: (props) => { + const { + idSchema, + schema, + formData, + formContext: {showDiff, idDiffMap} + } = props; + const displayValue = getDisplayValue(schema, formData); + const id = idSchema?.$id; + if (showDiff) { + const diff = idDiffMap?.[id]; + + if (diff && diff.lhs !== formData) { + const prevValue = getDisplayValue(schema, diff.lhs); return ( <> - [No Data Entered] - {errorIcon} + + {prevValue ?? [No Data Entered]} + +  --->  + + {displayValue ?? [No Data Entered]} + ); + } + } - const value = handleEnums(props, true, prevValue); + if (formData === null || formData === undefined || formData === '') return ( <> - {value} - {errorIcon} + [No Data Entered] + {ErrorIcon} ); - }, - BooleanField: (props) => { - const errorIcon = setErrorIcon(props); - const {idSchema, formData} = props; - const id = idSchema?.$id; - const hasDiff = diffPathArray.includes( - idSchema.$id.replace(/^[^_]*_/g, '') + return ( + <> + {displayValue} + {ErrorIcon} + + ); + }, + BooleanField: (props) => { + const { + idSchema, + formData, + formContext: {showDiff, idDiffMap} + } = props; + const id = idSchema?.$id; + const diff = idDiffMap?.[id]; + + if (showDiff && diff) { + return ( + <> + + {diff.lhs ? 'Yes' : 'No'} + +  --->  + + {formData ? 'Yes' : 'No'} + + ); + } - if (showDiff && hasDiff) { - const prevValue = - diffArray[ - diffPathArray.indexOf(idSchema.$id.replace(/^[^_]*_/g, '')) - ]; - return ( - <> - - {prevValue ? 'Yes' : 'No'} - -  --->  - - {formData ? 'Yes' : 'No'} - - - ); - } - + return ( + + {formData ? <>Yes {ErrorIcon} : <>No {ErrorIcon}}{' '} + + ); + }, + naics: (props) => , + emissionSource: EmissionSourceFields, + emissionGas: (props) => , + product: (props) => ( + + ), + productRowId: (props) => ( + + ), + fuel: (props) => , + fuelRowId: (props) => ( + + ), + emissionCategoryRowId: (props) => ( + + ), + NumberField: (props) => { + const { + idSchema, + formData, + schema, + formContext: {showDiff, idDiffMap} + } = props; + const id = idSchema?.$id; + if (formData === null || formData === undefined || formData === '') return ( - - {formData ? <>Yes {errorIcon} : <>No {errorIcon}}{' '} - + <> + [No Data Entered] + {ErrorIcon} + ); - }, - naics: (props) => , - emissionSource: EmissionSourceFields, - emissionGas: (props) => ( - - ), - product: (props) => ( - - ), - productRowId: (props) => ( - - ), - fuel: (props) => , - fuelRowId: (props) => ( - - ), - emissionCategoryRowId: (props) => ( - - ), - NumberField: (props) => { - const errorIcon = setErrorIcon(props); - - const {idSchema, formData} = props; - const id = idSchema?.$id; - if (formData === null || formData === undefined || formData === '') - return ( - <> - [No Data Entered] - {errorIcon} - - ); + const displayValue = getDisplayValue(schema, formData); - let prevValue; - let hasDiff = false; - if (showDiff) { - hasDiff = diffPathArray.includes(id.replace(/^[^_]*_/g, '')); - prevValue = - diffArray[diffPathArray.indexOf(id.replace(/^[^_]*_/g, ''))]; - if (hasDiff || previousIsEmpty) { - prevValue = handleEnums(props, false, prevValue); - const currentValue = handleEnums(props, true, prevValue); + const diff = idDiffMap?.[id]; - return ( - <> - - {prevValue ? ( - - ) : ( - [No Data Entered] - )} - -  --->  - - {currentValue ? ( - - ) : ( - [No Data Entered] - )} - - - ); - } - } + if (showDiff && diff) { + const prevValue = getDisplayValue(schema, diff.lhs); - const value = handleEnums(props, true, prevValue); return ( <> - - {errorIcon} + + {prevValue !== null && prevValue !== undefined ? ( + + ) : ( + [No Data Entered] + )} + +  --->  + + {displayValue !== null && displayValue !== undefined ? ( + + ) : ( + [No Data Entered] + )} + ); } - }; - return CUSTOM_FIELDS; + + return ( + <> + + {ErrorIcon} + + ); + } }; -export default customFields; +export default CUSTOM_FIELDS; diff --git a/app/containers/Applications/ApplicationDetailsCardItem.tsx b/app/containers/Applications/ApplicationDetailsCardItem.tsx index dfbe06455c..879a3b4a89 100644 --- a/app/containers/Applications/ApplicationDetailsCardItem.tsx +++ b/app/containers/Applications/ApplicationDetailsCardItem.tsx @@ -1,7 +1,7 @@ -import React, {useState, useMemo} from 'react'; +import React, {useState, useEffect} from 'react'; import {Button, Card, Collapse, Col, Row} from 'react-bootstrap'; import {createFragmentContainer, graphql} from 'react-relay'; -import JsonSchemaForm, {FieldProps, AjvError} from '@rjsf/core'; +import JsonSchemaForm, {AjvError, ErrorSchema} from '@rjsf/core'; import {FormJson} from 'next-env'; import {ApplicationDetailsCardItem_formResult} from '__generated__/ApplicationDetailsCardItem_formResult.graphql'; import {ApplicationDetailsCardItem_query} from '__generated__/ApplicationDetailsCardItem_query.graphql'; @@ -49,20 +49,27 @@ export const ApplicationDetailsCardItemComponent: React.FunctionComponent const {formJson} = formJsonByFormId; const {schema, uiSchema, customFormats} = formJson as FormJson; + const formIdPrefix = `${formJsonByFormId.name + .toLowerCase() + .replace(' ', '-')}`; + const transformErrors = (errors: AjvError[]) => { return customTransformErrors(errors, formJson); }; + const handleChange = (_formData, errorSchema: ErrorSchema) => { + if (setHasErrors) { + if (errorSchema?.__errors) { + setHasErrors(true); + } else { + setHasErrors(false); + } + } + }; + // Expands or collapses the form_result card const [isOpen, setIsOpen] = useState(false); - // The array of paths to each difference between diffFrom result & diffTo result (each path matches up with idSchema) - const diffPathArray = []; - // The array of differences - const diffArray = []; - // If the diffFrom result is empty, there is no path. This flag gives us control over what to show in the diff in that case. - let previousIsEmpty = false; - let diffTo; // Select the correct form result to diff to by matching formJson slugs diffToResults.forEach((result) => { @@ -70,7 +77,16 @@ export const ApplicationDetailsCardItemComponent: React.FunctionComponent diffTo = result; }); - useMemo(() => { + const [idDiffMap, setIdDiffMap] = useState< + Record + >({}); + const [formData, setFormData] = useState( + review ? diffTo.node.formResult : formResult.formResult + ); + // The array of paths to each difference between diffFrom result & diffTo result (each path matches up with idSchema) + useEffect(() => { + const newIdDiffMap: Record = {}; + let newFormData = Array.isArray(formData) ? [...formData] : {...formData}; if (diffFromResults && showDiff) { let diffFrom; // Select the correct form result to diff from by matching formJson slugs @@ -87,67 +103,67 @@ export const ApplicationDetailsCardItemComponent: React.FunctionComponent const rhs = diffTo.node.formResult; const differences = diff(lhs, rhs); - // These are the default values for empty form results. If the form results for the diffFrom are empty, set the previousIsEmpty flag - if ( - JSON.stringify(diffFrom) === '[]' || - JSON.stringify(diffFrom) === '{}' || - JSON.stringify(diffFrom) === '[{}]' - ) { - previousIsEmpty = true; - } else if (differences) { + if (differences) { // Populate the diffPathArray and diffArray differences.forEach((difference) => { - if (difference.path) { - diffPathArray.push(difference.path.join('_')); - diffArray.push(difference.lhs); + if (difference.kind === 'A') { + // Array differences + const arrayElementPath = [ + formIdPrefix, + ...(difference.path ?? []), + difference.index + ].join('_'); + if (difference.item.kind === 'N') { + // Added an element to the array + Object.keys(difference.item.rhs).forEach((key) => { + newIdDiffMap[`${arrayElementPath}_${key}`] = { + lhs: null, + rhs: difference.item.rhs[key] + }; + }); + } else if (difference.item.kind === 'D') { + console.log(difference); + const deletedItem = {}; + // Deleted an element from the array + Object.keys(difference.item.lhs).forEach((key) => { + deletedItem[key] = null; + newIdDiffMap[`${arrayElementPath}_${key}`] = { + lhs: difference.item.lhs[key], + rhs: null + }; + }); + if (Array.isArray(newFormData) && !difference.path) { + newFormData = [ + ...newFormData.slice(0, difference.index), + deletedItem, + ...newFormData.slice(difference.index) + ]; + } + } + } else if (difference.path) { + const {lhs, rhs} = difference; + newIdDiffMap[[formIdPrefix, ...difference.path].join('_')] = { + lhs, + rhs + }; } }); } } + setFormData(newFormData); + setIdDiffMap(newIdDiffMap); }, [ - diffArray, - diffPathArray, formResult.formJsonByFormId.slug, formResult.formResult, diffFromResults, showDiff ]); - // Some formData values are numbers that map to enums, this function uses the number values to return the enum names stored in the schema - const handleEnums = (props, isCurrent, prevValue) => { - if (props.schema.enum && props.schema.enumNames) { - // TODO: needs a fix on jsonschema types (missing enumNames) - const enumIndex = isCurrent - ? props.schema.enum.indexOf(props.formData) - : props.schema.enum.indexOf(prevValue); - if (enumIndex === -1 && isCurrent) return props.formData; - if (enumIndex === -1 && !isCurrent) return '[No Data Entered]'; - return props.schema.enumNames[enumIndex]; - } - - if (isCurrent) return props.formData; - - return prevValue; - }; - - const CUSTOM_FIELDS: Record< - string, - React.FunctionComponent - > = customFields( - showDiff, - diffPathArray, - diffArray, - handleEnums, - previousIsEmpty, - setHasErrors - ); const classTag = formJsonByFormId.slug; // Override submit button for each form with an empty fragment // eslint-disable-next-line react/jsx-no-useless-fragment const buttonOverride = <>; - const formIdPrefix = `${formJsonByFormId.name - .toLowerCase() - .replace(' ', '-')}`; + return ( ArrayFieldTemplate={SummaryFormArrayFieldTemplate} FieldTemplate={SummaryFormFieldTemplate} showErrorList={false} - fields={CUSTOM_FIELDS} + fields={customFields} customFormats={customFormats} transformErrors={transformErrors} schema={schema} idPrefix={formIdPrefix} uiSchema={uiSchema} + onChange={handleChange} ObjectFieldTemplate={FormObjectFieldTemplate} - formData={review ? diffTo.node.formResult : formResult.formResult} + formData={formData} formContext={{ query, showDiff, - diffPathArray, - diffArray, - previousIsEmpty + idDiffMap }} > {buttonOverride} @@ -229,9 +244,7 @@ export const ApplicationDetailsCardItemComponent: React.FunctionComponent export default createFragmentContainer(ApplicationDetailsCardItemComponent, { formResult: graphql` fragment ApplicationDetailsCardItem_formResult on FormResult { - id formResult - versionNumber formJsonByFormId { name slug diff --git a/schema/data/dev/form_result.sql b/schema/data/dev/form_result.sql index d90c784e09..6f7c062863 100644 --- a/schema/data/dev/form_result.sql +++ b/schema/data/dev/form_result.sql @@ -61,10 +61,16 @@ begin; "emissionCategoryRowId": 1 }, { - "fuelRowId": 11, + "fuelRowId": 10, "quantity": 40120, "fuelUnits": "kL", "emissionCategoryRowId": 3 + }, + { + "fuelRowId": 12, + "quantity": 40, + "fuelUnits": "kL", + "emissionCategoryRowId": 2 } ]'), ('[ @@ -76,15 +82,6 @@ begin; "requiresEmissionAllocation": true, "requiresProductAmount": true, "isCiipProduct": true - }, - { - "productAmount": 12000, - "productRowId": 26, - "productUnits": "t", - "productEmissions": 1300, - "requiresEmissionAllocation": true, - "requiresProductAmount": true, - "isCiipProduct": true } ]');