@epcare/esm-report-builder 0.1.1-pre.39 → 0.1.1-pre.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/538.js +1 -0
- package/dist/538.js.map +1 -0
- package/dist/729.js +1 -1
- package/dist/main.js +1 -1
- package/dist/openmrs-esm-report-builder.js.buildmanifest.json +26 -26
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/components/indicators/indicator-search-select.component.tsx +0 -12
- package/src/components/indicators/indicators-page.component.tsx +2 -3
- package/src/components/linelist/builder/column-category-selector.component.tsx +162 -9
- package/src/components/linelist/builder/column-category-selector.scss +148 -3
- package/src/components/linelist/builder/column-debug-panel.component.tsx +197 -0
- package/src/components/linelist/builder/column-debug-panel.scss +141 -0
- package/src/components/linelist/builder/encounter-diagnosis-column-modal.component.tsx +43 -22
- package/src/components/linelist/builder/linelist-builder-workspace.component.tsx +1 -23
- package/src/components/linelist/builder/observation-column-modal.component.tsx +125 -40
- package/src/components/linelist/modals/linelist-report-builder-modal.component.tsx +78 -8
- package/src/components/linelist/pages/linelist-report-editor-page.component.tsx +0 -2
- package/src/components/linelist/shared/json-preview.component.tsx +5 -2
- package/src/components/report-sections/hooks/useSectionEditorState.ts +4 -7
- package/src/components/shared/concept-selector.component.tsx +19 -2
- package/src/hooks/theme/useMambaTables.ts +0 -2
- package/src/types/linelist-types.ts +19 -1
- package/src/utils/config-enhancer.ts +62 -0
- package/dist/378.js +0 -1
- package/dist/378.js.map +0 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column Debug Panel Component
|
|
3
|
+
*
|
|
4
|
+
* Displays detailed column information for debugging
|
|
5
|
+
* Shows ID, position, data definition config, source info, etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Button, Tag, CodeSnippet, Accordion, AccordionItem } from '@carbon/react';
|
|
10
|
+
import { Settings, Information } from '@carbon/react/icons';
|
|
11
|
+
import type { LinelistColumnDraft } from '../../../types/linelist-types';
|
|
12
|
+
import styles from './column-debug-panel.scss';
|
|
13
|
+
|
|
14
|
+
type Props = {
|
|
15
|
+
column: LinelistColumnDraft;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function ColumnDebugPanel({ column, onClose }: Props) {
|
|
20
|
+
return (
|
|
21
|
+
<div className={styles.debugPanel}>
|
|
22
|
+
<div className={styles.header}>
|
|
23
|
+
<div className={styles.headerTitle}>
|
|
24
|
+
<Settings size={16} className={styles.icon} />
|
|
25
|
+
<span>Column Debug Info</span>
|
|
26
|
+
</div>
|
|
27
|
+
<Button kind="ghost" size="sm" onClick={onClose}>
|
|
28
|
+
Close
|
|
29
|
+
</Button>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<Accordion className={styles.accordion}>
|
|
33
|
+
{/* Basic Info */}
|
|
34
|
+
<AccordionItem title="Basic Information" open={true}>
|
|
35
|
+
<div className={styles.section}>
|
|
36
|
+
<DebugRow label="Column ID" value={column.id} type="uuid" />
|
|
37
|
+
<DebugRow label="Name" value={column.name} />
|
|
38
|
+
<DebugRow label="Description" value={column.description} />
|
|
39
|
+
<DebugRow label="Position (sortOrder)" value={column.sortOrder?.toString()} type="number" />
|
|
40
|
+
<DebugRow label="Data Type" value={column.dataDefinitionType} type="tag" />
|
|
41
|
+
</div>
|
|
42
|
+
</AccordionItem>
|
|
43
|
+
|
|
44
|
+
{/* Source Info */}
|
|
45
|
+
<AccordionItem title="Source Information" open={true}>
|
|
46
|
+
<div className={styles.section}>
|
|
47
|
+
{column.source ? (
|
|
48
|
+
<>
|
|
49
|
+
<DebugRow label="Data Source UUID" value={column.source.dataSourceUuid} type="uuid" />
|
|
50
|
+
<DebugRow label="Data Source Name" value={column.source.dataSourceUuid} />
|
|
51
|
+
<DebugRow label="Table" value={column.source.table} type="code" />
|
|
52
|
+
<DebugRow label="Field" value={column.source.field} type="code" />
|
|
53
|
+
<DebugRow label="Field Type" value={column.source.fieldType} type="tag" />
|
|
54
|
+
{column.source.conceptUuid && (
|
|
55
|
+
<DebugRow label="Concept UUID" value={column.source.conceptUuid} type="uuid" />
|
|
56
|
+
)}
|
|
57
|
+
{column.source.attributeTypeUuid && (
|
|
58
|
+
<DebugRow label="Attribute Type UUID" value={column.source.attributeTypeUuid} type="uuid" />
|
|
59
|
+
)}
|
|
60
|
+
{column.source.identifierTypeUuid && (
|
|
61
|
+
<DebugRow label="Identifier Type UUID" value={column.source.identifierTypeUuid} type="uuid" />
|
|
62
|
+
)}
|
|
63
|
+
</>
|
|
64
|
+
) : (
|
|
65
|
+
<div className={styles.warning}>
|
|
66
|
+
<Information size={16} />
|
|
67
|
+
<span>No source information available</span>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</AccordionItem>
|
|
72
|
+
|
|
73
|
+
{/* Data Definition Config */}
|
|
74
|
+
<AccordionItem title="Data Definition Config" open={false}>
|
|
75
|
+
<div className={styles.section}>
|
|
76
|
+
{column.dataDefinitionConfig ? (
|
|
77
|
+
<pre className={styles.code}>{JSON.stringify(column.dataDefinitionConfig, null, 2)}</pre>
|
|
78
|
+
) : (
|
|
79
|
+
<div className={styles.warning}>
|
|
80
|
+
<Information size={16} />
|
|
81
|
+
<span>No data definition config available</span>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</AccordionItem>
|
|
86
|
+
|
|
87
|
+
{/* Repeat Resolution */}
|
|
88
|
+
<AccordionItem title="Repeat Resolution" open={false}>
|
|
89
|
+
<div className={styles.section}>
|
|
90
|
+
{column.repeatResolution ? (
|
|
91
|
+
<>
|
|
92
|
+
<DebugRow label="Strategy" value={column.repeatResolution.strategy} />
|
|
93
|
+
<DebugRow label="Order By" value={column.repeatResolution.orderBy} />
|
|
94
|
+
<DebugRow label="Restrict to Period" value={column.repeatResolution.restrictToPeriod?.toString()} type="boolean" />
|
|
95
|
+
<DebugRow label="Ignore Voided" value={column.repeatResolution.ignoreVoided?.toString()} type="boolean" />
|
|
96
|
+
</>
|
|
97
|
+
) : (
|
|
98
|
+
<div className={styles.info}>
|
|
99
|
+
<Information size={16} />
|
|
100
|
+
<span>No repeat resolution configured</span>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</AccordionItem>
|
|
105
|
+
|
|
106
|
+
{/* Display Settings */}
|
|
107
|
+
<AccordionItem title="Display Settings" open={false}>
|
|
108
|
+
<div className={styles.section}>
|
|
109
|
+
{column.display ? (
|
|
110
|
+
<>
|
|
111
|
+
<DebugRow label="Width" value={column.display.width?.toString()} type="number" />
|
|
112
|
+
<DebugRow label="Align" value={column.display.align} />
|
|
113
|
+
<DebugRow label="Sortable" value={column.display.sortable?.toString()} type="boolean" />
|
|
114
|
+
<DebugRow label="Filterable" value={column.display.filterable?.toString()} type="boolean" />
|
|
115
|
+
<DebugRow label="Format" value={column.display.format} />
|
|
116
|
+
</>
|
|
117
|
+
) : (
|
|
118
|
+
<div className={styles.info}>
|
|
119
|
+
<Information size={16} />
|
|
120
|
+
<span>No display settings configured</span>
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
</AccordionItem>
|
|
125
|
+
|
|
126
|
+
{/* Addition Info */}
|
|
127
|
+
<AccordionItem title="Addition Info" open={false}>
|
|
128
|
+
<div className={styles.section}>
|
|
129
|
+
{column.additionInfo ? (
|
|
130
|
+
<>
|
|
131
|
+
<DebugRow label="Added Via" value={column.additionInfo.addedVia} />
|
|
132
|
+
<DebugRow label="Added At" value={column.additionInfo.addedAt} />
|
|
133
|
+
<DebugRow label="Order Added" value={column.additionInfo.orderAdded?.toString()} type="number" />
|
|
134
|
+
</>
|
|
135
|
+
) : (
|
|
136
|
+
<div className={styles.info}>
|
|
137
|
+
<Information size={16} />
|
|
138
|
+
<span>No addition info available</span>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
</AccordionItem>
|
|
143
|
+
|
|
144
|
+
{/* Raw JSON */}
|
|
145
|
+
<AccordionItem title="Raw Column JSON" open={false}>
|
|
146
|
+
<div className={styles.section}>
|
|
147
|
+
<CodeSnippet type="multi" feedback="Copied!">
|
|
148
|
+
{JSON.stringify(column, null, 2)}
|
|
149
|
+
</CodeSnippet>
|
|
150
|
+
</div>
|
|
151
|
+
</AccordionItem>
|
|
152
|
+
</Accordion>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function DebugRow({ label, value, type = 'text' }: { label: string; value: any; type?: string }) {
|
|
158
|
+
if (value === undefined || value === null || value === '') {
|
|
159
|
+
return (
|
|
160
|
+
<div className={styles.row}>
|
|
161
|
+
<span className={styles.label}>{label}:</span>
|
|
162
|
+
<span className={styles.valueEmpty}>(not set)</span>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let displayValue = value;
|
|
168
|
+
|
|
169
|
+
switch (type) {
|
|
170
|
+
case 'uuid':
|
|
171
|
+
case 'code':
|
|
172
|
+
displayValue = <code className={styles.codeInline}>{value}</code>;
|
|
173
|
+
break;
|
|
174
|
+
case 'tag':
|
|
175
|
+
displayValue = <Tag size="sm" type="cool-gray">{value}</Tag>;
|
|
176
|
+
break;
|
|
177
|
+
case 'boolean':
|
|
178
|
+
displayValue = (
|
|
179
|
+
<Tag size="sm" type={value === 'true' ? 'green' : 'red'}>
|
|
180
|
+
{value === 'true' ? 'Yes' : 'No'}
|
|
181
|
+
</Tag>
|
|
182
|
+
);
|
|
183
|
+
break;
|
|
184
|
+
case 'number':
|
|
185
|
+
displayValue = <Tag size="sm" type="blue">{value}</Tag>;
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
displayValue = value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div className={styles.row}>
|
|
193
|
+
<span className={styles.label}>{label}:</span>
|
|
194
|
+
<span className={styles.value}>{displayValue}</span>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
.debugPanel {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 50%;
|
|
4
|
+
left: 50%;
|
|
5
|
+
transform: translate(-50%, -50%);
|
|
6
|
+
width: 90%;
|
|
7
|
+
max-width: 600px;
|
|
8
|
+
max-height: 80vh;
|
|
9
|
+
background-color: #f4f4f4;
|
|
10
|
+
border: 2px solid #0f62fe;
|
|
11
|
+
border-radius: 8px;
|
|
12
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
13
|
+
z-index: 9999;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
|
|
17
|
+
.header {
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: space-between;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: 1rem;
|
|
22
|
+
background-color: #0f62fe;
|
|
23
|
+
color: white;
|
|
24
|
+
border-radius: 6px 6px 0 0;
|
|
25
|
+
|
|
26
|
+
.headerTitle {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 0.5rem;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.icon {
|
|
34
|
+
// Icon styling
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.accordion {
|
|
39
|
+
flex: 1;
|
|
40
|
+
overflow-y: auto;
|
|
41
|
+
padding: 1rem;
|
|
42
|
+
|
|
43
|
+
// Scrollbar styling
|
|
44
|
+
&::-webkit-scrollbar {
|
|
45
|
+
width: 8px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&::-webkit-scrollbar-track {
|
|
49
|
+
background: #e0e0e0;
|
|
50
|
+
border-radius: 4px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&::-webkit-scrollbar-thumb {
|
|
54
|
+
background: #8a8a8a;
|
|
55
|
+
border-radius: 4px;
|
|
56
|
+
|
|
57
|
+
&:hover {
|
|
58
|
+
background: #6f6f6f;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.section {
|
|
64
|
+
padding: 0.75rem 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.row {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: flex-start;
|
|
70
|
+
gap: 0.75rem;
|
|
71
|
+
padding: 0.375rem 0;
|
|
72
|
+
border-bottom: 1px solid #e0e0e0;
|
|
73
|
+
|
|
74
|
+
&:last-child {
|
|
75
|
+
border-bottom: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.label {
|
|
79
|
+
font-weight: 500;
|
|
80
|
+
color: #525252;
|
|
81
|
+
min-width: 150px;
|
|
82
|
+
font-size: 0.875rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.value {
|
|
86
|
+
color: #161616;
|
|
87
|
+
font-size: 0.875rem;
|
|
88
|
+
word-break: break-all;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.valueEmpty {
|
|
92
|
+
color: #a8a8a8;
|
|
93
|
+
font-style: italic;
|
|
94
|
+
font-size: 0.875rem;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.codeInline {
|
|
99
|
+
background-color: #e0e0e0;
|
|
100
|
+
padding: 0.125rem 0.375rem;
|
|
101
|
+
border-radius: 3px;
|
|
102
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
|
|
103
|
+
font-size: 0.8rem;
|
|
104
|
+
color: #0f62fe;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.code {
|
|
108
|
+
background-color: #1a1a1a;
|
|
109
|
+
color: #f4f4f4;
|
|
110
|
+
padding: 1rem;
|
|
111
|
+
border-radius: 4px;
|
|
112
|
+
overflow-x: auto;
|
|
113
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', monospace;
|
|
114
|
+
font-size: 0.8rem;
|
|
115
|
+
line-height: 1.5;
|
|
116
|
+
max-height: 300px;
|
|
117
|
+
overflow-y: auto;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.warning {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
gap: 0.5rem;
|
|
124
|
+
padding: 0.75rem;
|
|
125
|
+
background-color: #fff8e1;
|
|
126
|
+
border-left: 3px solid #f1c21b;
|
|
127
|
+
border-radius: 4px;
|
|
128
|
+
color: #525252;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.info {
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 0.5rem;
|
|
135
|
+
padding: 0.75rem;
|
|
136
|
+
background-color: #e5f1ff;
|
|
137
|
+
border-left: 3px solid #0f62fe;
|
|
138
|
+
border-radius: 4px;
|
|
139
|
+
color: #525252;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -29,6 +29,8 @@ type Props = {
|
|
|
29
29
|
onAddColumn: (column: LinelistColumnDraft) => void;
|
|
30
30
|
/** Existing columns to check for duplicates */
|
|
31
31
|
existingColumns?: LinelistColumnDraft[];
|
|
32
|
+
/** Column to edit (if provided, modal is in edit mode) */
|
|
33
|
+
editingColumn?: LinelistColumnDraft | null;
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
type StrategyOption = 'ALL_VALUES' | 'LATEST' | 'FIRST_WITHIN_PERIOD' | 'LAST_WITHIN_PERIOD';
|
|
@@ -52,6 +54,7 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
52
54
|
onClose,
|
|
53
55
|
onAddColumn,
|
|
54
56
|
existingColumns = [],
|
|
57
|
+
editingColumn = null,
|
|
55
58
|
}) => {
|
|
56
59
|
const [conceptSelectorKey, setConceptSelectorKey] = useState(0);
|
|
57
60
|
const [selectedConcept, setSelectedConcept] = useState<ConceptSummary | undefined>();
|
|
@@ -61,15 +64,6 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
61
64
|
const [confirmedOnly, setConfirmedOnly] = useState(true);
|
|
62
65
|
const [returnDisplay, setReturnDisplay] = useState(true);
|
|
63
66
|
|
|
64
|
-
/**
|
|
65
|
-
* Reset form state when modal opens/closes
|
|
66
|
-
*/
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (open) {
|
|
69
|
-
setConceptSelectorKey((k) => k + 1);
|
|
70
|
-
}
|
|
71
|
-
}, [open]);
|
|
72
|
-
|
|
73
67
|
/**
|
|
74
68
|
* Reset form state
|
|
75
69
|
*/
|
|
@@ -82,6 +76,33 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
82
76
|
setReturnDisplay(true);
|
|
83
77
|
}, []);
|
|
84
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Reset form state when modal opens/closes
|
|
81
|
+
* Also populate form when editing existing column
|
|
82
|
+
*/
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (open) {
|
|
85
|
+
setConceptSelectorKey((k) => k + 1);
|
|
86
|
+
if (editingColumn) {
|
|
87
|
+
// Populate form with existing column data
|
|
88
|
+
const config = editingColumn.dataDefinitionConfig || {};
|
|
89
|
+
setSelectedConcept(editingColumn.source?.conceptUuid ? {
|
|
90
|
+
uuid: editingColumn.source.conceptUuid,
|
|
91
|
+
display: config.conceptName || editingColumn.name,
|
|
92
|
+
datatype: { name: editingColumn.source?.fieldType || 'TEXT' },
|
|
93
|
+
} as ConceptSummary : undefined);
|
|
94
|
+
setColumnName(editingColumn.name || '');
|
|
95
|
+
setStrategy(config.strategy || 'LAST_WITHIN_PERIOD');
|
|
96
|
+
setRank(config.rank || 'ANY');
|
|
97
|
+
setConfirmedOnly(config.confirmedOnly !== false);
|
|
98
|
+
setReturnDisplay(config.returnDisplay !== false);
|
|
99
|
+
} else {
|
|
100
|
+
// Reset for new column creation
|
|
101
|
+
resetForm();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, [open, editingColumn, resetForm]);
|
|
105
|
+
|
|
85
106
|
/**
|
|
86
107
|
* Handle modal close
|
|
87
108
|
*/
|
|
@@ -108,9 +129,9 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
108
129
|
// Allow empty concept selection to capture ALL diagnoses
|
|
109
130
|
const finalColumnName = columnName?.trim() || (selectedConcept?.display || 'Diagnoses');
|
|
110
131
|
|
|
111
|
-
// Check for duplicate column names
|
|
132
|
+
// Check for duplicate column names (exclude current column when editing)
|
|
112
133
|
const duplicateName = existingColumns.some(col =>
|
|
113
|
-
col.name.toLowerCase() === finalColumnName.toLowerCase()
|
|
134
|
+
col.id !== editingColumn?.id && col.name.toLowerCase() === finalColumnName.toLowerCase()
|
|
114
135
|
);
|
|
115
136
|
if (duplicateName) {
|
|
116
137
|
// TODO: Show error notification
|
|
@@ -120,13 +141,13 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
120
141
|
const now = new Date().toISOString();
|
|
121
142
|
|
|
122
143
|
const newColumn: LinelistColumnDraft = {
|
|
123
|
-
id: `col-${now}-${selectedConcept?.uuid || 'all-diagnoses'}`,
|
|
144
|
+
id: editingColumn?.id || `col-${now}-${selectedConcept?.uuid || 'all-diagnoses'}`,
|
|
124
145
|
name: finalColumnName,
|
|
125
|
-
description: selectedConcept ? `Diagnosis: ${selectedConcept.display}` : 'All encounter diagnoses',
|
|
146
|
+
description: editingColumn?.description || (selectedConcept ? `Diagnosis: ${selectedConcept.display}` : 'All encounter diagnoses'),
|
|
126
147
|
source: {
|
|
127
148
|
dataSourceUuid: 'encounter_diagnoses',
|
|
128
149
|
dataSourceName: 'Encounter Diagnoses',
|
|
129
|
-
table: '
|
|
150
|
+
table: 'encounter_diagnoses',
|
|
130
151
|
field: selectedConcept?.uuid || '*',
|
|
131
152
|
fieldType: 'TEXT',
|
|
132
153
|
conceptUuid: selectedConcept?.uuid,
|
|
@@ -141,18 +162,18 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
141
162
|
returnDisplay,
|
|
142
163
|
},
|
|
143
164
|
additionInfo: {
|
|
144
|
-
addedVia: 'VISUAL_SELECTOR',
|
|
145
|
-
addedAt: now,
|
|
146
|
-
orderAdded: existingColumns.length,
|
|
165
|
+
addedVia: editingColumn?.additionInfo?.addedVia || 'VISUAL_SELECTOR',
|
|
166
|
+
addedAt: editingColumn?.additionInfo?.addedAt || now,
|
|
167
|
+
orderAdded: editingColumn?.additionInfo?.orderAdded || existingColumns.length,
|
|
147
168
|
},
|
|
148
|
-
display: {
|
|
169
|
+
display: editingColumn?.display || {
|
|
149
170
|
width: 150,
|
|
150
171
|
align: 'left',
|
|
151
172
|
sortable: true,
|
|
152
173
|
filterable: true,
|
|
153
174
|
format: 'coded',
|
|
154
175
|
},
|
|
155
|
-
sortOrder: existingColumns.length,
|
|
176
|
+
sortOrder: editingColumn?.sortOrder ?? existingColumns.length,
|
|
156
177
|
repeatResolution: {
|
|
157
178
|
strategy: strategy === 'ALL_VALUES' ? 'NONE' : 'LATEST',
|
|
158
179
|
orderBy: 'encounter_date',
|
|
@@ -163,7 +184,7 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
163
184
|
|
|
164
185
|
onAddColumn(newColumn);
|
|
165
186
|
handleClose();
|
|
166
|
-
}, [selectedConcept, columnName, strategy, rank, confirmedOnly, returnDisplay, existingColumns, onAddColumn, handleClose]);
|
|
187
|
+
}, [selectedConcept, columnName, strategy, rank, confirmedOnly, returnDisplay, existingColumns, onAddColumn, handleClose, editingColumn]);
|
|
167
188
|
|
|
168
189
|
/**
|
|
169
190
|
* Check if form is valid
|
|
@@ -174,11 +195,11 @@ const EncounterDiagnosisColumnModal: React.FC<Props> = ({
|
|
|
174
195
|
<Modal
|
|
175
196
|
open={open}
|
|
176
197
|
onRequestClose={handleClose}
|
|
177
|
-
modalHeading="Add Diagnosis Column"
|
|
198
|
+
modalHeading={editingColumn ? "Edit Diagnosis Column" : "Add Diagnosis Column"}
|
|
178
199
|
modalLabel="Data Catalogue"
|
|
179
200
|
size="md"
|
|
180
201
|
preventCloseOnClickOutside
|
|
181
|
-
primaryButtonText="Add Column"
|
|
202
|
+
primaryButtonText={editingColumn ? "Save Changes" : "Add Column"}
|
|
182
203
|
primaryButtonDisabled={!isFormValid}
|
|
183
204
|
onRequestSubmit={handleSubmit}
|
|
184
205
|
secondaryButtonText="Cancel"
|
|
@@ -47,7 +47,7 @@ import type {
|
|
|
47
47
|
import type { EtlStructure } from '../../../types/etl/etl-types';
|
|
48
48
|
import {
|
|
49
49
|
draftToConfig,
|
|
50
|
-
isLinelistDraftReadyToCompile,
|
|
50
|
+
// isLinelistDraftReadyToCompile,
|
|
51
51
|
// isLinelistDraftReadyToPublish,
|
|
52
52
|
createEmptyDraft,
|
|
53
53
|
// isLinelistDraftValid,
|
|
@@ -82,13 +82,10 @@ type Props = {};
|
|
|
82
82
|
* This function populates the draft with all builder details from the saved report
|
|
83
83
|
*/
|
|
84
84
|
function reportToDraft(report: LinelistReportDto): LinelistReportDraft {
|
|
85
|
-
console.log('🔴 reportToDraft called with:', report);
|
|
86
|
-
|
|
87
85
|
let config: LinelistReportDefinitionConfig | null = null;
|
|
88
86
|
|
|
89
87
|
try {
|
|
90
88
|
config = report.configJson ? JSON.parse(report.configJson) : null;
|
|
91
|
-
console.log('Parsed config:', config);
|
|
92
89
|
} catch (e) {
|
|
93
90
|
console.error('Failed to parse report config:', e);
|
|
94
91
|
}
|
|
@@ -357,7 +354,6 @@ function reportToDraft(report: LinelistReportDto): LinelistReportDraft {
|
|
|
357
354
|
let indicatorRules: PopulationDefinition['indicatorRules'] = undefined;
|
|
358
355
|
|
|
359
356
|
const configIndicatorRules = builderState?.indicatorRules || (config as any).indicatorRules;
|
|
360
|
-
console.log('🔴 configIndicatorRules:', configIndicatorRules);
|
|
361
357
|
if (Array.isArray(configIndicatorRules) && configIndicatorRules.length > 0) {
|
|
362
358
|
indicatorRules = configIndicatorRules.map((rule: any, idx: number) => ({
|
|
363
359
|
id: rule.id || `rule-${idx}`,
|
|
@@ -367,18 +363,15 @@ function reportToDraft(report: LinelistReportDto): LinelistReportDraft {
|
|
|
367
363
|
logicalOperator: rule.logicalOperator || 'AND',
|
|
368
364
|
negate: rule.negate || false,
|
|
369
365
|
}));
|
|
370
|
-
console.log('🔴 Loaded indicatorRules from config:', indicatorRules);
|
|
371
366
|
}
|
|
372
367
|
|
|
373
368
|
// Fall back to metaJson for buildMethod and indicator rules if not in config
|
|
374
369
|
if (report.metaJson && (!indicatorRules || buildMethod === 'SQL_BUILDER')) {
|
|
375
370
|
try {
|
|
376
371
|
const meta = JSON.parse(report.metaJson);
|
|
377
|
-
console.log('🔴 Parsed metaJson:', meta);
|
|
378
372
|
// Try to get buildMethod from metaJson as fallback
|
|
379
373
|
if (meta.buildMethod && (meta.buildMethod === 'INDICATOR_BASED' || meta.buildMethod === 'VISUAL_FILTER' || meta.buildMethod === 'SQL_BUILDER')) {
|
|
380
374
|
buildMethod = meta.buildMethod;
|
|
381
|
-
console.log('🔴 Using buildMethod from metaJson:', buildMethod);
|
|
382
375
|
}
|
|
383
376
|
// Also try to get indicator rules from metaJson as fallback
|
|
384
377
|
if (!indicatorRules && Array.isArray(meta.indicatorRules) && meta.indicatorRules.length > 0) {
|
|
@@ -390,7 +383,6 @@ function reportToDraft(report: LinelistReportDto): LinelistReportDraft {
|
|
|
390
383
|
logicalOperator: rule.logicalOperator || 'AND',
|
|
391
384
|
negate: rule.negate || false,
|
|
392
385
|
}));
|
|
393
|
-
console.log('🔴 Loaded indicatorRules from metaJson:', indicatorRules);
|
|
394
386
|
}
|
|
395
387
|
} catch (e) {
|
|
396
388
|
console.warn('Failed to parse metaJson:', e);
|
|
@@ -437,9 +429,6 @@ function reportToDraft(report: LinelistReportDto): LinelistReportDraft {
|
|
|
437
429
|
},
|
|
438
430
|
],
|
|
439
431
|
};
|
|
440
|
-
console.log('🔴 Final population state:', population);
|
|
441
|
-
console.log('🔴 population.indicatorRules:', population.indicatorRules);
|
|
442
|
-
console.log('🔴 population.buildMethod:', population.buildMethod);
|
|
443
432
|
|
|
444
433
|
// === CREATE THE DRAFT ===
|
|
445
434
|
// Build population sources from primary datasource
|
|
@@ -503,7 +492,6 @@ function reportToDraft(report: LinelistReportDto): LinelistReportDraft {
|
|
|
503
492
|
},
|
|
504
493
|
};
|
|
505
494
|
|
|
506
|
-
console.log('🟢 Returning draft with builder details:', draft);
|
|
507
495
|
return draft;
|
|
508
496
|
}
|
|
509
497
|
|
|
@@ -581,12 +569,10 @@ const LinelistBuilderWorkspace: React.FC<Props> = () => {
|
|
|
581
569
|
import('../../../resources/linelist/linelist-reports.api')
|
|
582
570
|
.then(({ getLinelistReport }) => getLinelistReport(reportId))
|
|
583
571
|
.then((report: LinelistReportDto) => {
|
|
584
|
-
console.error('🔴 LOADED REPORT:', report?.name, report);
|
|
585
572
|
setInitialReport(report);
|
|
586
573
|
setMode('edit');
|
|
587
574
|
// Convert report to draft
|
|
588
575
|
const draft = reportToDraft(report);
|
|
589
|
-
console.error('🟢 CONVERTED TO DRAFT:', draft);
|
|
590
576
|
setDraft(draft);
|
|
591
577
|
})
|
|
592
578
|
.catch((err) => {
|
|
@@ -1229,14 +1215,6 @@ function getDefaultOperator(fieldType: FilterFieldType): FilterOperator {
|
|
|
1229
1215
|
}
|
|
1230
1216
|
}
|
|
1231
1217
|
|
|
1232
|
-
// Debug: Check what's preventing readiness
|
|
1233
|
-
const validationErrors = validateLinelistDraft(draft);
|
|
1234
|
-
if (Object.keys(validationErrors).length > 0) {
|
|
1235
|
-
console.log('⚠️ Draft validation errors:', validationErrors);
|
|
1236
|
-
}
|
|
1237
|
-
const isReady = isLinelistDraftReadyToCompile(draft);
|
|
1238
|
-
console.log('🔴 Compile button state:', { compiling, hasReportUuid: !!initialReport?.uuid, isReady });
|
|
1239
|
-
|
|
1240
1218
|
if (loading) {
|
|
1241
1219
|
return (
|
|
1242
1220
|
<div className={styles.workspace}>
|