Skip to content

Commit c97b8c6

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 c97b8c6

File tree

2 files changed

+108
-5
lines changed

2 files changed

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

0 commit comments

Comments
 (0)