Skip to content

Commit de6adf8

Browse files
committed
Bugfix - order conditions in next attribute according to order their fields appear on the page to help avoid page skipping weirdness
1 parent de23a20 commit de6adf8

File tree

2 files changed

+107
-5
lines changed

2 files changed

+107
-5
lines changed

designer/client/AdapterDesigner.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {AdapterVisualisation} from "./components/Visualization";
88
import {AdapterFormDefinition} from "@communitiesuk/model";
99
import {AdapterDataContext} from "./context/AdapterDataContext";
1010
import AdapterMenu from "./components/menu/AdapterMenu";
11+
import {sortConditionsBySourceFieldOrder} from "./utils/conditionOrdering";
1112

1213
interface Props {
1314
match?: any;
@@ -54,13 +55,14 @@ export default class AdapterDesigner extends Component<Props, State> {
5455
this.setState({flyoutCount: --currentCount}, callback());
5556
};
5657

57-
save = async (toUpdate, callback = () => {
58-
}) => {
58+
save = async (toUpdate, callback = () => {}) => {
5959
try {
60-
await this.designerApi.save(this.id, toUpdate);
60+
const sortedData = sortConditionsBySourceFieldOrder(toUpdate);
61+
62+
await this.designerApi.save(this.id, sortedData);
6163
// @ts-ignore
62-
this.setState({data: toUpdate, updatedAt: new Date().toLocaleTimeString(), error: undefined,}, callback());
63-
return toUpdate;
64+
this.setState({data: sortedData, updatedAt: new Date().toLocaleTimeString(), error: undefined,}, callback());
65+
return sortedData;
6466
} catch (e) {
6567
//@ts-ignore
6668
this.setState({error: e.message});
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { AdapterFormDefinition } from "@communitiesuk/model";
2+
3+
export function sortConditionsBySourceFieldOrder(data: AdapterFormDefinition): AdapterFormDefinition {
4+
const sortedData = { ...data };
5+
6+
sortedData.pages = data.pages.map(page => {
7+
if (!page.next || page.next.length === 0) {
8+
return page;
9+
}
10+
11+
// Find the source page that these conditions reference
12+
const sourcePagePath = getConditionSourcePage(page.next, data);
13+
if (!sourcePagePath) {
14+
return page; // No conditions to sort
15+
}
16+
17+
const sourcePage = data.pages.find(p => p.path === sourcePagePath);
18+
if (!sourcePage) {
19+
return page;
20+
}
21+
22+
// Get the order of options from the source field (checkboxes, etc.)
23+
const fieldOptionOrder = getFieldOptionOrder(sourcePage, data);
24+
25+
// Sort conditions based on the order of options in the source field
26+
const sortedNext = [...page.next].sort((a, b) => {
27+
// Non-conditional links first
28+
if (!a.condition && !b.condition) return 0;
29+
if (!a.condition) return -1;
30+
if (!b.condition) return 1;
31+
32+
// Get the option values these conditions check for
33+
const aOptionValue = getConditionOptionValue(a.condition, data);
34+
const bOptionValue = getConditionOptionValue(b.condition, data);
35+
36+
// Sort by the order they appear in the source field
37+
const aIndex = fieldOptionOrder.indexOf(aOptionValue);
38+
const bIndex = fieldOptionOrder.indexOf(bOptionValue);
39+
40+
// If both found, sort by their field order
41+
if (aIndex !== -1 && bIndex !== -1) {
42+
return aIndex - bIndex;
43+
}
44+
45+
// Fallback to alphabetical for any not found
46+
return a.path.localeCompare(b.path);
47+
});
48+
49+
return {
50+
...page,
51+
next: sortedNext
52+
};
53+
});
54+
55+
return sortedData;
56+
}
57+
58+
function getConditionSourcePage(nextLinks: any[], data: AdapterFormDefinition): string | null {
59+
// Find the first conditional link and determine what page it's checking
60+
const conditionalLink = nextLinks.find(link => link.condition);
61+
if (!conditionalLink) return null;
62+
63+
const condition = data.conditions?.find(c => c.name === conditionalLink.condition);
64+
if (!condition?.value?.conditions?.[0]?.field?.name) return null;
65+
66+
// Extract page path from field name (e.g., "FabDefault.ZRERCV" -> page with component "ZRERCV")
67+
const fieldName = condition.value.conditions[0].field.name;
68+
const componentName = fieldName.includes('.') ?
69+
fieldName.split('.').pop() : fieldName;
70+
71+
// Find the page that contains this component
72+
return data.pages.find(page =>
73+
page.components?.some(component => component.name === componentName)
74+
)?.path || null;
75+
}
76+
77+
function getFieldOptionOrder(page: any, data: AdapterFormDefinition): string[] {
78+
// Find the checkboxes field (or other multi-select field)
79+
const checkboxField = page.components?.find(component =>
80+
component.type === 'CheckboxesField' ||
81+
component.type === 'RadiosField'
82+
);
83+
84+
if (!checkboxField) return [];
85+
86+
// If it uses a list reference, get the list
87+
if (checkboxField.values?.type === 'listRef') {
88+
const listName = checkboxField.values.list || checkboxField.list;
89+
const list = data.lists?.find(l => l.name === listName);
90+
return list?.items?.map(item => item.value) || [];
91+
}
92+
93+
// If it has inline items
94+
return checkboxField.items?.map(item => item.value) || [];
95+
}
96+
97+
function getConditionOptionValue(conditionName: string, data: AdapterFormDefinition): string {
98+
const condition = data.conditions?.find(c => c.name === conditionName);
99+
return condition?.value?.conditions?.[0]?.value?.value || '';
100+
}

0 commit comments

Comments
 (0)