Skip to content

Commit

Permalink
feat: add flattened passport structure for List items that is compati…
Browse files Browse the repository at this point in the history
…ble with Calculate (#3249)
  • Loading branch information
jessicamcinchak authored Jun 6, 2024
1 parent 9f0f1f6 commit 300de77
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
20 changes: 19 additions & 1 deletion editor.planx.uk/src/@planx/components/List/Public/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
Schema,
UserData,
} from "../model";
import { flatten } from "../utils";

interface ListContextValue {
schema: Schema;
Expand Down Expand Up @@ -131,7 +132,24 @@ export const ListProvider: React.FC<ListProviderProps> = (props) => {
userData: getInitialValues(),
},
onSubmit: (values) => {
handleSubmit?.(makeData(props, values.userData));
// defaultPassportData is used when coming "back"
const defaultPassportData = makeData(props, values.userData)?.["data"];

// flattenedPassportData makes individual list items compatible with Calculate components
const flattenedPassportData = flatten(defaultPassportData);

// basic example of general summary stats we can add onSubmit
const summaries = {
[`${props.fn}.count`]: defaultPassportData[`${props.fn}`].length,
};

handleSubmit?.({
data: {
...defaultPassportData,
...flattenedPassportData,
...summaries,
},
});
},
validateOnBlur: false,
validateOnChange: false,
Expand Down
12 changes: 12 additions & 0 deletions editor.planx.uk/src/@planx/components/List/Public/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ const mockPayload = {
size: "Medium",
},
],
"mockFn.one.age": 10,
"mockFn.one.cuteness": "Very",
"mockFn.one.email": "[email protected]",
"mockFn.one.name": "Richard Parker",
"mockFn.one.size": "Medium",
"mockFn.two.age": 10,
"mockFn.two.cuteness": "Very",
"mockFn.two.email": "[email protected]",
"mockFn.two.name": "Richard Parker",
"mockFn.two.size": "Medium",
"mockFn.count": 2,
},
};

Expand Down Expand Up @@ -355,6 +366,7 @@ describe("Form validation and error handling", () => {
test.todo("number fields use existing validation schemas");
test.todo("question fields use validation schema");
test.todo("unique constraints are enforced on question where this is set");
test.todo("optional fields can be empty when saving an item");
test.todo("an error displays if the minimum number of items is not met");
test.todo("an error displays if the maximum number of items is exceeded");
test.todo(
Expand Down
87 changes: 87 additions & 0 deletions editor.planx.uk/src/@planx/components/List/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Flattens nested object so we can output passport variables like `{listFn}.{itemIndexAsText}.{fieldFn}`
// Adapted from https://gist.github.com/penguinboy/762197
export function flatten<T extends Record<string, any>>(
object: T,
path: string | null = null,
separator = ".",
): T {
return Object.keys(object).reduce((acc: T, key: string): T => {
const value = object[key];

// If the key is a whole number, convert to text before setting newPath
// eg because Calculate/MathJS cannot automate passport variables with number segments
if (/^-?\d+$/.test(key)) {
key = convertNumberToText(parseInt(key) + 1);
}

const newPath = [path, key].filter(Boolean).join(separator);

const isObject = [
typeof value === "object",
value !== null,
!(Array.isArray(value) && value.length === 0),
].every(Boolean);

return isObject
? { ...acc, ...flatten(value, newPath, separator) }
: { ...acc, [newPath]: value };
}, {} as T);
}

// Convert a whole number up to 99 to a spelled-out word (eg 34 => 'thirtyfour')
// Adapted from https://stackoverflow.com/questions/5529934/javascript-numbers-to-words
const ones = [
"",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
];
const tens = [
"",
"",
"twenty",
"thirty",
"forty",
"fifty",
"sixty",
"seventy",
"eighty",
"ninety",
];
const teens = [
"ten",
"eleven",
"twelve",
"thirteen",
"fourteen",
"fifteen",
"sixteen",
"seventeen",
"eighteen",
"nineteen",
];

function convertTens(num: number): string {
if (num < 10) {
return ones[num];
} else if (num >= 10 && num < 20) {
return teens[num - 10];
} else {
// format as compound string - eg "thirtyfour" instead of "thirty four"
return tens[Math.floor(num / 10)] + ones[num % 10];
}
}

function convertNumberToText(num: number): string {
if (num == 0) {
return "zero";
} else {
return convertTens(num);
}
}

0 comments on commit 300de77

Please sign in to comment.