Skip to content

Commit

Permalink
feat: Updates Repeating Sets, Pages and Rules to work together on the…
Browse files Browse the repository at this point in the history
… Review page and form output (#4186)

* Initial commit
* Start on Review page formatting
* Updates build form to handle pages and repeating sets
* Updates build form types and simplifies logic a bit

---------

Co-authored-by: Vivian Nobrega <43480681+ShadeWyrm@users.noreply.github.com>
  • Loading branch information
thiessenp-cds and ShadeWyrm authored Aug 16, 2024
1 parent 9806cbe commit 5422d6c
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ export function buildFormDataObject(formRecord: PublicFormRecord, values: Respon

function _handleDynamicRowTypeIfNeeded(
element: FormElement,
value: Response
value: Response | Responses[]
): [string, string | FileInputResponse][] {
if (element.type === FormElementTypes.dynamicRow) {
if (element.type === FormElementTypes.dynamicRow && Array.isArray(value)) {
if (element.properties.subElements === undefined) return [];

const responses = value as Responses[];
const subElements = element.properties.subElements;

Expand Down Expand Up @@ -66,15 +65,15 @@ function _handleDynamicRowTypeIfNeeded(
// `flat` function is needed because we use a `map` in a `map`.
.flat()
);
} else {
const result = _handleFormDataType(element, value);
return result ? [result] : [];
}

const result = _handleFormDataType(element, value);
return result ? [result] : [];
}

function _handleFormDataType(
element: FormElement,
value: Response
value: Response | Responses[]
): [string, string | FileInputResponse] | undefined {
switch (element.type) {
case FormElementTypes.textField:
Expand Down
132 changes: 105 additions & 27 deletions components/clientComponents/forms/Review/Review.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button } from "@clientComponents/globals";
import { Theme } from "@clientComponents/globals/Buttons/themes";
import { useFocusIt } from "@lib/hooks/useFocusIt";
import { useGCFormsContext } from "@lib/hooks/useGCFormContext";
import { FormElement } from "@lib/types";
import { FileInputResponse, FormElement, FormElementTypes } from "@lib/types";
import { Language } from "@lib/types/form-builder-types";
import { getLocalizedProperty } from "@lib/utils";
import {
Expand All @@ -14,31 +14,39 @@ import {
getElementIdsAsNumber,
Group,
} from "@lib/formContext";
import { randomId } from "@lib/client/clientHelpers";

type ReviewItem = {
id: string;
name: string;
title: string;
elements: {
elementId: number;
title: string;
values: string;
}[];
elements: Element[];
};

function getFormElementValues(elementName: number | null, formValues: void | FormValues) {
const value = formValues[elementName as keyof typeof formValues];
if (Array.isArray(value)) {
return (value as Array<string>).join(", ") || "-";
}
return value || "-";
}
type Element = {
title: string;
values: string | FileInputResponse | Element[];
};

function getFormElementTitle(formElementId: number, formElements: FormElement[], lang: string) {
const formElement = formElements.find((item) => item.id === formElementId);
return formElement
? (formElement.properties?.[getLocalizedProperty("title", lang)] as string)
: "-";
function formatElementValues(values: Element["values"]) {
if (!values) {
return "-";
}
// Case of a File upload
if ((values as FileInputResponse).based64EncodedFile !== undefined) {
const file = values as FileInputResponse;
if (!file.name || !file.size || file.size < 0) {
return "-";
}
const fileSizeInMB = (file.size / 1024 / 1024).toFixed(2);
return `${file.name} (${fileSizeInMB} MB)`;
}
// Case of an array like element e.g. checkbox
if (Array.isArray(values)) {
return values.join(", ") || "-";
}
// Case of a single value element e.g. input
return String(values);
}

function getReviewItemElements(
Expand All @@ -52,14 +60,48 @@ function getReviewItemElements(
const shownElementIds = getElementIdsAsNumber(
filterValuesForShownElements(groupElements, shownFormElements)
);
const result = shownElementIds.map((elementId) => {
return shownElementIds.map((elementId) => {
const element = formElements.find((item) => item.id === elementId);
let resultValues: string | Element[] = formatElementValues(
formValues[elementId as unknown as keyof typeof formValues] as Element["values"]
);
// Handle any Sub Elements. Note Sub Elements = Dynamic Rows = Repeating Sets
if (element?.type === FormElementTypes.dynamicRow) {
resultValues = [];
const parentId = element.id;
const parentTitle = element.properties?.[getLocalizedProperty("title", lang)];
const subElements = element.properties?.subElements;
// Use FormValues as the source of truth and for each FormValue value, map the related
// subElement title to the FormValue value
const subElementValues = (formValues[parentId] as string[]).map(
(valueRows, valueRowsIndex) => {
const subElementsTitle = `${parentTitle} - ${valueRowsIndex + 1}`;
const valueRowsAsArray = Object.keys(valueRows).map(
(key) => valueRows[key as keyof typeof valueRows]
);
// Match the FormValue index to the subElement index to assign the Element title
const titlesMappedToValues = valueRowsAsArray.map((formValue, valueRowIndex) => {
return {
title: subElements?.[valueRowIndex].properties?.[getLocalizedProperty("title", lang)],
values: formValue,
};
});
return {
title: subElementsTitle,
values: titlesMappedToValues,
} as Element;
}
);
resultValues.push({
title: parentTitle as string,
values: subElementValues,
});
}
return {
elementId,
title: getFormElementTitle(elementId, formElements, lang),
values: getFormElementValues(elementId, formValues),
title: (element?.properties?.[getLocalizedProperty("title", lang)] as string) || "-",
values: resultValues,
};
});
return result;
}

export const Review = ({ language }: { language: Language }): React.ReactElement => {
Expand All @@ -78,7 +120,7 @@ export const Review = ({ language }: { language: Language }): React.ReactElement
return groupHistory
.filter((key) => key !== "review") // Removed to avoid showing as a group
.map((groupId) => {
const group: Group = groups[groupId as keyof typeof groups];
const group: Group = groups[groupId as keyof typeof groups] || {};
return {
id: groupId,
name: group.name,
Expand Down Expand Up @@ -140,17 +182,53 @@ const QuestionsAnswersList = ({ reviewItem }: { reviewItem: ReviewItem }): React
<dl className="my-10">
{Array.isArray(reviewItem.elements) &&
reviewItem.elements.map((reviewElement) => {
if (Array.isArray(reviewElement.values)) {
return <SubElements key={randomId()} elements={reviewElement.values as Element[]} />;
}
return (
<div key={reviewElement.elementId} className="mb-8">
<dt className="mb-2 font-bold">{reviewElement.title}</dt>
<dd>{reviewElement.values}</dd>
<div key={randomId()} className="mb-8">
<dt className="font-bold mb-2">{reviewElement.title}</dt>
<dd>{formatElementValues(reviewElement.values)}</dd>
</div>
);
})}
</dl>
);
};

// Handle formatting Sub Elements. Note Sub Elements = Dynamic Rows = Repeating Sets.
const SubElements = ({ elements }: { elements: Element[] }) => {
return elements?.map((subElementItem) => {
return (subElementItem.values as Element[])?.map((element) => {
if (Array.isArray(element.values)) {
const dlId = randomId();
// Create a nested DL for each Sub Element list
return (
<dl key={dlId} aria-labelledby={dlId} className="my-10">
<h4 className="italic" id={dlId}>
{element.title}
</h4>
{(element.values as Element[]).map((elementValues) => {
return (
<div key={randomId()} className="mb-2">
<dt className="font-bold mb-2">{elementValues.title}</dt>
<dd>{formatElementValues(elementValues.values)}</dd>
</div>
);
})}
</dl>
);
}
return (
<div key={randomId()} className="mb-2">
<dt className="font-bold mb-2">{element.title}</dt>
<dd>{formatElementValues(element.values)}</dd>
</div>
);
});
});
};

const EditButton = ({
reviewItem,
theme,
Expand Down

0 comments on commit 5422d6c

Please sign in to comment.