Skip to content

Commit 0002482

Browse files
committed
start printing node graph
1 parent 962e579 commit 0002482

5 files changed

Lines changed: 193 additions & 18 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
"framer-motion": "^12.4.7",
2222
"haversine-distance": "^1.2.3",
2323
"lodash": "^4.17.21",
24+
"mermaid": "^11.6.0",
2425
"react": "^18.3.1",
2526
"react-dom": "^18.3.1",
2627
"react-markdown": "^9.0.1",
2728
"react-router-dom": "^6.23.1",
2829
"react-scripts": "5.0.1",
2930
"reactflow": "^11.11.4",
31+
"svg-pan-zoom": "^3.6.2",
3032
"swiper": "^11.2.4",
3133
"web-vitals": "^2.1.4",
3234
"zustand": "^4.5.2"

src/ui/screens/benefit-page/BenefitPageScreen.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@ import BenefitPageExampleList from './components/BenefitPageExampleList';
1010
import BenefitPageRules from "./components/BenefitPageRules";
1111
import BenefitPageHeader from "./components/BenefitPageHeader";
1212
import AppScreenWrapperContainer from '@/ui/shared-components/app-screen-wrapper/AppScreenWrapperContainer';
13-
import ReactFlow, { Background, Controls } from 'reactflow';
14-
import 'reactflow/dist/style.css';
13+
import RecursiveRulesTable from './components/RecursiveRulesTable';
1514

1615
const BenefitPageScreen = ({
1716
t,
1817
id,
1918
benefitPageData,
2019
validatedStatus,
2120
categoryTitles,
22-
flowGraph
21+
matchingGraph
2322
}) => {
2423

2524
return (
@@ -32,13 +31,7 @@ const BenefitPageScreen = ({
3231
flexDirection: "column",
3332
width: '100%'
3433
}}>
35-
<div style={{ width: '100%', height: '100vh', background: '#fff' }}>
36-
<ReactFlow nodes={flowGraph?.nodes} edges={flowGraph?.edges} fitView>
37-
<Background color="#eee" gap={16} />
38-
<Controls />
39-
</ReactFlow>
40-
</div>
41-
34+
<RecursiveRulesTable graphRoot={matchingGraph?.root} />
4235
<VBox
4336
sx={{
4437
backgroundColor: 'white.main',

src/ui/screens/benefit-page/BenefitPageScreenContainer.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,14 @@ const BenefitPageScreenContainer = () => {
3535
fetchMatchingReport();
3636
}, [id, activeUserId, language]);
3737

38-
const flowGraph = useMemo(() => {
39-
if (matchingGraph) {
40-
console.log("Building flow graph for matchingGraph:", matchingGraph);
41-
return buildFlow(matchingGraph.root);
42-
}
43-
}, [matchingGraph]);
44-
4538
return (
4639
<BenefitPageScreen
4740
t={t}
4841
id={id}
4942
benefitPageData={benefitPageData}
5043
validatedStatus={validatedStatus}
5144
categoryTitles={categoryTitles}
52-
flowGraph={flowGraph}
45+
matchingGraph={matchingGraph}
5346
/>
5447
);
5548
};
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React from 'react';
2+
import { Typography, Box } from '@mui/material';
3+
import { VBox } from '@/ui/shared-components/LayoutBoxes';
4+
import theme from '@/theme';
5+
import {
6+
InConstraint,
7+
MaxExclusiveConstraint,
8+
MinInclusiveConstraint
9+
} from './RuleTypes';
10+
11+
const typeLabelMap = {
12+
NodeROOT: 'Requirements',
13+
NodeAND: 'AND',
14+
NodeOR: 'OR',
15+
};
16+
17+
function renderNode(node, skipAndWrapper = false, parentField = '') {
18+
if (!node) return null;
19+
const t = node.constructor?.name;
20+
21+
if (t === 'NodeROOT') {
22+
return renderNode(node.children?.[0], true, '');
23+
}
24+
25+
if (t === 'NodeDATAFIELD') {
26+
const rules = (node.children || []).filter(
27+
c => c.constructor?.name === 'NodeRULE' &&
28+
c.type !== 'sh:MinCountConstraintComponent'
29+
);
30+
if (rules.length === 0) return null;
31+
32+
return (
33+
<VBox
34+
gap={1}
35+
sx={{
36+
border: '1px solid',
37+
borderColor: 'black.light',
38+
padding: 2,
39+
borderRadius: theme.shape.borderRadius,
40+
}}
41+
>
42+
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>
43+
{node.path}
44+
</Typography>
45+
{rules.map((rule, i) => (
46+
<VBox key={i} sx={{ pl: 2 }}>
47+
{renderNode(rule, false, node.path)}
48+
</VBox>
49+
))}
50+
</VBox>
51+
);
52+
}
53+
54+
if (t === 'NodeNOT') {
55+
return null;
56+
}
57+
58+
if (t === 'NodeRULE') {
59+
if (node.type === 'sh:MinCountConstraintComponent') return null;
60+
61+
switch (node.type) {
62+
case 'sh:MinInclusiveConstraintComponent':
63+
return <MinInclusiveConstraint field={parentField} value={node.value} />;
64+
case 'sh:MaxExclusiveConstraintComponent':
65+
return <MaxExclusiveConstraint field={parentField} value={node.value} />;
66+
case 'sh:InConstraintComponent':
67+
return <InConstraint field={parentField} value={node.value} />;
68+
default: {
69+
const valStr =
70+
typeof node.value === 'boolean'
71+
? node.value.toString()
72+
: Array.isArray(node.value)
73+
? node.value.join(', ')
74+
: node.value == null
75+
? '<no value>'
76+
: node.value;
77+
return (
78+
<Typography>
79+
{parentField} needs to satisfy {node.type.replace('sh:', '')}{valStr}
80+
</Typography>
81+
);
82+
}
83+
}
84+
}
85+
86+
if (t === 'NodeAND' || t === 'NodeOR') {
87+
const children = node.children || [];
88+
89+
const dataFields = children.filter(c =>
90+
c.constructor?.name === 'NodeDATAFIELD' &&
91+
(c.children || []).some(
92+
r => r.constructor?.name === 'NodeRULE' &&
93+
r.type !== 'sh:MinCountConstraintComponent'
94+
)
95+
);
96+
const andGroups = children.filter(c => c.constructor?.name === 'NodeAND');
97+
const orGroups = children.filter(c => c.constructor?.name === 'NodeOR');
98+
99+
if (dataFields.length + andGroups.length + orGroups.length === 0) {
100+
return null;
101+
}
102+
103+
const label = skipAndWrapper && t === 'NodeAND'
104+
? null
105+
: typeLabelMap[t];
106+
107+
return (
108+
<VBox gap={2}>
109+
{label && <Typography variant="body1">{label}</Typography>}
110+
111+
{dataFields.map((df, i) => (
112+
<Box key={`df-${i}`} sx={{ pl: 2 }}>
113+
{renderNode(df, false, parentField)}
114+
</Box>
115+
))}
116+
117+
{andGroups.map((grp, i) => (
118+
<Box key={`and-${i}`} sx={{ pl: 2 }}>
119+
{renderNode(grp, false, parentField)}
120+
</Box>
121+
))}
122+
123+
{orGroups.map((grp, i) => (
124+
<Box key={`or-${i}`} sx={{ pl: 2 }}>
125+
{renderNode(grp, false, parentField)}
126+
</Box>
127+
))}
128+
</VBox>
129+
);
130+
}
131+
132+
return null;
133+
}
134+
135+
export default function RecursiveRulesTable({ graphRoot }) {
136+
if (!graphRoot) return null;
137+
return (
138+
<VBox
139+
sx={{
140+
backgroundColor: 'white.main',
141+
padding: '32px',
142+
borderRadius: theme.shape.borderRadius,
143+
}}
144+
>
145+
{renderNode(graphRoot)}
146+
</VBox>
147+
);
148+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import { Typography } from '@mui/material';
3+
4+
export const MinInclusiveConstraint = ({ field, value }) => (
5+
<Typography variant="body1">
6+
{field} needs to be greater than or equal to {value}
7+
</Typography>
8+
);
9+
10+
export const MaxExclusiveConstraint = ({ field, value }) => (
11+
<Typography variant="body1">
12+
{field} needs to be less than {value}
13+
</Typography>
14+
);
15+
16+
export const InConstraint = ({ field, value }) => {
17+
if (!Array.isArray(value) || value.length === 0) {
18+
return (
19+
<Typography variant="body1">
20+
{field} needs to be one of the following values: (no values provided)
21+
</Typography>
22+
);
23+
}
24+
25+
if (value.length === 1) {
26+
const singleVal = value[0];
27+
return (
28+
<Typography variant="body1">
29+
{field} needs to be {String(singleVal)}
30+
</Typography>
31+
);
32+
}
33+
34+
return (
35+
<Typography variant="body1">
36+
{field} needs to be one of the following values: {value.join(', ')}
37+
</Typography>
38+
);
39+
};

0 commit comments

Comments
 (0)