Skip to content

Commit a1441bc

Browse files
committed
new way to display rule graph
1 parent eafdb56 commit a1441bc

2 files changed

Lines changed: 106 additions & 56 deletions

File tree

src/ui/language/translations.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ const translations = {
235235
yes: "Yes",
236236
no: "No",
237237
description: "This table shows the conditions that need to be met in order to be eligible for this benefit. The conditions are based on the data fields you provided in your profile.",
238+
explanationRules: "Each condition is either an 'and' or an 'or' condition. An 'and' condition means that all of the conditions must be met, while an 'or' condition means that at least one of the conditions must be met. These can also be combined.",
239+
orExplanationTitle: "Or-conditions",
240+
orExplanationText: "At least one of the following conditions must be met",
241+
andExplanationTitle: "And-Conditions",
242+
andExplanationText: "All of the following conditions must be met",
238243
andConditions: "All of the following conditions must be met",
239244
orConditions: "At least one of the following conditions must be met",
240245
datafieldQuestion: "Datafield question",
@@ -516,12 +521,17 @@ const translations = {
516521
yes: "Ja",
517522
no: "Nein",
518523
description: "Diese Tabelle zeigt die Bedingungen, die erfüllt sein müssen, um für diese Leistung anspruchsberechtigt zu sein. Die Bedingungen basieren auf den Datenfeldern, die du in deinem Profil angegeben hast.",
519-
andConditions: "Alle der folgenden Bedingungen müssen erfüllt sein",
520-
orConditions: "Eine der folgenden Bedingungen muss erfüllt sein",
524+
explanationRules: "Die Voraussetzungen für einen Anspruch können aus einfachen Bedingungen bestehen, die immer erfüllt sein müssen. Daneben gibt es Oder- und abhängige Bedingungen. Diese Bedingungen können auch kombiniert werden.",
525+
orExplanationTitle: "Oder-Bedingungen",
526+
orExplanationText: "Hier reicht es, eine von mehreren Möglichkeiten zu erfüllen.",
527+
andExplanationTitle: "Und-Bedingungen",
528+
andExplanationText: "Hier müssen alle Bedingungen erfüllt sein.",
529+
andConditions: "Alle Bedingungen müssen erfüllt sein",
530+
orConditions: "Mindestens eine Bedingung muss erfüllt sein",
521531
datafieldQuestion: "Die Frage",
522532
actualValue: "Die folgende Antwort wurde gegeben",
523533
responseMustNotBe: "Die Antwort darf nicht sein",
524-
responseNeedsToBe: "Die Antwort muss sein",
534+
responseNeedsToBe: "Benötigte Antwort",
525535
responseMustBeLessThan: "Die Antwort muss kleiner sein als",
526536
responseMustBeGreaterThanOrEqualTo: "Die Antwort muss gleich oder größer sein als",
527537
responseMustNotBeOneOf: "Die Antwort darf nicht eine der folgenden sein",

src/ui/screens/benefit-page/components/RecursiveRulesTable.js

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,104 @@
1-
import React from 'react';
2-
import { Typography, Box } from '@mui/material';
3-
import { VBox } from '@/ui/shared-components/LayoutBoxes';
1+
import React, { useState } from 'react';
42
import theme from '@/theme';
53
import { RuleSwitch } from './RuleTypes';
64
import { useMetadataStore } from '@/ui/storage/zustand';
5+
import { Typography, IconButton, Collapse, Box } from '@mui/material';
6+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
7+
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
8+
import { HBox, VBox } from '@/ui/shared-components/LayoutBoxes';
9+
import { useStore } from "@/ui/shared-components/ViewportUpdater";
10+
11+
function CollapsibleGroup({ label, children, status }) {
12+
const isDesktop = useStore((state) => state.isDesktop);
13+
const [open, setOpen] = useState(false);
14+
15+
const bg =
16+
status === 'ok' ? 'secondary.light' :
17+
status === 'violation' ? 'error.light' :
18+
'white.main';
19+
20+
return (
21+
<VBox
22+
gap={2}
23+
sx={{
24+
backgroundColor: open ? 'transparent' : bg,
25+
border: '1px solid #ccc',
26+
padding: isDesktop ? 1 : 0.125,
27+
borderRadius: theme.shape.borderRadius,
28+
boxShadow: '0px 1px 2px rgba(0, 0, 0, 0.25)',
29+
}}
30+
>
31+
<Box display="flex" alignItems="center" justifyContent="space-between">
32+
<HBox sx={{ padding: 2, justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
33+
<Typography variant="body1">{label}</Typography>
34+
<IconButton size="small" onClick={() => setOpen(prev => !prev)}>
35+
{open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
36+
</IconButton>
37+
</HBox>
38+
</Box>
39+
{
40+
open && (
41+
<Collapse in={open}>
42+
<VBox>
43+
{children}
44+
</VBox>
45+
</Collapse>
46+
)
47+
}
48+
</VBox>
49+
);
50+
}
751

852
function renderNode(
953
node,
1054
parentField = {},
1155
metadata,
56+
t,
1257
isNegated = false,
13-
t
58+
groupType = null,
1459
) {
1560
if (!node) return null;
1661
const nodeType = node.constructor?.nodeType;
1762

18-
// ROOT → skip wrapper, first AND skip label
19-
if (nodeType === 'NodeROOT') {
20-
return renderNode(node.children?.[0], '', metadata, false, t);
21-
}
22-
23-
if (nodeType === 'NodeCLASS') {
24-
return renderNode(node.children?.[0], '', metadata, false, t);
25-
}
26-
2763
// NOT → flip negation on child
2864
if (nodeType === 'NodeNOT') {
2965
const child = node.children?.[0];
30-
return renderNode(child, parentField, metadata, !isNegated, t);
66+
return renderNode(child, parentField, metadata, t, !isNegated);
3167
}
3268

3369
// AND / OR group
34-
if (nodeType === 'NodeAND' || nodeType === 'NodeOR') {
70+
if (['NodeROOT', 'NodeCLASS', 'NodeAND', 'NodeOR'].includes(nodeType)) {
3571
const children = node.children || [];
3672
const dataFields = children.filter(c => c.constructor?.nodeType === 'NodeDATAFIELD' && c.children?.length > 0);
3773
const andGroups = children.filter(c => c.constructor?.nodeType === 'NodeAND');
3874
const orGroups = children.filter(c => c.constructor?.nodeType === 'NodeOR');
75+
const classGroups = children.filter(c => c.constructor?.nodeType === 'NodeCLASS');
3976

40-
if (dataFields.length + andGroups.length + orGroups.length === 0) {
77+
if (dataFields.length + andGroups.length + orGroups.length + classGroups.length === 0) {
4178
return null;
4279
}
4380

44-
const mappedLabel = {
45-
'NodeAND': t('app.benefitPage.rulesTable.andConditions'),
46-
'NodeOR': t('app.benefitPage.rulesTable.orConditions'),
47-
}
48-
4981
return (
50-
<VBox
51-
sx={{
52-
position: 'relative',
53-
pl: 2,
54-
'&::before': {
55-
content: '""',
56-
position: 'absolute',
57-
top: '0rem',
58-
bottom: 0,
59-
left: 0,
60-
borderLeft: '2px dashed #ccc',
61-
},
62-
}}
63-
>
64-
<Typography variant="h6">
65-
{mappedLabel[nodeType]}
66-
</Typography>
67-
82+
<VBox>
6883
{dataFields.map((df, i) => (
69-
<Box key={i} sx={{ mb: 1 }}>
70-
{renderNode(df, parentField, metadata, isNegated, t)}
71-
</Box>
84+
<VBox key={i} sx={{ mb: 1 }}>
85+
{renderNode(df, parentField, metadata, t, isNegated)}
86+
</VBox>
87+
))}
88+
{classGroups.map((grp, i) => (
89+
<VBox key={i} sx={{ mb: 1 }}>
90+
{renderNode(grp, parentField, metadata, t, isNegated, 'NodeCLASS')}
91+
</VBox>
7292
))}
7393
{andGroups.map((grp, i) => (
74-
<Box key={i} sx={{ mb: 1 }}>
75-
{renderNode(grp, parentField, metadata, isNegated, t)}
76-
</Box>
94+
<CollapsibleGroup key={i} label={t('app.benefitPage.rulesTable.andConditions')} status={grp.status}>
95+
{renderNode(grp, parentField, metadata, t, isNegated, 'NodeAND')}
96+
</CollapsibleGroup>
7797
))}
7898
{orGroups.map((grp, i) => (
79-
<Box key={i}>
80-
{renderNode(grp, parentField, metadata, isNegated, t)}
81-
</Box>
99+
<CollapsibleGroup key={i} label={t('app.benefitPage.rulesTable.orConditions')} status={grp.status}>
100+
{renderNode(grp, parentField, metadata, t, isNegated, 'NodeOR')}
101+
</CollapsibleGroup>
82102
))}
83103
</VBox>
84104
);
@@ -135,8 +155,8 @@ function renderNode(
135155
r,
136156
parentField,
137157
metadata,
138-
isNegated,
139-
t
158+
t,
159+
isNegated
140160
)}
141161
</VBox>
142162
))}
@@ -162,6 +182,7 @@ function renderNode(
162182
export default function RecursiveRulesTable({ graphRoot, t }) {
163183
const metadata = useMetadataStore(state => state.metadata);
164184
if (!graphRoot) return null;
185+
165186
return (
166187
<VBox
167188
sx={{
@@ -171,15 +192,34 @@ export default function RecursiveRulesTable({ graphRoot, t }) {
171192
borderRadius: theme.shape.borderRadius,
172193
}}
173194
>
174-
<VBox>
195+
<VBox sx={{ gap: 2 }}>
175196
<Typography variant="h2" sx={{ fontWeight: '400', wordBreak: "break-word" }}>
176197
{t("app.benefitPage.rulesTable.header")}
177198
</Typography>
178-
<Typography variant="body1" sx={{ marginTop: 1 }}>
199+
<Typography variant="body1">
179200
{t("app.benefitPage.rulesTable.description")}
180201
</Typography>
202+
<Typography variant="body1">
203+
{t("app.benefitPage.rulesTable.explanationRules")}
204+
</Typography>
205+
<VBox>
206+
<Typography variant="body1" sx={{ fontWeight: 'bold'}}>
207+
{t("app.benefitPage.rulesTable.andExplanationTitle")}
208+
</Typography>
209+
<Typography variant="body1">
210+
{t("app.benefitPage.rulesTable.andExplanationText")}
211+
</Typography>
212+
</VBox>
213+
<VBox>
214+
<Typography variant="body1" sx={{ fontWeight: 'bold'}}>
215+
{t("app.benefitPage.rulesTable.orExplanationTitle")}
216+
</Typography>
217+
<Typography variant="body1">
218+
{t("app.benefitPage.rulesTable.orExplanationText")}
219+
</Typography>
220+
</VBox>
181221
</VBox>
182-
{renderNode(graphRoot, '', metadata?.['ff:hasDF'], false, t)}
222+
{renderNode(graphRoot, '', metadata?.['ff:hasDF'], t)}
183223
</VBox>
184224
);
185225
}

0 commit comments

Comments
 (0)