@nnkogift/dhis2-form-utils-devtools 0.1.0-alpha.4 → 0.1.0-alpha.6
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/EffectBadge.d.ts +17 -0
- package/dist/EffectBadge.js +55 -0
- package/dist/EffectBadge.js.map +1 -0
- package/dist/RuleDetailsModal.d.ts +30 -0
- package/dist/RuleDetailsModal.js +403 -0
- package/dist/RuleDetailsModal.js.map +1 -0
- package/dist/RuleDevtoolsScope.d.ts +14 -0
- package/dist/RuleDevtoolsScope.js +25 -0
- package/dist/RuleDevtoolsScope.js.map +1 -0
- package/dist/RuleGraphModal.d.ts +17 -0
- package/dist/RuleGraphModal.js +35 -0
- package/dist/RuleGraphModal.js.map +1 -0
- package/dist/RuleGraphView.d.ts +43 -0
- package/dist/RuleGraphView.js +401 -0
- package/dist/RuleGraphView.js.map +1 -0
- package/dist/RulesPanel.d.ts +19 -0
- package/dist/RulesPanel.js +463 -0
- package/dist/RulesPanel.js.map +1 -0
- package/dist/RulesPanelGraphFallback.d.ts +5 -0
- package/dist/RulesPanelGraphFallback.js +19 -0
- package/dist/RulesPanelGraphFallback.js.map +1 -0
- package/dist/TraceTimeline.d.ts +16 -0
- package/dist/TraceTimeline.js +227 -0
- package/dist/TraceTimeline.js.map +1 -0
- package/dist/attach.d.ts +12 -0
- package/dist/attach.js +14 -0
- package/dist/attach.js.map +1 -0
- package/dist/buildGraph.d.ts +25 -0
- package/dist/buildGraph.js +67 -0
- package/dist/buildGraph.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -0
- package/dist/createLabelLookup.d.ts +25 -0
- package/dist/createLabelLookup.js +81 -0
- package/dist/createLabelLookup.js.map +1 -0
- package/dist/effectStyles.d.ts +28 -0
- package/dist/effectStyles.js +142 -0
- package/dist/effectStyles.js.map +1 -0
- package/dist/formatAgo.d.ts +3 -0
- package/dist/formatAgo.js +25 -0
- package/dist/formatAgo.js.map +1 -0
- package/dist/formatRuleActionSummary.d.ts +33 -0
- package/dist/formatRuleActionSummary.js +61 -0
- package/dist/formatRuleActionSummary.js.map +1 -0
- package/dist/graphLayout.d.ts +48 -0
- package/dist/graphLayout.js +137 -0
- package/dist/graphLayout.js.map +1 -0
- package/dist/graphNodeStyles.d.ts +9 -0
- package/dist/graphNodeStyles.js +22 -0
- package/dist/graphNodeStyles.js.map +1 -0
- package/dist/i18n.d.ts +4 -0
- package/dist/i18n.js +9 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.cjs +1364 -651
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +12 -63
- package/dist/index.d.ts +12 -63
- package/dist/index.js +5 -1669
- package/dist/index.js.map +1 -1
- package/dist/programRuleDetailQuery.d.ts +70 -0
- package/dist/programRuleDetailQuery.js +42 -0
- package/dist/programRuleDetailQuery.js.map +1 -0
- package/dist/resolveProgramRulesList.d.ts +21 -0
- package/dist/resolveProgramRulesList.js +45 -0
- package/dist/resolveProgramRulesList.js.map +1 -0
- package/dist/traceEntry.d.ts +12 -0
- package/dist/traceEntry.js +20 -0
- package/dist/traceEntry.js.map +1 -0
- package/dist/traceStore.d.ts +12 -0
- package/dist/traceStore.js +39 -0
- package/dist/traceStore.js.map +1 -0
- package/dist/useFlipReorder.d.ts +8 -0
- package/dist/useFlipReorder.js +52 -0
- package/dist/useFlipReorder.js.map +1 -0
- package/dist/useProgramRuleDetail.d.ts +17 -0
- package/dist/useProgramRuleDetail.js +30 -0
- package/dist/useProgramRuleDetail.js.map +1 -0
- package/package.json +27 -4
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { IconFullscreen16, SegmentedControl, Button, IconInfo16 } from '@dhis2/ui';
|
|
3
|
+
import { useFormStore, useFormStateContext } from '@nnkogift/dhis2-form-utils-hooks';
|
|
4
|
+
import { lazy, useState, useMemo, useSyncExternalStore, useCallback, Suspense, useRef } from 'react';
|
|
5
|
+
import { buildGraphFromTrace } from './buildGraph.js';
|
|
6
|
+
import { createLabelLookup } from './createLabelLookup.js';
|
|
7
|
+
import { EffectBadge } from './EffectBadge.js';
|
|
8
|
+
import { formatAgo } from './formatAgo.js';
|
|
9
|
+
import { formatRuleActionSummary } from './formatRuleActionSummary.js';
|
|
10
|
+
import { translate } from './i18n.js';
|
|
11
|
+
import { resolveProgramRulesList } from './resolveProgramRulesList.js';
|
|
12
|
+
import { useRuleTraceStore } from './RuleDevtoolsScope.js';
|
|
13
|
+
import { RuleDetailsModal } from './RuleDetailsModal.js';
|
|
14
|
+
import { RulesPanelGraphFallback } from './RulesPanelGraphFallback.js';
|
|
15
|
+
import { TraceTimeline } from './TraceTimeline.js';
|
|
16
|
+
import { getActiveRuleIds, resolveGraphTraceEntry } from './traceEntry.js';
|
|
17
|
+
import { useFlipReorder } from './useFlipReorder.js';
|
|
18
|
+
|
|
19
|
+
const LazyRuleGraphView = lazy(
|
|
20
|
+
() => import('./RuleGraphView').then((module) => ({ default: module.RuleGraphView }))
|
|
21
|
+
);
|
|
22
|
+
const LazyRuleGraphModal = lazy(
|
|
23
|
+
() => import('./RuleGraphModal').then((module) => ({ default: module.RuleGraphModal }))
|
|
24
|
+
);
|
|
25
|
+
function resolveScopeStageId(metadata, activeProgramStageId) {
|
|
26
|
+
if (activeProgramStageId !== void 0) {
|
|
27
|
+
return activeProgramStageId;
|
|
28
|
+
}
|
|
29
|
+
return metadata.formKind === "event" ? metadata.programStageId : null;
|
|
30
|
+
}
|
|
31
|
+
function isRuleInScope(rule, scopeStageId) {
|
|
32
|
+
return rule.programStageId === scopeStageId;
|
|
33
|
+
}
|
|
34
|
+
function resolveAccentClassName(inScope, firing, selected) {
|
|
35
|
+
if (selected) {
|
|
36
|
+
return "bg-dhis2-blue-600";
|
|
37
|
+
}
|
|
38
|
+
if (!inScope) {
|
|
39
|
+
return "bg-transparent";
|
|
40
|
+
}
|
|
41
|
+
return firing ? "bg-dhis2-teal-600" : "bg-dhis2-grey-400";
|
|
42
|
+
}
|
|
43
|
+
function resolveCardStatus(inScope, firing) {
|
|
44
|
+
if (!inScope) {
|
|
45
|
+
return { label: translate("Out of scope"), className: "text-dhis2-grey-500" };
|
|
46
|
+
}
|
|
47
|
+
return firing ? { label: translate("Firing"), className: "text-dhis2-teal-700" } : { label: translate("Idle"), className: "text-dhis2-grey-600" };
|
|
48
|
+
}
|
|
49
|
+
function resolveDetailsStatus(inScope, firing) {
|
|
50
|
+
if (!inScope) {
|
|
51
|
+
return "out-of-scope";
|
|
52
|
+
}
|
|
53
|
+
return firing ? "firing" : "idle";
|
|
54
|
+
}
|
|
55
|
+
function resolveAppliesToCaption(rule, labelLookup) {
|
|
56
|
+
return rule.programStageId === null ? translate("Applies to registration") : translate("Applies to {{stage}}", {
|
|
57
|
+
stage: labelLookup.resolveStageName(rule.programStageId)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function formatActionLabel(action) {
|
|
61
|
+
if (action.targetLabel && action.detail) {
|
|
62
|
+
return `${action.type} \xB7 ${action.targetLabel} = ${action.detail}`;
|
|
63
|
+
}
|
|
64
|
+
if (action.targetLabel) {
|
|
65
|
+
return `${action.type} \xB7 ${action.targetLabel}`;
|
|
66
|
+
}
|
|
67
|
+
return action.type;
|
|
68
|
+
}
|
|
69
|
+
function countObservedRules(entries) {
|
|
70
|
+
const ruleIds = /* @__PURE__ */ new Set();
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
for (const result of entry.ruleResults) {
|
|
73
|
+
ruleIds.add(result.ruleId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return ruleIds.size;
|
|
77
|
+
}
|
|
78
|
+
const PANEL_TAB_KEYS = ["rules", "trace", "graph"];
|
|
79
|
+
const SCOPE_FILTER_OPTIONS = [
|
|
80
|
+
{ label: translate("In scope"), value: "scoped" },
|
|
81
|
+
{ label: translate("All"), value: "all" }
|
|
82
|
+
];
|
|
83
|
+
function resolveTabLabel(key) {
|
|
84
|
+
switch (key) {
|
|
85
|
+
case "rules":
|
|
86
|
+
return translate("Rules");
|
|
87
|
+
case "trace":
|
|
88
|
+
return translate("Trace");
|
|
89
|
+
case "graph":
|
|
90
|
+
return translate("Graph");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function RulesPanel({
|
|
94
|
+
metadata,
|
|
95
|
+
showConditions = true,
|
|
96
|
+
activeProgramStageId
|
|
97
|
+
}) {
|
|
98
|
+
const formStore = useFormStore();
|
|
99
|
+
const { form } = useFormStateContext();
|
|
100
|
+
const traceStore = useRuleTraceStore();
|
|
101
|
+
const [tab, setTab] = useState("rules");
|
|
102
|
+
const [selectedEntryId, setSelectedEntryId] = useState(null);
|
|
103
|
+
const [highlightRuleId, setHighlightRuleId] = useState(null);
|
|
104
|
+
const [graphModalOpen, setGraphModalOpen] = useState(false);
|
|
105
|
+
const [scopeFilter, setScopeFilter] = useState("scoped");
|
|
106
|
+
const [detailsRuleId, setDetailsRuleId] = useState(null);
|
|
107
|
+
const labelLookup = useMemo(() => createLabelLookup(metadata), [metadata]);
|
|
108
|
+
const catalog = useMemo(() => resolveProgramRulesList(metadata), [metadata]);
|
|
109
|
+
const scopeStageId = useMemo(
|
|
110
|
+
() => resolveScopeStageId(metadata, activeProgramStageId),
|
|
111
|
+
[metadata, activeProgramStageId]
|
|
112
|
+
);
|
|
113
|
+
const entries = useSyncExternalStore(
|
|
114
|
+
useCallback((listener) => traceStore.subscribe(listener), [traceStore]),
|
|
115
|
+
useCallback(() => traceStore.getSnapshot(), [traceStore]),
|
|
116
|
+
useCallback(() => traceStore.getSnapshot(), [traceStore])
|
|
117
|
+
);
|
|
118
|
+
const fieldState = useSyncExternalStore(
|
|
119
|
+
useCallback((listener) => formStore.fieldStore.subscribeAll(listener), [formStore]),
|
|
120
|
+
useCallback(() => formStore.fieldStore.getSnapshot(), [formStore]),
|
|
121
|
+
useCallback(() => formStore.fieldStore.getSnapshot(), [formStore])
|
|
122
|
+
);
|
|
123
|
+
const activeRuleIds = useMemo(() => getActiveRuleIds(entries), [entries]);
|
|
124
|
+
const scopedRules = useMemo(
|
|
125
|
+
() => catalog.filter((rule) => isRuleInScope(rule, scopeStageId)),
|
|
126
|
+
[catalog, scopeStageId]
|
|
127
|
+
);
|
|
128
|
+
const firingCount = useMemo(
|
|
129
|
+
() => scopedRules.filter((rule) => activeRuleIds.has(rule.id)).length,
|
|
130
|
+
[scopedRules, activeRuleIds]
|
|
131
|
+
);
|
|
132
|
+
const selectedEntry = useMemo(
|
|
133
|
+
() => entries.find((entry) => entry.id === selectedEntryId) ?? null,
|
|
134
|
+
[entries, selectedEntryId]
|
|
135
|
+
);
|
|
136
|
+
const observedRuleCount = useMemo(() => countObservedRules(entries), [entries]);
|
|
137
|
+
const highlightedRuleName = useMemo(() => {
|
|
138
|
+
if (!highlightRuleId) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return labelLookup.resolveRuleName(highlightRuleId);
|
|
142
|
+
}, [highlightRuleId, labelLookup]);
|
|
143
|
+
const graphHasNodes = useMemo(() => {
|
|
144
|
+
const entry = resolveGraphTraceEntry(entries, selectedEntryId);
|
|
145
|
+
return entry ? buildGraphFromTrace([entry], labelLookup).nodes.length > 0 : false;
|
|
146
|
+
}, [entries, labelLookup, selectedEntryId]);
|
|
147
|
+
const graphSubtitle = useMemo(() => {
|
|
148
|
+
if (selectedEntry && highlightedRuleName) {
|
|
149
|
+
return translate("Highlighting evaluation {{time}} \xB7 Rule: {{name}}", {
|
|
150
|
+
time: formatAgo(selectedEntry.timestamp),
|
|
151
|
+
name: highlightedRuleName
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (selectedEntry) {
|
|
155
|
+
return translate("Highlighting evaluation {{time}}", {
|
|
156
|
+
time: formatAgo(selectedEntry.timestamp)
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (highlightedRuleName) {
|
|
160
|
+
return translate("Rule: {{name}}", { name: highlightedRuleName });
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}, [highlightedRuleName, selectedEntry]);
|
|
164
|
+
const detailsRule = useMemo(
|
|
165
|
+
() => catalog.find((rule) => rule.id === detailsRuleId) ?? null,
|
|
166
|
+
[catalog, detailsRuleId]
|
|
167
|
+
);
|
|
168
|
+
const detailsStatus = detailsRule ? resolveDetailsStatus(
|
|
169
|
+
isRuleInScope(detailsRule, scopeStageId),
|
|
170
|
+
activeRuleIds.has(detailsRule.id)
|
|
171
|
+
) : "idle";
|
|
172
|
+
const detailsProgramStageName = detailsRule ? detailsRule.programStageId === null ? null : labelLookup.resolveStageName(detailsRule.programStageId) : void 0;
|
|
173
|
+
const graphProps = {
|
|
174
|
+
entries,
|
|
175
|
+
fieldState,
|
|
176
|
+
formValues: form.getValues(),
|
|
177
|
+
selectedEntryId,
|
|
178
|
+
highlightRuleId,
|
|
179
|
+
labelLookup
|
|
180
|
+
};
|
|
181
|
+
const expandGraphButton = /* @__PURE__ */ jsx(
|
|
182
|
+
"button",
|
|
183
|
+
{
|
|
184
|
+
type: "button",
|
|
185
|
+
className: "inline-flex size-11 min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded border border-dhis2-grey-300 bg-white text-dhis2-grey-800 hover:bg-dhis2-grey-100 focus-visible:outline-2 focus-visible:outline-dhis2-teal-600 focus-visible:outline-offset-1",
|
|
186
|
+
"aria-label": translate("Open graph in full screen"),
|
|
187
|
+
onClick: () => {
|
|
188
|
+
setGraphModalOpen(true);
|
|
189
|
+
},
|
|
190
|
+
children: /* @__PURE__ */ jsx(IconFullscreen16, {})
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
return /* @__PURE__ */ jsxs(
|
|
194
|
+
"aside",
|
|
195
|
+
{
|
|
196
|
+
className: "flex h-full w-[404px] min-w-[320px] max-w-[min(92vw,480px)] flex-col border-s border-dhis2-grey-400 bg-dhis2-grey-100",
|
|
197
|
+
"aria-label": translate("Rules"),
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ jsxs("header", { className: "shrink-0 border-b border-dhis2-grey-200 bg-white px-dp16 pt-dp12", children: [
|
|
200
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-dp12 pb-[10px]", children: [
|
|
201
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-dp8", children: [
|
|
202
|
+
/* @__PURE__ */ jsx(
|
|
203
|
+
"span",
|
|
204
|
+
{
|
|
205
|
+
className: "size-2 shrink-0 rounded-full bg-dhis2-teal-600",
|
|
206
|
+
"aria-hidden": "true"
|
|
207
|
+
}
|
|
208
|
+
),
|
|
209
|
+
/* @__PURE__ */ jsx("h2", { className: "m-0 text-base font-bold leading-[1.35] text-dhis2-grey-900", children: translate("Rules") })
|
|
210
|
+
] }),
|
|
211
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-dp8", children: [
|
|
212
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-dhis2-grey-600", children: translate("{{scoped}} in scope \xB7 {{firing}} firing", {
|
|
213
|
+
scoped: scopedRules.length,
|
|
214
|
+
firing: firingCount
|
|
215
|
+
}) }),
|
|
216
|
+
/* @__PURE__ */ jsx(
|
|
217
|
+
SegmentedControl,
|
|
218
|
+
{
|
|
219
|
+
options: SCOPE_FILTER_OPTIONS,
|
|
220
|
+
selected: scopeFilter,
|
|
221
|
+
onChange: ({ value }) => {
|
|
222
|
+
setScopeFilter(value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
] })
|
|
227
|
+
] }),
|
|
228
|
+
/* @__PURE__ */ jsx("div", { className: "flex", children: PANEL_TAB_KEYS.map((key) => /* @__PURE__ */ jsx(
|
|
229
|
+
"button",
|
|
230
|
+
{
|
|
231
|
+
type: "button",
|
|
232
|
+
className: `cursor-pointer border-0 border-b-[3px] bg-transparent px-dp12 py-dp8 text-sm font-medium ${tab === key ? "border-b-dhis2-blue-600 text-dhis2-blue-600" : "border-b-transparent text-dhis2-grey-700"}`,
|
|
233
|
+
onClick: () => {
|
|
234
|
+
setTab(key);
|
|
235
|
+
},
|
|
236
|
+
children: resolveTabLabel(key)
|
|
237
|
+
},
|
|
238
|
+
key
|
|
239
|
+
)) })
|
|
240
|
+
] }),
|
|
241
|
+
tab !== "rules" && (selectedEntry || highlightedRuleName) ? /* @__PURE__ */ jsxs("div", { className: "mx-dp16 mt-dp12 flex shrink-0 items-center justify-between gap-dp12 rounded border border-dhis2-teal-400 bg-dhis2-teal-050 p-dp12 text-[0.8125rem] leading-[1.45] text-dhis2-grey-900", children: [
|
|
242
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
243
|
+
selectedEntry ? translate("Highlighting evaluation {{time}}", {
|
|
244
|
+
time: formatAgo(selectedEntry.timestamp)
|
|
245
|
+
}) : null,
|
|
246
|
+
selectedEntry && highlightedRuleName ? " \xB7 " : null,
|
|
247
|
+
highlightedRuleName ? translate("Rule: {{name}}", { name: highlightedRuleName }) : null
|
|
248
|
+
] }),
|
|
249
|
+
/* @__PURE__ */ jsx(
|
|
250
|
+
Button,
|
|
251
|
+
{
|
|
252
|
+
small: true,
|
|
253
|
+
onClick: () => {
|
|
254
|
+
setSelectedEntryId(null);
|
|
255
|
+
setHighlightRuleId(null);
|
|
256
|
+
},
|
|
257
|
+
children: translate("Clear")
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
] }) : null,
|
|
261
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto p-dp16", children: tab === "rules" ? /* @__PURE__ */ jsx(
|
|
262
|
+
RulesTab,
|
|
263
|
+
{
|
|
264
|
+
catalog,
|
|
265
|
+
scopeStageId,
|
|
266
|
+
scopeFilter,
|
|
267
|
+
activeRuleIds,
|
|
268
|
+
selectedRuleId: highlightRuleId,
|
|
269
|
+
showConditions,
|
|
270
|
+
labelLookup,
|
|
271
|
+
onSelectRule: (ruleId) => {
|
|
272
|
+
setHighlightRuleId(ruleId);
|
|
273
|
+
setTab("graph");
|
|
274
|
+
},
|
|
275
|
+
onOpenDetails: (ruleId) => {
|
|
276
|
+
setDetailsRuleId(ruleId);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
) : tab === "trace" ? /* @__PURE__ */ jsx(
|
|
280
|
+
TraceTimeline,
|
|
281
|
+
{
|
|
282
|
+
entries,
|
|
283
|
+
selectedEntryId,
|
|
284
|
+
highlightRuleId,
|
|
285
|
+
onSelectEntry: (entryId) => {
|
|
286
|
+
setSelectedEntryId((current) => current === entryId ? null : entryId);
|
|
287
|
+
},
|
|
288
|
+
onHighlightRule: (ruleId) => {
|
|
289
|
+
setHighlightRuleId(ruleId);
|
|
290
|
+
if (ruleId) {
|
|
291
|
+
setTab("graph");
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
labelLookup
|
|
295
|
+
}
|
|
296
|
+
) : /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(RulesPanelGraphFallback, {}), children: /* @__PURE__ */ jsx(
|
|
297
|
+
LazyRuleGraphView,
|
|
298
|
+
{
|
|
299
|
+
...graphProps,
|
|
300
|
+
headerActions: graphHasNodes ? expandGraphButton : void 0
|
|
301
|
+
}
|
|
302
|
+
) }) }),
|
|
303
|
+
entries.length > 0 && tab !== "rules" ? /* @__PURE__ */ jsx("p", { className: "sr-only", "aria-live": "polite", children: translate("{{evaluations}} evaluations \xB7 {{rules}} rules observed", {
|
|
304
|
+
evaluations: entries.length,
|
|
305
|
+
rules: observedRuleCount
|
|
306
|
+
}) }) : null,
|
|
307
|
+
graphModalOpen ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
|
|
308
|
+
LazyRuleGraphModal,
|
|
309
|
+
{
|
|
310
|
+
...graphProps,
|
|
311
|
+
open: graphModalOpen,
|
|
312
|
+
onClose: () => {
|
|
313
|
+
setGraphModalOpen(false);
|
|
314
|
+
},
|
|
315
|
+
subtitle: graphSubtitle,
|
|
316
|
+
layoutKey: "open"
|
|
317
|
+
}
|
|
318
|
+
) }) : null,
|
|
319
|
+
/* @__PURE__ */ jsx(
|
|
320
|
+
RuleDetailsModal,
|
|
321
|
+
{
|
|
322
|
+
open: detailsRuleId != null,
|
|
323
|
+
onClose: () => {
|
|
324
|
+
setDetailsRuleId(null);
|
|
325
|
+
},
|
|
326
|
+
ruleId: detailsRuleId,
|
|
327
|
+
ruleName: detailsRule?.name ?? "",
|
|
328
|
+
status: detailsStatus,
|
|
329
|
+
programStageName: detailsProgramStageName,
|
|
330
|
+
programRuleVariables: metadata.metadata.programRuleVariables
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
function sortRulesFiringFirst(visibleRules, activeRuleIds) {
|
|
338
|
+
const firing = [];
|
|
339
|
+
const idle = [];
|
|
340
|
+
for (const rule of visibleRules) {
|
|
341
|
+
(activeRuleIds.has(rule.id) ? firing : idle).push(rule);
|
|
342
|
+
}
|
|
343
|
+
return [...firing, ...idle];
|
|
344
|
+
}
|
|
345
|
+
function RulesTab({
|
|
346
|
+
catalog,
|
|
347
|
+
scopeStageId,
|
|
348
|
+
scopeFilter,
|
|
349
|
+
activeRuleIds,
|
|
350
|
+
selectedRuleId,
|
|
351
|
+
showConditions,
|
|
352
|
+
labelLookup,
|
|
353
|
+
onSelectRule,
|
|
354
|
+
onOpenDetails
|
|
355
|
+
}) {
|
|
356
|
+
const listRef = useRef(null);
|
|
357
|
+
const visibleRules = scopeFilter === "all" ? catalog : catalog.filter((rule) => isRuleInScope(rule, scopeStageId));
|
|
358
|
+
const sortedRules = useMemo(
|
|
359
|
+
() => sortRulesFiringFirst(visibleRules, activeRuleIds),
|
|
360
|
+
[visibleRules, activeRuleIds]
|
|
361
|
+
);
|
|
362
|
+
useFlipReorder(
|
|
363
|
+
useMemo(() => sortedRules.map((rule) => rule.id), [sortedRules]),
|
|
364
|
+
listRef
|
|
365
|
+
);
|
|
366
|
+
if (!catalog.length) {
|
|
367
|
+
return /* @__PURE__ */ jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("This program has no rules.") });
|
|
368
|
+
}
|
|
369
|
+
if (!sortedRules.length) {
|
|
370
|
+
return /* @__PURE__ */ jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("No rules are in scope for this stage.") });
|
|
371
|
+
}
|
|
372
|
+
return /* @__PURE__ */ jsx("ul", { ref: listRef, className: "m-0 flex list-none flex-col gap-[10px] p-0", children: sortedRules.map((rule) => {
|
|
373
|
+
const inScope = isRuleInScope(rule, scopeStageId);
|
|
374
|
+
const firing = activeRuleIds.has(rule.id);
|
|
375
|
+
const isSelected = selectedRuleId === rule.id;
|
|
376
|
+
const status = resolveCardStatus(inScope, firing);
|
|
377
|
+
return /* @__PURE__ */ jsx("li", { "data-rule-id": rule.id, className: "m-0 shrink-0", children: /* @__PURE__ */ jsxs(
|
|
378
|
+
"article",
|
|
379
|
+
{
|
|
380
|
+
role: "button",
|
|
381
|
+
tabIndex: 0,
|
|
382
|
+
onClick: () => {
|
|
383
|
+
onSelectRule(rule.id);
|
|
384
|
+
},
|
|
385
|
+
onKeyDown: (event) => {
|
|
386
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
onSelectRule(rule.id);
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
className: "relative flex cursor-pointer flex-col gap-dp8 rounded-[5px] border border-dhis2-grey-300 bg-white py-[11px] pe-[12px] ps-[14px] shadow-[0_1px_2px_rgb(0_0_0/4%)]",
|
|
392
|
+
children: [
|
|
393
|
+
/* @__PURE__ */ jsx(
|
|
394
|
+
"span",
|
|
395
|
+
{
|
|
396
|
+
className: `absolute inset-y-0 start-0 w-[3px] rounded-s-[5px] ${resolveAccentClassName(inScope, firing, isSelected)}`,
|
|
397
|
+
"aria-hidden": "true"
|
|
398
|
+
}
|
|
399
|
+
),
|
|
400
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-dp8", children: [
|
|
401
|
+
/* @__PURE__ */ jsx(
|
|
402
|
+
"h3",
|
|
403
|
+
{
|
|
404
|
+
className: `m-0 min-w-0 flex-1 text-sm font-semibold leading-[1.4] ${inScope ? "text-dhis2-grey-900" : "text-dhis2-grey-600"}`,
|
|
405
|
+
children: rule.name
|
|
406
|
+
}
|
|
407
|
+
),
|
|
408
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-[6px]", children: [
|
|
409
|
+
/* @__PURE__ */ jsx(
|
|
410
|
+
"span",
|
|
411
|
+
{
|
|
412
|
+
className: `text-[11px] font-semibold ${status.className}`,
|
|
413
|
+
children: status.label
|
|
414
|
+
}
|
|
415
|
+
),
|
|
416
|
+
/* @__PURE__ */ jsx(
|
|
417
|
+
"button",
|
|
418
|
+
{
|
|
419
|
+
type: "button",
|
|
420
|
+
title: translate("Program rule details"),
|
|
421
|
+
"aria-label": translate("Program rule details"),
|
|
422
|
+
onClick: (event) => {
|
|
423
|
+
event.stopPropagation();
|
|
424
|
+
onOpenDetails(rule.id);
|
|
425
|
+
},
|
|
426
|
+
className: "inline-flex size-[22px] shrink-0 cursor-pointer items-center justify-center rounded-[3px] border-0 bg-transparent text-dhis2-grey-600 hover:bg-dhis2-grey-200 hover:text-dhis2-blue-600",
|
|
427
|
+
children: /* @__PURE__ */ jsx(IconInfo16, {})
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
] })
|
|
431
|
+
] }),
|
|
432
|
+
rule.programRuleActions.length ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-[6px]", children: rule.programRuleActions.map((action, index) => {
|
|
433
|
+
const summary = formatRuleActionSummary(
|
|
434
|
+
action,
|
|
435
|
+
labelLookup
|
|
436
|
+
);
|
|
437
|
+
return /* @__PURE__ */ jsx(
|
|
438
|
+
EffectBadge,
|
|
439
|
+
{
|
|
440
|
+
type: summary.type,
|
|
441
|
+
children: formatActionLabel(summary)
|
|
442
|
+
},
|
|
443
|
+
`${rule.id}-action-${String(index)}`
|
|
444
|
+
);
|
|
445
|
+
}) }) : null,
|
|
446
|
+
showConditions && rule.condition ? /* @__PURE__ */ jsx(
|
|
447
|
+
"p",
|
|
448
|
+
{
|
|
449
|
+
className: "m-0 break-words font-mono text-[11px] leading-[1.5] text-dhis2-grey-700",
|
|
450
|
+
title: rule.condition,
|
|
451
|
+
children: rule.condition
|
|
452
|
+
}
|
|
453
|
+
) : null,
|
|
454
|
+
!inScope ? /* @__PURE__ */ jsx("span", { className: "text-[11px] text-dhis2-grey-600", children: resolveAppliesToCaption(rule, labelLookup) }) : null
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
) }, rule.id);
|
|
458
|
+
}) });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export { RulesPanel };
|
|
462
|
+
//# sourceMappingURL=RulesPanel.js.map
|
|
463
|
+
//# sourceMappingURL=RulesPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/RulesPanel.tsx"],"names":[],"mappings":"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","file":"RulesPanel.js","sourcesContent":["import { Button, IconFullscreen16, IconInfo16, SegmentedControl } from '@dhis2/ui';\nimport type { RuleTraceEntry } from '@nnkogift/dhis2-form-utils-hooks';\nimport { useFormStateContext, useFormStore } from '@nnkogift/dhis2-form-utils-hooks';\nimport {\n lazy,\n Suspense,\n useCallback,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { buildGraphFromTrace } from './buildGraph.js';\nimport {\n createLabelLookup,\n type DevtoolsLabelLookup,\n type RuleDevtoolsMetadata,\n} from './createLabelLookup.js';\nimport { EffectBadge } from './EffectBadge.js';\nimport { formatAgo } from './formatAgo.js';\nimport { formatRuleActionSummary } from './formatRuleActionSummary.js';\nimport { translate } from './i18n.js';\nimport type { CatalogRule } from './resolveProgramRulesList.js';\nimport { resolveProgramRulesList } from './resolveProgramRulesList.js';\nimport { useRuleTraceStore } from './RuleDevtoolsScope.js';\nimport { RuleDetailsModal, type RuleDetailsStatus } from './RuleDetailsModal.js';\nimport { RulesPanelGraphFallback } from './RulesPanelGraphFallback.js';\nimport { TraceTimeline } from './TraceTimeline.js';\nimport { getActiveRuleIds, resolveGraphTraceEntry } from './traceEntry.js';\nimport { useFlipReorder } from './useFlipReorder.js';\n\nconst LazyRuleGraphView = lazy(() =>\n import('./RuleGraphView').then((module) => ({ default: module.RuleGraphView }))\n);\n\nconst LazyRuleGraphModal = lazy(() =>\n import('./RuleGraphModal').then((module) => ({ default: module.RuleGraphModal }))\n);\n\nexport type { RuleDevtoolsMetadata } from './createLabelLookup.js';\n\nexport type RulesPanelProps = {\n metadata: RuleDevtoolsMetadata;\n /** Show each rule's raw condition expression in the Rules tab. Defaults to true. */\n showConditions?: boolean;\n /**\n * The program stage currently visible in the host app, overriding the metadata-derived\n * default. Pass `null` for a registration/enrollment view (no stage visible), or a stage\n * UID for the stage currently being viewed. Omit to fall back to `metadata.programStageId`\n * (event forms) or `null` (tracker forms).\n */\n activeProgramStageId?: string | null;\n};\n\ntype DevtoolsTab = 'rules' | 'trace' | 'graph';\n\ntype RuleScopeFilter = 'scoped' | 'all';\n\ntype RuleCardStatus = {\n label: string;\n className: string;\n};\n\nfunction resolveScopeStageId(\n metadata: RuleDevtoolsMetadata,\n activeProgramStageId: string | null | undefined\n): string | null {\n if (activeProgramStageId !== undefined) {\n return activeProgramStageId;\n }\n return metadata.formKind === 'event' ? metadata.programStageId : null;\n}\n\n/**\n * A rule with no `programStageId` belongs to the registration/enrollment form only — it is in\n * scope there and nowhere else. A rule with a `programStageId` is in scope only for that exact\n * stage. Scope is strictly tied to the form currently being viewed, not to where the underlying\n * DHIS2 rule engine happens to evaluate the rule.\n */\nfunction isRuleInScope(rule: CatalogRule, scopeStageId: string | null): boolean {\n return rule.programStageId === scopeStageId;\n}\n\nfunction resolveAccentClassName(inScope: boolean, firing: boolean, selected: boolean): string {\n if (selected) {\n return 'bg-dhis2-blue-600';\n }\n if (!inScope) {\n return 'bg-transparent';\n }\n return firing ? 'bg-dhis2-teal-600' : 'bg-dhis2-grey-400';\n}\n\nfunction resolveCardStatus(inScope: boolean, firing: boolean): RuleCardStatus {\n if (!inScope) {\n return { label: translate('Out of scope'), className: 'text-dhis2-grey-500' };\n }\n return firing\n ? { label: translate('Firing'), className: 'text-dhis2-teal-700' }\n : { label: translate('Idle'), className: 'text-dhis2-grey-600' };\n}\n\nfunction resolveDetailsStatus(inScope: boolean, firing: boolean): RuleDetailsStatus {\n if (!inScope) {\n return 'out-of-scope';\n }\n return firing ? 'firing' : 'idle';\n}\n\nfunction resolveAppliesToCaption(rule: CatalogRule, labelLookup: DevtoolsLabelLookup): string {\n return rule.programStageId === null\n ? translate('Applies to registration')\n : translate('Applies to {{stage}}', {\n stage: labelLookup.resolveStageName(rule.programStageId),\n });\n}\n\nfunction formatActionLabel(action: ReturnType<typeof formatRuleActionSummary>): string {\n if (action.targetLabel && action.detail) {\n return `${action.type} · ${action.targetLabel} = ${action.detail}`;\n }\n if (action.targetLabel) {\n return `${action.type} · ${action.targetLabel}`;\n }\n return action.type;\n}\n\nfunction countObservedRules(entries: readonly RuleTraceEntry[]): number {\n const ruleIds = new Set<string>();\n for (const entry of entries) {\n for (const result of entry.ruleResults) {\n ruleIds.add(result.ruleId);\n }\n }\n return ruleIds.size;\n}\n\nconst PANEL_TAB_KEYS: DevtoolsTab[] = ['rules', 'trace', 'graph'];\n\nconst SCOPE_FILTER_OPTIONS = [\n { label: translate('In scope'), value: 'scoped' satisfies RuleScopeFilter },\n { label: translate('All'), value: 'all' satisfies RuleScopeFilter },\n];\n\nfunction resolveTabLabel(key: DevtoolsTab): string {\n switch (key) {\n case 'rules':\n return translate('Rules');\n case 'trace':\n return translate('Trace');\n case 'graph':\n return translate('Graph');\n }\n}\n\nexport function RulesPanel({\n metadata,\n showConditions = true,\n activeProgramStageId,\n}: RulesPanelProps) {\n const formStore = useFormStore();\n const { form } = useFormStateContext();\n const traceStore = useRuleTraceStore();\n\n const [tab, setTab] = useState<DevtoolsTab>('rules');\n const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);\n const [highlightRuleId, setHighlightRuleId] = useState<string | null>(null);\n const [graphModalOpen, setGraphModalOpen] = useState(false);\n const [scopeFilter, setScopeFilter] = useState<RuleScopeFilter>('scoped');\n const [detailsRuleId, setDetailsRuleId] = useState<string | null>(null);\n\n const labelLookup = useMemo(() => createLabelLookup(metadata), [metadata]);\n const catalog = useMemo(() => resolveProgramRulesList(metadata), [metadata]);\n const scopeStageId = useMemo(\n () => resolveScopeStageId(metadata, activeProgramStageId),\n [metadata, activeProgramStageId]\n );\n\n const entries = useSyncExternalStore(\n useCallback((listener) => traceStore.subscribe(listener), [traceStore]),\n useCallback(() => traceStore.getSnapshot(), [traceStore]),\n useCallback(() => traceStore.getSnapshot(), [traceStore])\n );\n\n const fieldState = useSyncExternalStore(\n useCallback((listener) => formStore.fieldStore.subscribeAll(listener), [formStore]),\n useCallback(() => formStore.fieldStore.getSnapshot(), [formStore]),\n useCallback(() => formStore.fieldStore.getSnapshot(), [formStore])\n );\n\n const activeRuleIds = useMemo(() => getActiveRuleIds(entries), [entries]);\n const scopedRules = useMemo(\n () => catalog.filter((rule) => isRuleInScope(rule, scopeStageId)),\n [catalog, scopeStageId]\n );\n const firingCount = useMemo(\n () => scopedRules.filter((rule) => activeRuleIds.has(rule.id)).length,\n [scopedRules, activeRuleIds]\n );\n\n const selectedEntry = useMemo(\n () => entries.find((entry) => entry.id === selectedEntryId) ?? null,\n [entries, selectedEntryId]\n );\n const observedRuleCount = useMemo(() => countObservedRules(entries), [entries]);\n const highlightedRuleName = useMemo(() => {\n if (!highlightRuleId) {\n return null;\n }\n return labelLookup.resolveRuleName(highlightRuleId);\n }, [highlightRuleId, labelLookup]);\n\n const graphHasNodes = useMemo(() => {\n const entry = resolveGraphTraceEntry(entries, selectedEntryId);\n return entry ? buildGraphFromTrace([entry], labelLookup).nodes.length > 0 : false;\n }, [entries, labelLookup, selectedEntryId]);\n\n const graphSubtitle = useMemo(() => {\n if (selectedEntry && highlightedRuleName) {\n return translate('Highlighting evaluation {{time}} · Rule: {{name}}', {\n time: formatAgo(selectedEntry.timestamp),\n name: highlightedRuleName,\n });\n }\n if (selectedEntry) {\n return translate('Highlighting evaluation {{time}}', {\n time: formatAgo(selectedEntry.timestamp),\n });\n }\n if (highlightedRuleName) {\n return translate('Rule: {{name}}', { name: highlightedRuleName });\n }\n return null;\n }, [highlightedRuleName, selectedEntry]);\n\n const detailsRule = useMemo(\n () => catalog.find((rule) => rule.id === detailsRuleId) ?? null,\n [catalog, detailsRuleId]\n );\n const detailsStatus = detailsRule\n ? resolveDetailsStatus(\n isRuleInScope(detailsRule, scopeStageId),\n activeRuleIds.has(detailsRule.id)\n )\n : 'idle';\n const detailsProgramStageName = detailsRule\n ? detailsRule.programStageId === null\n ? null\n : labelLookup.resolveStageName(detailsRule.programStageId)\n : undefined;\n\n const graphProps = {\n entries,\n fieldState,\n formValues: form.getValues(),\n selectedEntryId,\n highlightRuleId,\n labelLookup,\n };\n\n const expandGraphButton = (\n <button\n type=\"button\"\n className=\"inline-flex size-11 min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded border border-dhis2-grey-300 bg-white text-dhis2-grey-800 hover:bg-dhis2-grey-100 focus-visible:outline-2 focus-visible:outline-dhis2-teal-600 focus-visible:outline-offset-1\"\n aria-label={translate('Open graph in full screen')}\n onClick={() => {\n setGraphModalOpen(true);\n }}\n >\n <IconFullscreen16 />\n </button>\n );\n\n return (\n <aside\n className=\"flex h-full w-[404px] min-w-[320px] max-w-[min(92vw,480px)] flex-col border-s border-dhis2-grey-400 bg-dhis2-grey-100\"\n aria-label={translate('Rules')}\n >\n <header className=\"shrink-0 border-b border-dhis2-grey-200 bg-white px-dp16 pt-dp12\">\n <div className=\"flex items-center justify-between gap-dp12 pb-[10px]\">\n <div className=\"flex items-center gap-dp8\">\n <span\n className=\"size-2 shrink-0 rounded-full bg-dhis2-teal-600\"\n aria-hidden=\"true\"\n />\n <h2 className=\"m-0 text-base font-bold leading-[1.35] text-dhis2-grey-900\">\n {translate('Rules')}\n </h2>\n </div>\n <div className=\"flex shrink-0 items-center gap-dp8\">\n <span className=\"text-xs text-dhis2-grey-600\">\n {translate('{{scoped}} in scope · {{firing}} firing', {\n scoped: scopedRules.length,\n firing: firingCount,\n })}\n </span>\n <SegmentedControl\n options={SCOPE_FILTER_OPTIONS}\n selected={scopeFilter}\n onChange={({ value }) => {\n setScopeFilter(value as RuleScopeFilter);\n }}\n />\n </div>\n </div>\n <div className=\"flex\">\n {PANEL_TAB_KEYS.map((key) => (\n <button\n key={key}\n type=\"button\"\n className={`cursor-pointer border-0 border-b-[3px] bg-transparent px-dp12 py-dp8 text-sm font-medium ${\n tab === key\n ? 'border-b-dhis2-blue-600 text-dhis2-blue-600'\n : 'border-b-transparent text-dhis2-grey-700'\n }`}\n onClick={() => {\n setTab(key);\n }}\n >\n {resolveTabLabel(key)}\n </button>\n ))}\n </div>\n </header>\n\n {tab !== 'rules' && (selectedEntry || highlightedRuleName) ? (\n <div className=\"mx-dp16 mt-dp12 flex shrink-0 items-center justify-between gap-dp12 rounded border border-dhis2-teal-400 bg-dhis2-teal-050 p-dp12 text-[0.8125rem] leading-[1.45] text-dhis2-grey-900\">\n <span className=\"min-w-0\">\n {selectedEntry\n ? translate('Highlighting evaluation {{time}}', {\n time: formatAgo(selectedEntry.timestamp),\n })\n : null}\n {selectedEntry && highlightedRuleName ? ' · ' : null}\n {highlightedRuleName\n ? translate('Rule: {{name}}', { name: highlightedRuleName })\n : null}\n </span>\n <Button\n small\n onClick={() => {\n setSelectedEntryId(null);\n setHighlightRuleId(null);\n }}\n >\n {translate('Clear')}\n </Button>\n </div>\n ) : null}\n\n <div className=\"min-h-0 flex-1 overflow-auto p-dp16\">\n {tab === 'rules' ? (\n <RulesTab\n catalog={catalog}\n scopeStageId={scopeStageId}\n scopeFilter={scopeFilter}\n activeRuleIds={activeRuleIds}\n selectedRuleId={highlightRuleId}\n showConditions={showConditions}\n labelLookup={labelLookup}\n onSelectRule={(ruleId) => {\n setHighlightRuleId(ruleId);\n setTab('graph');\n }}\n onOpenDetails={(ruleId) => {\n setDetailsRuleId(ruleId);\n }}\n />\n ) : tab === 'trace' ? (\n <TraceTimeline\n entries={entries}\n selectedEntryId={selectedEntryId}\n highlightRuleId={highlightRuleId}\n onSelectEntry={(entryId) => {\n setSelectedEntryId((current) => (current === entryId ? null : entryId));\n }}\n onHighlightRule={(ruleId) => {\n setHighlightRuleId(ruleId);\n if (ruleId) {\n setTab('graph');\n }\n }}\n labelLookup={labelLookup}\n />\n ) : (\n <Suspense fallback={<RulesPanelGraphFallback />}>\n <LazyRuleGraphView\n {...graphProps}\n headerActions={graphHasNodes ? expandGraphButton : undefined}\n />\n </Suspense>\n )}\n </div>\n\n {entries.length > 0 && tab !== 'rules' ? (\n <p className=\"sr-only\" aria-live=\"polite\">\n {translate('{{evaluations}} evaluations · {{rules}} rules observed', {\n evaluations: entries.length,\n rules: observedRuleCount,\n })}\n </p>\n ) : null}\n\n {graphModalOpen ? (\n <Suspense fallback={null}>\n <LazyRuleGraphModal\n {...graphProps}\n open={graphModalOpen}\n onClose={() => {\n setGraphModalOpen(false);\n }}\n subtitle={graphSubtitle}\n layoutKey=\"open\"\n />\n </Suspense>\n ) : null}\n\n <RuleDetailsModal\n open={detailsRuleId != null}\n onClose={() => {\n setDetailsRuleId(null);\n }}\n ruleId={detailsRuleId}\n ruleName={detailsRule?.name ?? ''}\n status={detailsStatus}\n programStageName={detailsProgramStageName}\n programRuleVariables={metadata.metadata.programRuleVariables}\n />\n </aside>\n );\n}\n\ntype RulesTabProps = {\n catalog: CatalogRule[];\n scopeStageId: string | null;\n scopeFilter: RuleScopeFilter;\n activeRuleIds: ReadonlySet<string>;\n selectedRuleId: string | null;\n showConditions: boolean;\n labelLookup: ReturnType<typeof createLabelLookup>;\n onSelectRule: (ruleId: string) => void;\n onOpenDetails: (ruleId: string) => void;\n};\n\nfunction sortRulesFiringFirst(\n visibleRules: CatalogRule[],\n activeRuleIds: ReadonlySet<string>\n): CatalogRule[] {\n const firing: CatalogRule[] = [];\n const idle: CatalogRule[] = [];\n for (const rule of visibleRules) {\n (activeRuleIds.has(rule.id) ? firing : idle).push(rule);\n }\n return [...firing, ...idle];\n}\n\nfunction RulesTab({\n catalog,\n scopeStageId,\n scopeFilter,\n activeRuleIds,\n selectedRuleId,\n showConditions,\n labelLookup,\n onSelectRule,\n onOpenDetails,\n}: RulesTabProps) {\n const listRef = useRef<HTMLUListElement>(null);\n\n const visibleRules =\n scopeFilter === 'all'\n ? catalog\n : catalog.filter((rule) => isRuleInScope(rule, scopeStageId));\n\n const sortedRules = useMemo(\n () => sortRulesFiringFirst(visibleRules, activeRuleIds),\n [visibleRules, activeRuleIds]\n );\n\n useFlipReorder(\n useMemo(() => sortedRules.map((rule) => rule.id), [sortedRules]),\n listRef\n );\n\n if (!catalog.length) {\n return (\n <p className=\"m-0 text-sm leading-normal text-dhis2-grey-600\">\n {translate('This program has no rules.')}\n </p>\n );\n }\n\n if (!sortedRules.length) {\n return (\n <p className=\"m-0 text-sm leading-normal text-dhis2-grey-600\">\n {translate('No rules are in scope for this stage.')}\n </p>\n );\n }\n\n return (\n <ul ref={listRef} className=\"m-0 flex list-none flex-col gap-[10px] p-0\">\n {sortedRules.map((rule) => {\n const inScope = isRuleInScope(rule, scopeStageId);\n const firing = activeRuleIds.has(rule.id);\n const isSelected = selectedRuleId === rule.id;\n const status = resolveCardStatus(inScope, firing);\n\n return (\n <li key={rule.id} data-rule-id={rule.id} className=\"m-0 shrink-0\">\n <article\n role=\"button\"\n tabIndex={0}\n onClick={() => {\n onSelectRule(rule.id);\n }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n onSelectRule(rule.id);\n }\n }}\n className=\"relative flex cursor-pointer flex-col gap-dp8 rounded-[5px] border border-dhis2-grey-300 bg-white py-[11px] pe-[12px] ps-[14px] shadow-[0_1px_2px_rgb(0_0_0/4%)]\"\n >\n <span\n className={`absolute inset-y-0 start-0 w-[3px] rounded-s-[5px] ${resolveAccentClassName(inScope, firing, isSelected)}`}\n aria-hidden=\"true\"\n />\n <div className=\"flex items-start justify-between gap-dp8\">\n <h3\n className={`m-0 min-w-0 flex-1 text-sm font-semibold leading-[1.4] ${\n inScope ? 'text-dhis2-grey-900' : 'text-dhis2-grey-600'\n }`}\n >\n {rule.name}\n </h3>\n <span className=\"flex shrink-0 items-center gap-[6px]\">\n <span\n className={`text-[11px] font-semibold ${status.className}`}\n >\n {status.label}\n </span>\n <button\n type=\"button\"\n title={translate('Program rule details')}\n aria-label={translate('Program rule details')}\n onClick={(event) => {\n event.stopPropagation();\n onOpenDetails(rule.id);\n }}\n className=\"inline-flex size-[22px] shrink-0 cursor-pointer items-center justify-center rounded-[3px] border-0 bg-transparent text-dhis2-grey-600 hover:bg-dhis2-grey-200 hover:text-dhis2-blue-600\"\n >\n <IconInfo16 />\n </button>\n </span>\n </div>\n\n {rule.programRuleActions.length ? (\n <div className=\"flex flex-wrap gap-[6px]\">\n {rule.programRuleActions.map((action, index) => {\n const summary = formatRuleActionSummary(\n action,\n labelLookup\n );\n return (\n <EffectBadge\n key={`${rule.id}-action-${String(index)}`}\n type={summary.type}\n >\n {formatActionLabel(summary)}\n </EffectBadge>\n );\n })}\n </div>\n ) : null}\n\n {showConditions && rule.condition ? (\n <p\n className=\"m-0 break-words font-mono text-[11px] leading-[1.5] text-dhis2-grey-700\"\n title={rule.condition}\n >\n {rule.condition}\n </p>\n ) : null}\n\n {!inScope ? (\n <span className=\"text-[11px] text-dhis2-grey-600\">\n {resolveAppliesToCaption(rule, labelLookup)}\n </span>\n ) : null}\n </article>\n </li>\n );\n })}\n </ul>\n );\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { CircularLoader } from '@dhis2/ui';
|
|
3
|
+
import { translate } from './i18n.js';
|
|
4
|
+
|
|
5
|
+
function RulesPanelGraphFallback() {
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
"div",
|
|
8
|
+
{
|
|
9
|
+
className: "flex min-h-[200px] items-center justify-center",
|
|
10
|
+
role: "status",
|
|
11
|
+
"aria-label": translate("Loading graph"),
|
|
12
|
+
children: /* @__PURE__ */ jsx(CircularLoader, { small: true })
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { RulesPanelGraphFallback };
|
|
18
|
+
//# sourceMappingURL=RulesPanelGraphFallback.js.map
|
|
19
|
+
//# sourceMappingURL=RulesPanelGraphFallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/RulesPanelGraphFallback.tsx"],"names":[],"mappings":";;;;AAGO,SAAS,uBAAA,GAA0B;AACtC,EAAA,uBACI,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACG,SAAA,EAAU,gDAAA;AAAA,MACV,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAY,UAAU,eAAe,CAAA;AAAA,MAErC,QAAA,kBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,KAAA,EAAK,IAAA,EAAC;AAAA;AAAA,GAC1B;AAER","file":"RulesPanelGraphFallback.js","sourcesContent":["import { CircularLoader } from '@dhis2/ui';\nimport { translate } from './i18n.js';\n\nexport function RulesPanelGraphFallback() {\n return (\n <div\n className=\"flex min-h-[200px] items-center justify-center\"\n role=\"status\"\n aria-label={translate('Loading graph')}\n >\n <CircularLoader small />\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { RuleTraceEntry } from '@nnkogift/dhis2-form-utils-hooks';
|
|
3
|
+
import { DevtoolsLabelLookup } from './createLabelLookup.js';
|
|
4
|
+
import '@nnkogift/dhis2-form-utils-metadata';
|
|
5
|
+
|
|
6
|
+
type TraceTimelineProps = {
|
|
7
|
+
entries: readonly RuleTraceEntry[];
|
|
8
|
+
selectedEntryId: string | null;
|
|
9
|
+
highlightRuleId: string | null;
|
|
10
|
+
onSelectEntry: (entryId: string) => void;
|
|
11
|
+
onHighlightRule: (ruleId: string | null) => void;
|
|
12
|
+
labelLookup?: DevtoolsLabelLookup;
|
|
13
|
+
};
|
|
14
|
+
declare function TraceTimeline({ entries, selectedEntryId, highlightRuleId, onSelectEntry, onHighlightRule, labelLookup, }: TraceTimelineProps): react_jsx_runtime.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { TraceTimeline };
|