@nnkogift/dhis2-form-utils-devtools 0.1.0-alpha.5 → 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.
Files changed (80) hide show
  1. package/dist/EffectBadge.d.ts +17 -0
  2. package/dist/EffectBadge.js +55 -0
  3. package/dist/EffectBadge.js.map +1 -0
  4. package/dist/RuleDetailsModal.d.ts +30 -0
  5. package/dist/RuleDetailsModal.js +403 -0
  6. package/dist/RuleDetailsModal.js.map +1 -0
  7. package/dist/RuleDevtoolsScope.d.ts +14 -0
  8. package/dist/RuleDevtoolsScope.js +25 -0
  9. package/dist/RuleDevtoolsScope.js.map +1 -0
  10. package/dist/RuleGraphModal.d.ts +17 -0
  11. package/dist/RuleGraphModal.js +35 -0
  12. package/dist/RuleGraphModal.js.map +1 -0
  13. package/dist/RuleGraphView.d.ts +43 -0
  14. package/dist/RuleGraphView.js +401 -0
  15. package/dist/RuleGraphView.js.map +1 -0
  16. package/dist/RulesPanel.d.ts +19 -0
  17. package/dist/RulesPanel.js +463 -0
  18. package/dist/RulesPanel.js.map +1 -0
  19. package/dist/RulesPanelGraphFallback.d.ts +5 -0
  20. package/dist/RulesPanelGraphFallback.js +19 -0
  21. package/dist/RulesPanelGraphFallback.js.map +1 -0
  22. package/dist/TraceTimeline.d.ts +16 -0
  23. package/dist/TraceTimeline.js +227 -0
  24. package/dist/TraceTimeline.js.map +1 -0
  25. package/dist/attach.d.ts +12 -0
  26. package/dist/attach.js +14 -0
  27. package/dist/attach.js.map +1 -0
  28. package/dist/buildGraph.d.ts +25 -0
  29. package/dist/buildGraph.js +67 -0
  30. package/dist/buildGraph.js.map +1 -0
  31. package/dist/constants.d.ts +3 -0
  32. package/dist/constants.js +5 -0
  33. package/dist/constants.js.map +1 -0
  34. package/dist/createLabelLookup.d.ts +25 -0
  35. package/dist/createLabelLookup.js +81 -0
  36. package/dist/createLabelLookup.js.map +1 -0
  37. package/dist/effectStyles.d.ts +28 -0
  38. package/dist/effectStyles.js +142 -0
  39. package/dist/effectStyles.js.map +1 -0
  40. package/dist/formatAgo.d.ts +3 -0
  41. package/dist/formatAgo.js +25 -0
  42. package/dist/formatAgo.js.map +1 -0
  43. package/dist/formatRuleActionSummary.d.ts +33 -0
  44. package/dist/formatRuleActionSummary.js +61 -0
  45. package/dist/formatRuleActionSummary.js.map +1 -0
  46. package/dist/graphLayout.d.ts +48 -0
  47. package/dist/graphLayout.js +137 -0
  48. package/dist/graphLayout.js.map +1 -0
  49. package/dist/graphNodeStyles.d.ts +9 -0
  50. package/dist/graphNodeStyles.js +22 -0
  51. package/dist/graphNodeStyles.js.map +1 -0
  52. package/dist/i18n.d.ts +4 -0
  53. package/dist/i18n.js +9 -0
  54. package/dist/i18n.js.map +1 -0
  55. package/dist/index.cjs +1230 -1089
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.css +1 -1
  58. package/dist/index.d.cts +12 -90
  59. package/dist/index.d.ts +12 -90
  60. package/dist/index.js +5 -2240
  61. package/dist/index.js.map +1 -1
  62. package/dist/programRuleDetailQuery.d.ts +70 -0
  63. package/dist/programRuleDetailQuery.js +42 -0
  64. package/dist/programRuleDetailQuery.js.map +1 -0
  65. package/dist/resolveProgramRulesList.d.ts +21 -0
  66. package/dist/resolveProgramRulesList.js +45 -0
  67. package/dist/resolveProgramRulesList.js.map +1 -0
  68. package/dist/traceEntry.d.ts +12 -0
  69. package/dist/traceEntry.js +20 -0
  70. package/dist/traceEntry.js.map +1 -0
  71. package/dist/traceStore.d.ts +12 -0
  72. package/dist/traceStore.js +39 -0
  73. package/dist/traceStore.js.map +1 -0
  74. package/dist/useFlipReorder.d.ts +8 -0
  75. package/dist/useFlipReorder.js +52 -0
  76. package/dist/useFlipReorder.js.map +1 -0
  77. package/dist/useProgramRuleDetail.d.ts +17 -0
  78. package/dist/useProgramRuleDetail.js +30 -0
  79. package/dist/useProgramRuleDetail.js.map +1 -0
  80. package/package.json +24 -4
package/dist/index.cjs CHANGED
@@ -1,23 +1,34 @@
1
1
  'use strict';
2
2
 
3
3
  var ui = require('@dhis2/ui');
4
- var dhis2FormUtilsHooks = require('@nnkogift/dhis2-form-utils-hooks');
5
- var react = require('react');
6
- var dhis2FormUtilsMetadata = require('@nnkogift/dhis2-form-utils-metadata');
7
4
  var i18n = require('@dhis2/d2-i18n');
8
5
  var jsxRuntime = require('react/jsx-runtime');
9
- var appRuntime = require('@dhis2/app-runtime');
6
+ var react = require('react');
10
7
  var react$1 = require('@xyflow/react');
8
+ var dhis2FormUtilsHooks = require('@nnkogift/dhis2-form-utils-hooks');
9
+ var dhis2FormUtilsMetadata = require('@nnkogift/dhis2-form-utils-metadata');
10
+ var appRuntime = require('@dhis2/app-runtime');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
13
 
14
14
  var i18n__default = /*#__PURE__*/_interopDefault(i18n);
15
15
 
16
- // src/RulesPanel.tsx
16
+ var __defProp = Object.defineProperty;
17
+ var __getOwnPropNames = Object.getOwnPropertyNames;
18
+ var __esm = (fn, res, err) => function __init() {
19
+ if (err) throw err[0];
20
+ try {
21
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
22
+ } catch (e) {
23
+ throw err = [e], e;
24
+ }
25
+ };
26
+ var __export = (target, all) => {
27
+ for (var name in all)
28
+ __defProp(target, name, { get: all[name], enumerable: true });
29
+ };
17
30
 
18
31
  // src/buildGraph.ts
19
- var nodeKey = (kind, id) => `${kind}:${id}`;
20
- var edgeKey = (source, ruleId, target, effectType) => `${source}|${ruleId}|${target}|${effectType ?? ""}`;
21
32
  function accumulateGraph(previous, entry, labelLookup) {
22
33
  const nodes = new Map(previous.nodes.map((node) => [node.id, node]));
23
34
  const edges = new Map(previous.edges.map((edge) => [edge.id, edge]));
@@ -79,167 +90,13 @@ function buildGraphFromTrace(entries, labelLookup) {
79
90
  { nodes: [], edges: [] }
80
91
  );
81
92
  }
82
- var createLookup = (rules, fields, sections, stages) => ({
83
- resolveRuleName: (id) => rules.get(id) ?? id,
84
- resolveFieldName: (id) => fields.get(id) ?? id,
85
- resolveSectionName: (id) => sections.get(id) ?? id,
86
- resolveStageName: (id) => stages.get(id) ?? id
87
- });
88
- function buildEventLookup(source) {
89
- const rules = /* @__PURE__ */ new Map();
90
- const fields = /* @__PURE__ */ new Map();
91
- const sections = /* @__PURE__ */ new Map();
92
- const stages = /* @__PURE__ */ new Map();
93
- for (const rule of source.metadata.programRules) {
94
- if (!rule.id) {
95
- continue;
96
- }
97
- rules.set(rule.id, rule.displayName ?? rule.id);
98
- }
99
- const stage = dhis2FormUtilsMetadata.selectProgramStage(
100
- source.metadata,
101
- source.programStageId
102
- ) ?? {
103
- id: source.programStageId,
104
- programStageDataElements: [],
105
- programStageSections: []
106
- };
107
- for (const psde of stage.programStageDataElements ?? []) {
108
- const de = psde.dataElement;
109
- if (!de?.id) {
110
- continue;
111
- }
112
- fields.set(de.id, de.displayFormName ?? de.displayName ?? de.id);
113
- }
114
- for (const section of stage.programStageSections ?? []) {
115
- sections.set(section.id, section.displayName ?? section.id);
116
- }
117
- for (const programStage of source.metadata.programStages) {
118
- if (!programStage.id) {
119
- continue;
120
- }
121
- stages.set(programStage.id, programStage.displayName ?? programStage.id);
122
- }
123
- return createLookup(rules, fields, sections, stages);
124
- }
125
- function buildTrackerLookup(source) {
126
- const rules = /* @__PURE__ */ new Map();
127
- const fields = /* @__PURE__ */ new Map();
128
- const sections = /* @__PURE__ */ new Map();
129
- const stages = /* @__PURE__ */ new Map();
130
- for (const rule of source.metadata.programRules) {
131
- if (!rule.id) {
132
- continue;
133
- }
134
- rules.set(rule.id, rule.name ?? rule.id);
135
- }
136
- for (const ptea of source.metadata.programTrackedEntityAttributes) {
137
- const tea = ptea.trackedEntityAttribute;
138
- if (!tea.id) {
139
- continue;
140
- }
141
- fields.set(tea.id, tea.formName ?? tea.displayName ?? tea.id);
142
- }
143
- for (const section of source.metadata.programSections ?? []) {
144
- sections.set(section.id, section.displayName ?? section.id);
145
- }
146
- for (const programStage of source.programStages ?? []) {
147
- if (!programStage.id) {
148
- continue;
149
- }
150
- stages.set(programStage.id, programStage.displayName ?? programStage.id);
151
- }
152
- return createLookup(rules, fields, sections, stages);
153
- }
154
- function createLabelLookup(source) {
155
- return source.formKind === "event" ? buildEventLookup(source) : buildTrackerLookup(source);
156
- }
157
- var EFFECT_VARIANT = {
158
- HIDEFIELD: "hide",
159
- HIDEOPTION: "hide",
160
- HIDEOPTIONGROUP: "hide",
161
- HIDESECTION: "hide",
162
- HIDEPROGRAMSTAGE: "hide",
163
- SHOWFIELD: "show",
164
- SHOWOPTION: "show",
165
- SHOWOPTIONGROUP: "show",
166
- ASSIGN: "assign",
167
- SETMANDATORYFIELD: "mandatory",
168
- UNSETMANDATORYFIELD: "mandatory",
169
- SHOWWARNING: "warning",
170
- WARNINGONCOMPLETE: "warning",
171
- SHOWERROR: "error",
172
- ERRORONCOMPLETE: "error",
173
- DISPLAYTEXT: "feedback",
174
- DISPLAYKEYVALUEPAIR: "feedback",
175
- SENDMESSAGE: "message",
176
- SCHEDULEMESSAGE: "message",
177
- read: "read"
178
- };
179
- var EFFECT_VISUALS = {
180
- hide: {
181
- tagClassName: "bg-dhis2-grey-200 text-dhis2-grey-900",
182
- edgeStroke: "#494949",
183
- shortLabel: "hide"
184
- },
185
- show: {
186
- tagClassName: "bg-dhis2-green-100 text-dhis2-green-900",
187
- edgeStroke: "#388e3c",
188
- shortLabel: "show"
189
- },
190
- assign: {
191
- tagClassName: "bg-dhis2-blue-100 text-dhis2-blue-900",
192
- edgeStroke: "#1565c0",
193
- shortLabel: "assign"
194
- },
195
- mandatory: {
196
- tagClassName: "bg-dhis2-yellow-100 text-dhis2-yellow-900",
197
- edgeStroke: "#e56408",
198
- shortLabel: "required"
199
- },
200
- warning: {
201
- tagClassName: "bg-dhis2-yellow-100 text-dhis2-yellow-900",
202
- edgeStroke: "#ff8302",
203
- shortLabel: "warn"
204
- },
205
- error: {
206
- tagClassName: "bg-dhis2-red-100 text-dhis2-red-900",
207
- edgeStroke: "#c62828",
208
- shortLabel: "error"
209
- },
210
- feedback: {
211
- tagClassName: "bg-dhis2-purple-100 text-dhis2-purple-900",
212
- edgeStroke: "#9c27b0",
213
- shortLabel: "feedback"
214
- },
215
- message: {
216
- tagClassName: "bg-dhis2-teal-100 text-dhis2-teal-900",
217
- edgeStroke: "#00796b",
218
- shortLabel: "message"
219
- },
220
- read: {
221
- tagClassName: "bg-dhis2-teal-100 text-dhis2-teal-900",
222
- edgeStroke: "#00796b",
223
- shortLabel: "read"
224
- },
225
- default: {
226
- tagClassName: "bg-dhis2-grey-100 text-dhis2-grey-800",
227
- edgeStroke: "#494949",
228
- shortLabel: "effect"
93
+ var nodeKey, edgeKey;
94
+ var init_buildGraph = __esm({
95
+ "src/buildGraph.ts"() {
96
+ nodeKey = (kind, id) => `${kind}:${id}`;
97
+ edgeKey = (source, ruleId, target, effectType) => `${source}|${ruleId}|${target}|${effectType ?? ""}`;
229
98
  }
230
- };
231
- var EFFECT_ICONS = {
232
- hide: ui.IconViewOff16,
233
- show: ui.IconView16,
234
- assign: ui.IconEdit16,
235
- mandatory: ui.IconStarFilled16,
236
- warning: ui.IconWarningFilled16,
237
- error: ui.IconErrorFilled16,
238
- feedback: ui.IconMessages16,
239
- message: ui.IconMail16,
240
- read: ui.IconLink16,
241
- default: ui.IconInfo16
242
- };
99
+ });
243
100
  function getEffectVariant(type) {
244
101
  return EFFECT_VARIANT[type] ?? "default";
245
102
  }
@@ -247,7 +104,6 @@ function getEffectVisual(type) {
247
104
  const variant = getEffectVariant(type);
248
105
  return { variant, ...EFFECT_VISUALS[variant] };
249
106
  }
250
- var TAG_LAYOUT_CLASS = "max-w-full break-words";
251
107
  function tagPropsForVariant(variant) {
252
108
  switch (variant) {
253
109
  case "show":
@@ -287,12 +143,108 @@ function getEffectEdgeStroke(type, highlighted = true) {
287
143
  function getEffectShortLabel(type) {
288
144
  return getEffectVisual(type).shortLabel;
289
145
  }
146
+ var EFFECT_VARIANT, EFFECT_VISUALS; exports.EFFECT_ICONS = void 0; var TAG_LAYOUT_CLASS;
147
+ var init_effectStyles = __esm({
148
+ "src/effectStyles.ts"() {
149
+ EFFECT_VARIANT = {
150
+ HIDEFIELD: "hide",
151
+ HIDEOPTION: "hide",
152
+ HIDEOPTIONGROUP: "hide",
153
+ HIDESECTION: "hide",
154
+ HIDEPROGRAMSTAGE: "hide",
155
+ SHOWFIELD: "show",
156
+ SHOWOPTION: "show",
157
+ SHOWOPTIONGROUP: "show",
158
+ ASSIGN: "assign",
159
+ SETMANDATORYFIELD: "mandatory",
160
+ UNSETMANDATORYFIELD: "mandatory",
161
+ SHOWWARNING: "warning",
162
+ WARNINGONCOMPLETE: "warning",
163
+ SHOWERROR: "error",
164
+ ERRORONCOMPLETE: "error",
165
+ DISPLAYTEXT: "feedback",
166
+ DISPLAYKEYVALUEPAIR: "feedback",
167
+ SENDMESSAGE: "message",
168
+ SCHEDULEMESSAGE: "message",
169
+ read: "read"
170
+ };
171
+ EFFECT_VISUALS = {
172
+ hide: {
173
+ tagClassName: "bg-dhis2-grey-200 text-dhis2-grey-900",
174
+ edgeStroke: "#494949",
175
+ shortLabel: "hide"
176
+ },
177
+ show: {
178
+ tagClassName: "bg-dhis2-green-100 text-dhis2-green-900",
179
+ edgeStroke: "#388e3c",
180
+ shortLabel: "show"
181
+ },
182
+ assign: {
183
+ tagClassName: "bg-dhis2-blue-100 text-dhis2-blue-900",
184
+ edgeStroke: "#1565c0",
185
+ shortLabel: "assign"
186
+ },
187
+ mandatory: {
188
+ tagClassName: "bg-dhis2-yellow-100 text-dhis2-yellow-900",
189
+ edgeStroke: "#e56408",
190
+ shortLabel: "required"
191
+ },
192
+ warning: {
193
+ tagClassName: "bg-dhis2-yellow-100 text-dhis2-yellow-900",
194
+ edgeStroke: "#ff8302",
195
+ shortLabel: "warn"
196
+ },
197
+ error: {
198
+ tagClassName: "bg-dhis2-red-100 text-dhis2-red-900",
199
+ edgeStroke: "#c62828",
200
+ shortLabel: "error"
201
+ },
202
+ feedback: {
203
+ tagClassName: "bg-dhis2-purple-100 text-dhis2-purple-900",
204
+ edgeStroke: "#9c27b0",
205
+ shortLabel: "feedback"
206
+ },
207
+ message: {
208
+ tagClassName: "bg-dhis2-teal-100 text-dhis2-teal-900",
209
+ edgeStroke: "#00796b",
210
+ shortLabel: "message"
211
+ },
212
+ read: {
213
+ tagClassName: "bg-dhis2-teal-100 text-dhis2-teal-900",
214
+ edgeStroke: "#00796b",
215
+ shortLabel: "read"
216
+ },
217
+ default: {
218
+ tagClassName: "bg-dhis2-grey-100 text-dhis2-grey-800",
219
+ edgeStroke: "#494949",
220
+ shortLabel: "effect"
221
+ }
222
+ };
223
+ exports.EFFECT_ICONS = {
224
+ hide: ui.IconViewOff16,
225
+ show: ui.IconView16,
226
+ assign: ui.IconEdit16,
227
+ mandatory: ui.IconStarFilled16,
228
+ warning: ui.IconWarningFilled16,
229
+ error: ui.IconErrorFilled16,
230
+ feedback: ui.IconMessages16,
231
+ message: ui.IconMail16,
232
+ read: ui.IconLink16,
233
+ default: ui.IconInfo16
234
+ };
235
+ TAG_LAYOUT_CLASS = "max-w-full break-words";
236
+ }
237
+ });
290
238
  function translate(message, variables) {
291
239
  return String(i18n__default.default.t(message, variables));
292
240
  }
241
+ var init_i18n = __esm({
242
+ "src/i18n.ts"() {
243
+ }
244
+ });
293
245
  function EffectBadge({ type, children, className = "" }) {
294
246
  const variant = getEffectVariant(type);
295
- const Icon = EFFECT_ICONS[variant];
247
+ const Icon = exports.EFFECT_ICONS[variant];
296
248
  const tagProps = getEffectTagRenderProps(type);
297
249
  const label = children ?? type;
298
250
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -306,23 +258,13 @@ function EffectBadge({ type, children, className = "" }) {
306
258
  }
307
259
  );
308
260
  }
309
- var LEGEND_VARIANTS = [
310
- { variant: "read", label: translate("Read") },
311
- { variant: "hide", label: translate("Hide") },
312
- { variant: "show", label: translate("Show") },
313
- { variant: "assign", label: translate("Assign") },
314
- { variant: "mandatory", label: translate("Required") },
315
- { variant: "warning", label: translate("Warning") },
316
- { variant: "error", label: translate("Error") },
317
- { variant: "feedback", label: translate("Feedback") }
318
- ];
319
261
  function EffectLegend({ activeVariants }) {
320
262
  const items = LEGEND_VARIANTS.filter((item) => activeVariants.has(item.variant));
321
263
  if (!items.length) {
322
264
  return null;
323
265
  }
324
266
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-dp8 border-t border-dhis2-grey-200 px-dp12 py-dp8", children: items.map((item) => {
325
- const Icon = EFFECT_ICONS[item.variant];
267
+ const Icon = exports.EFFECT_ICONS[item.variant];
326
268
  const tagProps = getEffectTagRenderPropsForVariant(item.variant);
327
269
  return /* @__PURE__ */ jsxRuntime.jsx(
328
270
  ui.Tag,
@@ -336,115 +278,846 @@ function EffectLegend({ activeVariants }) {
336
278
  );
337
279
  }) });
338
280
  }
339
-
340
- // src/formatAgo.ts
341
- function formatAgo(timestamp, now = Date.now()) {
342
- const diffMs = Math.max(0, now - timestamp);
343
- if (diffMs < 5e3) {
344
- return translate("just now");
345
- }
346
- const seconds = Math.floor(diffMs / 1e3);
347
- if (seconds < 60) {
348
- return translate("{{seconds}}s ago", { seconds });
281
+ var LEGEND_VARIANTS;
282
+ var init_EffectBadge = __esm({
283
+ "src/EffectBadge.tsx"() {
284
+ init_effectStyles();
285
+ init_i18n();
286
+ LEGEND_VARIANTS = [
287
+ { variant: "read", label: translate("Read") },
288
+ { variant: "hide", label: translate("Hide") },
289
+ { variant: "show", label: translate("Show") },
290
+ { variant: "assign", label: translate("Assign") },
291
+ { variant: "mandatory", label: translate("Required") },
292
+ { variant: "warning", label: translate("Warning") },
293
+ { variant: "error", label: translate("Error") },
294
+ { variant: "feedback", label: translate("Feedback") }
295
+ ];
349
296
  }
350
- const minutes = Math.floor(seconds / 60);
351
- if (minutes < 60) {
352
- return translate("{{minutes}}m ago", { minutes });
297
+ });
298
+
299
+ // src/traceEntry.ts
300
+ function resolveGraphTraceEntry(entries, selectedEntryId) {
301
+ if (!entries.length) {
302
+ return null;
353
303
  }
354
- const hours = Math.floor(minutes / 60);
355
- if (hours < 24) {
356
- return translate("{{hours}}h ago", { hours });
304
+ if (selectedEntryId) {
305
+ return entries.find((entry) => entry.id === selectedEntryId) ?? entries[entries.length - 1];
357
306
  }
358
- return new Date(timestamp).toLocaleString();
307
+ return entries[entries.length - 1];
359
308
  }
360
-
361
- // src/formatRuleActionSummary.ts
362
- function resolveFieldLabel(id, displayName, labelLookup) {
363
- if (labelLookup) {
364
- const resolved = labelLookup.resolveFieldName(id);
365
- if (resolved !== id) {
366
- return resolved;
367
- }
309
+ function getActiveRuleIds(entries) {
310
+ const latest = resolveGraphTraceEntry(entries);
311
+ if (!latest) {
312
+ return /* @__PURE__ */ new Set();
368
313
  }
369
- return displayName ?? id;
314
+ return new Set(latest.ruleResults.map((result) => result.ruleId));
370
315
  }
371
- function resolveSectionLabel(id, displayName, labelLookup) {
372
- if (labelLookup) {
373
- const resolved = labelLookup.resolveSectionName(id);
374
- if (resolved !== id) {
375
- return resolved;
376
- }
316
+ var init_traceEntry = __esm({
317
+ "src/traceEntry.ts"() {
377
318
  }
378
- return displayName ?? id;
319
+ });
320
+
321
+ // src/graphLayout.ts
322
+ function median(values) {
323
+ const sorted = [...values].sort((a, b) => a - b);
324
+ const mid = Math.floor(sorted.length / 2);
325
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
379
326
  }
380
- function formatRuleActionSummary(action, labelLookup) {
381
- const type = action.programRuleActionType ?? "UNKNOWN";
382
- let targetLabel = "";
383
- if (action.dataElement?.id) {
384
- targetLabel = resolveFieldLabel(
385
- action.dataElement.id,
386
- action.dataElement.displayName,
387
- labelLookup
388
- );
389
- } else if (action.trackedEntityAttribute?.id) {
390
- targetLabel = resolveFieldLabel(
391
- action.trackedEntityAttribute.id,
392
- action.trackedEntityAttribute.displayName,
393
- labelLookup
394
- );
395
- } else if (action.programStageSection?.id) {
396
- targetLabel = resolveSectionLabel(
397
- action.programStageSection.id,
398
- action.programStageSection.displayName,
399
- labelLookup
400
- );
401
- } else if (action.programSection?.id) {
402
- targetLabel = resolveSectionLabel(
403
- action.programSection.id,
404
- action.programSection.displayName,
405
- labelLookup
406
- );
407
- } else if (type === "DISPLAYTEXT" || type === "DISPLAYKEYVALUEPAIR") {
408
- targetLabel = action.location ?? "feedback";
409
- }
410
- let detail;
411
- if (action.data) {
412
- detail = action.data;
413
- }
414
- if (action.content && (type === "DISPLAYTEXT" || type === "DISPLAYKEYVALUEPAIR")) {
415
- detail = action.content;
327
+ function staggerOffset(indexInBucket) {
328
+ if (indexInBucket === 0) {
329
+ return 0;
416
330
  }
417
- return { type, targetLabel, detail };
418
- }
419
-
420
- // src/resolveProgramRulesList.ts
421
- function sortByPriority(rules) {
422
- return [...rules].sort((left, right) => (left.priority ?? 0) - (right.priority ?? 0));
331
+ const magnitude = Math.ceil(indexInBucket / 2) * STAGGER;
332
+ return indexInBucket % 2 === 1 ? magnitude : -magnitude;
423
333
  }
424
- function toCatalogRule(rule) {
425
- return {
426
- id: rule.id,
427
- name: rule.name,
428
- condition: rule.condition,
429
- priority: rule.priority,
430
- programRuleActions: [...rule.programRuleActions ?? []],
431
- programStageId: rule.programStageId
432
- };
334
+ function columnFor(node) {
335
+ if (node.role === "target" && node.kind === "feedback") {
336
+ return FEEDBACK_COLUMN;
337
+ }
338
+ return ROLE_COLUMNS[node.role];
433
339
  }
434
- function resolveProgramRulesList(metadata) {
435
- if (metadata.formKind === "event") {
436
- return sortByPriority(
437
- metadata.metadata.programRules.filter((rule) => Boolean(rule.id)).map(
438
- (rule) => toCatalogRule({
439
- id: rule.id,
440
- name: rule.displayName ?? rule.id,
441
- condition: rule.condition,
442
- priority: rule.priority,
443
- programRuleActions: rule.programRuleActions,
444
- programStageId: rule.programStage?.id ?? null
445
- })
446
- )
447
- );
340
+ function prepareFlowGraph(graph) {
341
+ const readSources = /* @__PURE__ */ new Set();
342
+ const effectTargets = /* @__PURE__ */ new Set();
343
+ for (const edge of graph.edges) {
344
+ if (edge.effectType === "read") {
345
+ readSources.add(edge.source);
346
+ } else {
347
+ effectTargets.add(edge.target);
348
+ }
349
+ }
350
+ const flowNodes = [];
351
+ const endpointByGraphId = /* @__PURE__ */ new Map();
352
+ for (const node of graph.nodes) {
353
+ if (node.kind === "rule") {
354
+ flowNodes.push({
355
+ id: node.id,
356
+ graphNodeId: node.id,
357
+ kind: node.kind,
358
+ label: node.label,
359
+ role: "rule"
360
+ });
361
+ continue;
362
+ }
363
+ const isSource = readSources.has(node.id);
364
+ const isTarget = effectTargets.has(node.id);
365
+ const endpoints = {};
366
+ if (isSource) {
367
+ const flowId = isTarget ? `${node.id}@src` : node.id;
368
+ endpoints.source = flowId;
369
+ flowNodes.push({
370
+ id: flowId,
371
+ graphNodeId: node.id,
372
+ kind: node.kind,
373
+ label: node.label,
374
+ role: "source"
375
+ });
376
+ }
377
+ if (isTarget) {
378
+ const flowId = isSource ? `${node.id}@tgt` : node.id;
379
+ endpoints.target = flowId;
380
+ flowNodes.push({
381
+ id: flowId,
382
+ graphNodeId: node.id,
383
+ kind: node.kind,
384
+ label: node.label,
385
+ role: "target"
386
+ });
387
+ }
388
+ endpointByGraphId.set(node.id, endpoints);
389
+ }
390
+ const flowEdges = graph.edges.map((edge) => {
391
+ if (edge.effectType === "read") {
392
+ const endpoints2 = endpointByGraphId.get(edge.source);
393
+ return { ...edge, source: endpoints2?.source ?? edge.source };
394
+ }
395
+ const endpoints = endpointByGraphId.get(edge.target);
396
+ return { ...edge, target: endpoints?.target ?? edge.target };
397
+ });
398
+ return { nodes: flowNodes, edges: flowEdges };
399
+ }
400
+ function computeGraphLayout(flowNodes, flowEdges) {
401
+ const positions = /* @__PURE__ */ new Map();
402
+ const ruleLane = /* @__PURE__ */ new Map();
403
+ flowNodes.filter((node) => node.role === "rule").forEach((node, index) => {
404
+ ruleLane.set(node.id, index);
405
+ positions.set(node.id, { x: ROLE_COLUMNS.rule, y: index * LANE_HEIGHT });
406
+ });
407
+ const connectedRuleLanes = /* @__PURE__ */ new Map();
408
+ const addLane = (nodeId, lane) => {
409
+ const lanes = connectedRuleLanes.get(nodeId) ?? /* @__PURE__ */ new Set();
410
+ lanes.add(lane);
411
+ connectedRuleLanes.set(nodeId, lanes);
412
+ };
413
+ for (const edge of flowEdges) {
414
+ const sourceLane = ruleLane.get(edge.source);
415
+ const targetLane = ruleLane.get(edge.target);
416
+ if (targetLane !== void 0 && sourceLane === void 0) {
417
+ addLane(edge.source, targetLane);
418
+ }
419
+ if (sourceLane !== void 0 && targetLane === void 0) {
420
+ addLane(edge.target, sourceLane);
421
+ }
422
+ }
423
+ const bucketCounts = /* @__PURE__ */ new Map();
424
+ for (const node of flowNodes) {
425
+ if (node.role === "rule") {
426
+ continue;
427
+ }
428
+ const lanes = connectedRuleLanes.get(node.id);
429
+ const lane = lanes && lanes.size > 0 ? median([...lanes]) : 0;
430
+ const column = columnFor(node);
431
+ const bucketKey = `${String(column)}:${String(lane)}`;
432
+ const indexInBucket = bucketCounts.get(bucketKey) ?? 0;
433
+ bucketCounts.set(bucketKey, indexInBucket + 1);
434
+ positions.set(node.id, {
435
+ x: column,
436
+ y: lane * LANE_HEIGHT + staggerOffset(indexInBucket)
437
+ });
438
+ }
439
+ return positions;
440
+ }
441
+ var ROLE_COLUMNS, FEEDBACK_COLUMN, LANE_HEIGHT, STAGGER;
442
+ var init_graphLayout = __esm({
443
+ "src/graphLayout.ts"() {
444
+ ROLE_COLUMNS = {
445
+ source: 0,
446
+ rule: 280,
447
+ target: 560
448
+ };
449
+ FEEDBACK_COLUMN = 720;
450
+ LANE_HEIGHT = 104;
451
+ STAGGER = 24;
452
+ ({
453
+ field: ROLE_COLUMNS.source,
454
+ rule: ROLE_COLUMNS.rule,
455
+ section: ROLE_COLUMNS.target,
456
+ feedback: FEEDBACK_COLUMN
457
+ });
458
+ }
459
+ });
460
+
461
+ // src/graphNodeStyles.ts
462
+ function getGraphNodeClassName(kind, highlighted) {
463
+ return `p-dp8 rounded border bg-white min-w-28 max-w-[180px] text-xs shadow-[0_1px_2px_rgb(0_0_0/6%)] transition-opacity duration-150 ease-out cursor-grab active:cursor-grabbing ${GRAPH_NODE_CLASSES[kind]} ${highlighted ? "" : "opacity-35"}`;
464
+ }
465
+ function getLegendSwatchClassName(kind) {
466
+ return `size-2.5 shrink-0 rounded-sm ${LEGEND_SWATCH_CLASSES[kind]}`;
467
+ }
468
+ var GRAPH_NODE_CLASSES, LEGEND_SWATCH_CLASSES;
469
+ var init_graphNodeStyles = __esm({
470
+ "src/graphNodeStyles.ts"() {
471
+ GRAPH_NODE_CLASSES = {
472
+ field: "border-dhis2-blue-500",
473
+ rule: "border-dhis2-teal-600 bg-dhis2-teal-050",
474
+ section: "border-dhis2-grey-600",
475
+ feedback: "border-dhis2-purple-500"
476
+ };
477
+ LEGEND_SWATCH_CLASSES = {
478
+ field: "bg-dhis2-blue-500",
479
+ rule: "bg-dhis2-teal-600",
480
+ section: "bg-dhis2-grey-600",
481
+ feedback: "bg-dhis2-purple-500"
482
+ };
483
+ }
484
+ });
485
+
486
+ // src/RuleGraphView.tsx
487
+ var RuleGraphView_exports = {};
488
+ __export(RuleGraphView_exports, {
489
+ RuleGraphView: () => RuleGraphView,
490
+ toFlowGraph: () => toFlowGraph
491
+ });
492
+ function RuleGraphNode({ data }) {
493
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: getGraphNodeClassName(data.kind, data.highlighted), children: [
494
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "target", position: react$1.Position.Left }),
495
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-dp4 block text-[0.625rem] font-semibold uppercase leading-none tracking-wide text-dhis2-grey-600", children: KIND_LABELS[data.kind] }),
496
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "break-words font-semibold leading-[1.3]", children: data.label }),
497
+ data.value !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-dp4 border-t border-dhis2-grey-200 pt-dp4 font-mono text-[0.6875rem] break-all text-dhis2-grey-700", children: data.value }) : null,
498
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "source", position: react$1.Position.Right })
499
+ ] });
500
+ }
501
+ function RuleGraphEdge({
502
+ id,
503
+ sourceX,
504
+ sourceY,
505
+ sourcePosition,
506
+ targetX,
507
+ targetY,
508
+ targetPosition,
509
+ markerEnd,
510
+ style,
511
+ data
512
+ }) {
513
+ const [hovered, setHovered] = react.useState(false);
514
+ const [edgePath, labelX, labelY] = react$1.getSmoothStepPath({
515
+ sourceX,
516
+ sourceY,
517
+ sourcePosition,
518
+ targetX,
519
+ targetY,
520
+ targetPosition,
521
+ offset: data?.offset ?? 20
522
+ });
523
+ const showLabel = data ? data.showLabel || hovered : false;
524
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
525
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.BaseEdge, { id, path: edgePath, markerEnd, style }),
526
+ /* @__PURE__ */ jsxRuntime.jsx(
527
+ "path",
528
+ {
529
+ d: edgePath,
530
+ fill: "none",
531
+ stroke: "transparent",
532
+ strokeWidth: 16,
533
+ onMouseEnter: () => {
534
+ setHovered(true);
535
+ },
536
+ onMouseLeave: () => {
537
+ setHovered(false);
538
+ }
539
+ }
540
+ ),
541
+ showLabel && data ? /* @__PURE__ */ jsxRuntime.jsx(react$1.EdgeLabelRenderer, { children: /* @__PURE__ */ jsxRuntime.jsx(
542
+ "div",
543
+ {
544
+ className: "nodrag nopan absolute rounded-sm px-dp4 text-[10px] font-semibold",
545
+ style: {
546
+ transform: `translate(-50%, -50%) translate(${String(labelX)}px, ${String(labelY)}px)`,
547
+ background: "rgba(255,255,255,0.92)",
548
+ color: data.stroke,
549
+ pointerEvents: "all"
550
+ },
551
+ onMouseEnter: () => {
552
+ setHovered(true);
553
+ },
554
+ onMouseLeave: () => {
555
+ setHovered(false);
556
+ },
557
+ children: data.label
558
+ }
559
+ ) }) : null
560
+ ] });
561
+ }
562
+ function effectNodeKey(effect) {
563
+ const kind = effect.type === "HIDESECTION" ? "section" : effect.type === "DISPLAYTEXT" || effect.type === "DISPLAYKEYVALUEPAIR" ? "feedback" : "field";
564
+ return `${kind}:${effect.targetId}`;
565
+ }
566
+ function entryGraphKeys(entry) {
567
+ const keys = /* @__PURE__ */ new Set();
568
+ for (const fieldId of entry.changedFields) {
569
+ keys.add(`field:${fieldId}`);
570
+ }
571
+ for (const result of entry.ruleResults) {
572
+ keys.add(`rule:${result.ruleId}`);
573
+ for (const effect of result.effects) {
574
+ keys.add(effectNodeKey(effect));
575
+ }
576
+ }
577
+ return keys;
578
+ }
579
+ function ruleGraphKeys(entry, ruleId) {
580
+ const keys = /* @__PURE__ */ new Set([`rule:${ruleId}`]);
581
+ for (const result of entry.ruleResults) {
582
+ if (result.ruleId !== ruleId) {
583
+ continue;
584
+ }
585
+ for (const effect of result.effects) {
586
+ keys.add(effectNodeKey(effect));
587
+ }
588
+ }
589
+ return keys;
590
+ }
591
+ function toFlowGraph(graph, fieldState, formValues, highlightedKeys) {
592
+ const { nodes: flowNodes, edges: flowEdges } = prepareFlowGraph(graph);
593
+ const positions = computeGraphLayout(flowNodes, flowEdges);
594
+ const graphNodeIdByFlowId = new Map(flowNodes.map((node) => [node.id, node.graphNodeId]));
595
+ const nodes = flowNodes.map((node) => {
596
+ const position = positions.get(node.id) ?? { x: 0, y: 0 };
597
+ const rawFieldId = node.kind === "field" ? node.graphNodeId.slice("field:".length) : void 0;
598
+ const assignedValue = rawFieldId && rawFieldId in fieldState ? fieldState[rawFieldId].assignedValue : void 0;
599
+ const formValue = rawFieldId ? formValues[rawFieldId] : void 0;
600
+ const displayValue = formatDisplayValue(assignedValue) ?? formatDisplayValue(formValue);
601
+ return {
602
+ id: node.id,
603
+ type: "ruleGraphNode",
604
+ position,
605
+ data: {
606
+ label: node.label,
607
+ kind: node.kind,
608
+ graphNodeId: node.graphNodeId,
609
+ value: displayValue,
610
+ highlighted: highlightedKeys ? highlightedKeys.has(node.graphNodeId) : true
611
+ }
612
+ };
613
+ });
614
+ const dense = flowEdges.length > READ_LABEL_EDGE_THRESHOLD;
615
+ const sourceFanOut = /* @__PURE__ */ new Map();
616
+ const edges = flowEdges.map((edge) => {
617
+ const sourceGraphId = graphNodeIdByFlowId.get(edge.source) ?? edge.source;
618
+ const targetGraphId = graphNodeIdByFlowId.get(edge.target) ?? edge.target;
619
+ const isHighlighted = highlightedKeys ? highlightedKeys.has(sourceGraphId) && highlightedKeys.has(targetGraphId) : true;
620
+ const effectType = edge.effectType ?? "default";
621
+ const isRead = effectType === "read";
622
+ const stroke = getEffectEdgeStroke(effectType, isHighlighted);
623
+ const label = getEffectShortLabel(effectType);
624
+ const showLabel = !(isRead && dense);
625
+ const fanIndex = sourceFanOut.get(edge.source) ?? 0;
626
+ sourceFanOut.set(edge.source, fanIndex + 1);
627
+ const offset = 20 + fanIndex * 14;
628
+ return {
629
+ id: edge.id,
630
+ source: edge.source,
631
+ target: edge.target,
632
+ type: "ruleGraphEdge",
633
+ animated: isHighlighted && !isRead,
634
+ zIndex: isHighlighted ? 1 : 0,
635
+ style: {
636
+ stroke,
637
+ strokeWidth: isRead ? 1 : Math.min(1 + edge.fireCount, 4),
638
+ opacity: isHighlighted ? isRead ? 0.5 : 1 : 0.35
639
+ },
640
+ markerEnd: {
641
+ type: react$1.MarkerType.ArrowClosed,
642
+ color: stroke
643
+ },
644
+ data: {
645
+ label,
646
+ stroke,
647
+ showLabel,
648
+ offset
649
+ }
650
+ };
651
+ });
652
+ return { nodes, edges };
653
+ }
654
+ function GraphToolbar({
655
+ nodeCount,
656
+ edgeCount,
657
+ headerActions,
658
+ activeEffectVariants
659
+ }) {
660
+ const items = [
661
+ { kind: "field", label: translate("Field") },
662
+ { kind: "rule", label: translate("Rule") },
663
+ { kind: "section", label: translate("Section") },
664
+ { kind: "feedback", label: translate("Feedback") }
665
+ ];
666
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b border-dhis2-grey-200 bg-white", children: [
667
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-dp8 px-dp12 py-dp8", children: [
668
+ /* @__PURE__ */ jsxRuntime.jsx(
669
+ "div",
670
+ {
671
+ className: "flex min-w-0 flex-wrap items-center gap-x-dp12 gap-y-dp8",
672
+ "aria-hidden": "true",
673
+ children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
674
+ "span",
675
+ {
676
+ className: "inline-flex items-center gap-dp8 text-xs text-dhis2-grey-800",
677
+ children: [
678
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: getLegendSwatchClassName(item.kind) }),
679
+ item.label
680
+ ]
681
+ },
682
+ item.kind
683
+ ))
684
+ }
685
+ ),
686
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-dp8", children: [
687
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs tabular-nums text-dhis2-grey-600", children: translate("{{nodes}} nodes \xB7 {{edges}} edges", {
688
+ nodes: nodeCount,
689
+ edges: edgeCount
690
+ }) }),
691
+ headerActions
692
+ ] })
693
+ ] }),
694
+ /* @__PURE__ */ jsxRuntime.jsx(EffectLegend, { activeVariants: activeEffectVariants })
695
+ ] });
696
+ }
697
+ function FitViewOnChange({
698
+ layoutKey,
699
+ nodeCount,
700
+ edgeCount
701
+ }) {
702
+ const { fitView } = react$1.useReactFlow();
703
+ react.useEffect(() => {
704
+ const frameId = requestAnimationFrame(() => {
705
+ void fitView({ padding: 0.2, duration: 150 });
706
+ });
707
+ return () => {
708
+ cancelAnimationFrame(frameId);
709
+ };
710
+ }, [edgeCount, fitView, layoutKey, nodeCount]);
711
+ return null;
712
+ }
713
+ function mergeNodePositions(next, current) {
714
+ const currentById = new Map(current.map((node) => [node.id, node]));
715
+ return next.map((node) => {
716
+ const existing = currentById.get(node.id);
717
+ if (existing) {
718
+ return { ...node, position: existing.position };
719
+ }
720
+ return node;
721
+ });
722
+ }
723
+ function RuleGraphCanvas({
724
+ nodes: layoutNodes,
725
+ edges: layoutEdges,
726
+ layoutKey
727
+ }) {
728
+ const [nodes, setNodes, onNodesChange] = react$1.useNodesState(layoutNodes);
729
+ const [edges, setEdges, onEdgesChange] = react$1.useEdgesState(layoutEdges);
730
+ react.useEffect(() => {
731
+ setNodes((current) => mergeNodePositions(layoutNodes, current));
732
+ }, [layoutNodes, setNodes]);
733
+ react.useEffect(() => {
734
+ setEdges(layoutEdges);
735
+ }, [layoutEdges, setEdges]);
736
+ return /* @__PURE__ */ jsxRuntime.jsxs(
737
+ react$1.ReactFlow,
738
+ {
739
+ nodes,
740
+ edges,
741
+ nodeTypes,
742
+ edgeTypes,
743
+ onNodesChange,
744
+ onEdgesChange,
745
+ nodesDraggable: true,
746
+ defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
747
+ elevateEdgesOnSelect: true,
748
+ fitView: true,
749
+ proOptions: { hideAttribution: true },
750
+ className: "h-full w-full",
751
+ children: [
752
+ /* @__PURE__ */ jsxRuntime.jsx(
753
+ FitViewOnChange,
754
+ {
755
+ layoutKey,
756
+ nodeCount: nodes.length,
757
+ edgeCount: edges.length
758
+ }
759
+ ),
760
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Background, { gap: 16, size: 1 }),
761
+ /* @__PURE__ */ jsxRuntime.jsx(react$1.Controls, { showInteractive: false })
762
+ ]
763
+ }
764
+ );
765
+ }
766
+ function RuleGraphView({
767
+ entries,
768
+ fieldState,
769
+ formValues,
770
+ selectedEntryId,
771
+ highlightRuleId,
772
+ labelLookup,
773
+ className,
774
+ layoutKey,
775
+ headerActions,
776
+ minHeightClassName = "min-h-[360px]"
777
+ }) {
778
+ const graphEntry = react.useMemo(
779
+ () => resolveGraphTraceEntry(entries, selectedEntryId),
780
+ [entries, selectedEntryId]
781
+ );
782
+ const graph = react.useMemo(
783
+ () => graphEntry ? buildGraphFromTrace([graphEntry], labelLookup) : { nodes: [], edges: [] },
784
+ [graphEntry, labelLookup]
785
+ );
786
+ const highlightedKeys = react.useMemo(() => {
787
+ if (!graphEntry || !selectedEntryId && !highlightRuleId) {
788
+ return null;
789
+ }
790
+ const keys = /* @__PURE__ */ new Set();
791
+ if (selectedEntryId) {
792
+ for (const key of entryGraphKeys(graphEntry)) {
793
+ keys.add(key);
794
+ }
795
+ }
796
+ if (highlightRuleId) {
797
+ for (const key of ruleGraphKeys(graphEntry, highlightRuleId)) {
798
+ keys.add(key);
799
+ }
800
+ }
801
+ return keys;
802
+ }, [graphEntry, highlightRuleId, selectedEntryId]);
803
+ const { nodes, edges } = react.useMemo(
804
+ () => toFlowGraph(graph, fieldState, formValues, highlightedKeys),
805
+ [graph, fieldState, formValues, highlightedKeys]
806
+ );
807
+ const activeEffectVariants = react.useMemo(() => {
808
+ const variants = /* @__PURE__ */ new Set();
809
+ for (const edge of graph.edges) {
810
+ if (edge.effectType) {
811
+ variants.add(getEffectVariant(edge.effectType));
812
+ }
813
+ }
814
+ return variants;
815
+ }, [graph.edges]);
816
+ if (!graph.nodes.length) {
817
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-dp8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.NoticeBox, { title: translate("No rule relationships yet"), children: translate(
818
+ "Interact with the form to build the dependency graph. Only rules active in the latest evaluation are shown. Select a trace entry to inspect a past evaluation. Connections flow Field \u2192 Rule \u2192 Target (read / effect)."
819
+ ) }) });
820
+ }
821
+ return /* @__PURE__ */ jsxRuntime.jsxs(
822
+ "div",
823
+ {
824
+ className: `flex h-full min-h-[480px] flex-1 flex-col overflow-hidden rounded-md border border-dhis2-grey-200 bg-white ${className ?? ""}`,
825
+ children: [
826
+ /* @__PURE__ */ jsxRuntime.jsx(
827
+ GraphToolbar,
828
+ {
829
+ nodeCount: graph.nodes.length,
830
+ edgeCount: graph.edges.length,
831
+ headerActions,
832
+ activeEffectVariants
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsxRuntime.jsx(
836
+ "div",
837
+ {
838
+ className: `${minHeightClassName} flex-1 bg-white`,
839
+ style: { width: "100%", height: "100%" },
840
+ children: /* @__PURE__ */ jsxRuntime.jsx(react$1.ReactFlowProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(RuleGraphCanvas, { nodes, edges, layoutKey }) })
841
+ }
842
+ )
843
+ ]
844
+ }
845
+ );
846
+ }
847
+ var KIND_LABELS, READ_LABEL_EDGE_THRESHOLD, DEFAULT_EDGE_OPTIONS, nodeTypes, edgeTypes, formatDisplayValue;
848
+ var init_RuleGraphView = __esm({
849
+ "src/RuleGraphView.tsx"() {
850
+ init_buildGraph();
851
+ init_graphLayout();
852
+ init_EffectBadge();
853
+ init_effectStyles();
854
+ init_graphNodeStyles();
855
+ init_i18n();
856
+ init_traceEntry();
857
+ KIND_LABELS = {
858
+ field: "Field",
859
+ rule: "Rule",
860
+ section: "Section",
861
+ feedback: "Feedback"
862
+ };
863
+ READ_LABEL_EDGE_THRESHOLD = 4;
864
+ DEFAULT_EDGE_OPTIONS = {
865
+ type: "ruleGraphEdge",
866
+ style: { stroke: getEffectEdgeStroke("default") },
867
+ markerEnd: {
868
+ type: react$1.MarkerType.ArrowClosed,
869
+ color: getEffectEdgeStroke("default")
870
+ }
871
+ };
872
+ nodeTypes = {
873
+ ruleGraphNode: react.memo(RuleGraphNode)
874
+ };
875
+ edgeTypes = {
876
+ ruleGraphEdge: react.memo(RuleGraphEdge)
877
+ };
878
+ formatDisplayValue = (value) => {
879
+ if (value === void 0 || value === null || value === "") {
880
+ return void 0;
881
+ }
882
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
883
+ return String(value);
884
+ }
885
+ return JSON.stringify(value);
886
+ };
887
+ }
888
+ });
889
+
890
+ // src/RuleGraphModal.tsx
891
+ var RuleGraphModal_exports = {};
892
+ __export(RuleGraphModal_exports, {
893
+ RuleGraphModal: () => RuleGraphModal
894
+ });
895
+ function RuleGraphModal({
896
+ open,
897
+ onClose,
898
+ subtitle,
899
+ layoutKey,
900
+ ...graphProps
901
+ }) {
902
+ if (!open) {
903
+ return null;
904
+ }
905
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Modal, { large: true, fluid: true, onClose, children: [
906
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.ModalTitle, { children: [
907
+ translate("Rule dependency graph"),
908
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-dp4 block text-sm font-normal text-dhis2-grey-700", children: subtitle }) : null
909
+ ] }),
910
+ /* @__PURE__ */ jsxRuntime.jsx(ui.ModalContent, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(85vh,900px)] w-full min-w-[min(1200px,92vw)]", children: /* @__PURE__ */ jsxRuntime.jsx(
911
+ RuleGraphView,
912
+ {
913
+ ...graphProps,
914
+ layoutKey: layoutKey ?? "modal",
915
+ className: "h-full min-h-0 rounded-none border-0",
916
+ minHeightClassName: "min-h-0 h-full"
917
+ }
918
+ ) }) })
919
+ ] });
920
+ }
921
+ var init_RuleGraphModal = __esm({
922
+ "src/RuleGraphModal.tsx"() {
923
+ init_i18n();
924
+ init_RuleGraphView();
925
+ }
926
+ });
927
+
928
+ // src/RulesPanel.tsx
929
+ init_buildGraph();
930
+ var createLookup = (rules, fields, sections, stages) => ({
931
+ resolveRuleName: (id) => rules.get(id) ?? id,
932
+ resolveFieldName: (id) => fields.get(id) ?? id,
933
+ resolveSectionName: (id) => sections.get(id) ?? id,
934
+ resolveStageName: (id) => stages.get(id) ?? id
935
+ });
936
+ function buildEventLookup(source) {
937
+ const rules = /* @__PURE__ */ new Map();
938
+ const fields = /* @__PURE__ */ new Map();
939
+ const sections = /* @__PURE__ */ new Map();
940
+ const stages = /* @__PURE__ */ new Map();
941
+ for (const rule of source.metadata.programRules) {
942
+ if (!rule.id) {
943
+ continue;
944
+ }
945
+ rules.set(rule.id, rule.displayName ?? rule.id);
946
+ }
947
+ const stage = dhis2FormUtilsMetadata.selectProgramStage(
948
+ source.metadata,
949
+ source.programStageId
950
+ ) ?? {
951
+ id: source.programStageId,
952
+ programStageDataElements: [],
953
+ programStageSections: []
954
+ };
955
+ for (const psde of stage.programStageDataElements ?? []) {
956
+ const de = psde.dataElement;
957
+ if (!de?.id) {
958
+ continue;
959
+ }
960
+ fields.set(de.id, de.displayFormName ?? de.displayName ?? de.id);
961
+ }
962
+ for (const section of stage.programStageSections ?? []) {
963
+ sections.set(section.id, section.displayName ?? section.id);
964
+ }
965
+ for (const programStage of source.metadata.programStages) {
966
+ if (!programStage.id) {
967
+ continue;
968
+ }
969
+ stages.set(programStage.id, programStage.displayName ?? programStage.id);
970
+ }
971
+ return createLookup(rules, fields, sections, stages);
972
+ }
973
+ function buildTrackerLookup(source) {
974
+ const rules = /* @__PURE__ */ new Map();
975
+ const fields = /* @__PURE__ */ new Map();
976
+ const sections = /* @__PURE__ */ new Map();
977
+ const stages = /* @__PURE__ */ new Map();
978
+ for (const rule of source.metadata.programRules) {
979
+ if (!rule.id) {
980
+ continue;
981
+ }
982
+ rules.set(rule.id, rule.name ?? rule.id);
983
+ }
984
+ for (const ptea of source.metadata.programTrackedEntityAttributes) {
985
+ const tea = ptea.trackedEntityAttribute;
986
+ if (!tea.id) {
987
+ continue;
988
+ }
989
+ fields.set(tea.id, tea.formName ?? tea.displayName ?? tea.id);
990
+ }
991
+ for (const section of source.metadata.programSections ?? []) {
992
+ sections.set(section.id, section.displayName ?? section.id);
993
+ }
994
+ for (const programStage of source.programStages ?? []) {
995
+ if (!programStage.id) {
996
+ continue;
997
+ }
998
+ stages.set(programStage.id, programStage.displayName ?? programStage.id);
999
+ }
1000
+ return createLookup(rules, fields, sections, stages);
1001
+ }
1002
+ function createLabelLookup(source) {
1003
+ return source.formKind === "event" ? buildEventLookup(source) : buildTrackerLookup(source);
1004
+ }
1005
+
1006
+ // src/RulesPanel.tsx
1007
+ init_EffectBadge();
1008
+
1009
+ // src/formatAgo.ts
1010
+ init_i18n();
1011
+ function formatAgo(timestamp, now = Date.now()) {
1012
+ const diffMs = Math.max(0, now - timestamp);
1013
+ if (diffMs < 5e3) {
1014
+ return translate("just now");
1015
+ }
1016
+ const seconds = Math.floor(diffMs / 1e3);
1017
+ if (seconds < 60) {
1018
+ return translate("{{seconds}}s ago", { seconds });
1019
+ }
1020
+ const minutes = Math.floor(seconds / 60);
1021
+ if (minutes < 60) {
1022
+ return translate("{{minutes}}m ago", { minutes });
1023
+ }
1024
+ const hours = Math.floor(minutes / 60);
1025
+ if (hours < 24) {
1026
+ return translate("{{hours}}h ago", { hours });
1027
+ }
1028
+ return new Date(timestamp).toLocaleString();
1029
+ }
1030
+
1031
+ // src/formatRuleActionSummary.ts
1032
+ function resolveFieldLabel(id, displayName, labelLookup) {
1033
+ if (labelLookup) {
1034
+ const resolved = labelLookup.resolveFieldName(id);
1035
+ if (resolved !== id) {
1036
+ return resolved;
1037
+ }
1038
+ }
1039
+ return displayName ?? id;
1040
+ }
1041
+ function resolveSectionLabel(id, displayName, labelLookup) {
1042
+ if (labelLookup) {
1043
+ const resolved = labelLookup.resolveSectionName(id);
1044
+ if (resolved !== id) {
1045
+ return resolved;
1046
+ }
1047
+ }
1048
+ return displayName ?? id;
1049
+ }
1050
+ function formatRuleActionSummary(action, labelLookup) {
1051
+ const type = action.programRuleActionType ?? "UNKNOWN";
1052
+ let targetLabel = "";
1053
+ if (action.dataElement?.id) {
1054
+ targetLabel = resolveFieldLabel(
1055
+ action.dataElement.id,
1056
+ action.dataElement.displayName,
1057
+ labelLookup
1058
+ );
1059
+ } else if (action.trackedEntityAttribute?.id) {
1060
+ targetLabel = resolveFieldLabel(
1061
+ action.trackedEntityAttribute.id,
1062
+ action.trackedEntityAttribute.displayName,
1063
+ labelLookup
1064
+ );
1065
+ } else if (action.programStageSection?.id) {
1066
+ targetLabel = resolveSectionLabel(
1067
+ action.programStageSection.id,
1068
+ action.programStageSection.displayName,
1069
+ labelLookup
1070
+ );
1071
+ } else if (action.programSection?.id) {
1072
+ targetLabel = resolveSectionLabel(
1073
+ action.programSection.id,
1074
+ action.programSection.displayName,
1075
+ labelLookup
1076
+ );
1077
+ } else if (type === "DISPLAYTEXT" || type === "DISPLAYKEYVALUEPAIR") {
1078
+ targetLabel = action.location ?? "feedback";
1079
+ }
1080
+ let detail;
1081
+ if (action.data) {
1082
+ detail = action.data;
1083
+ }
1084
+ if (action.content && (type === "DISPLAYTEXT" || type === "DISPLAYKEYVALUEPAIR")) {
1085
+ detail = action.content;
1086
+ }
1087
+ return { type, targetLabel, detail };
1088
+ }
1089
+
1090
+ // src/RulesPanel.tsx
1091
+ init_i18n();
1092
+
1093
+ // src/resolveProgramRulesList.ts
1094
+ function sortByPriority(rules) {
1095
+ return [...rules].sort((left, right) => (left.priority ?? 0) - (right.priority ?? 0));
1096
+ }
1097
+ function toCatalogRule(rule) {
1098
+ return {
1099
+ id: rule.id,
1100
+ name: rule.name,
1101
+ condition: rule.condition,
1102
+ priority: rule.priority,
1103
+ programRuleActions: [...rule.programRuleActions ?? []],
1104
+ programStageId: rule.programStageId
1105
+ };
1106
+ }
1107
+ function resolveProgramRulesList(metadata) {
1108
+ if (metadata.formKind === "event") {
1109
+ return sortByPriority(
1110
+ metadata.metadata.programRules.filter((rule) => Boolean(rule.id)).map(
1111
+ (rule) => toCatalogRule({
1112
+ id: rule.id,
1113
+ name: rule.displayName ?? rule.id,
1114
+ condition: rule.condition,
1115
+ priority: rule.priority,
1116
+ programRuleActions: rule.programRuleActions,
1117
+ programStageId: rule.programStage?.id ?? null
1118
+ })
1119
+ )
1120
+ );
448
1121
  }
449
1122
  return sortByPriority(
450
1123
  metadata.metadata.programRules.filter((rule) => Boolean(rule.id)).map(
@@ -524,6 +1197,10 @@ function useRuleTraceStore() {
524
1197
  return store;
525
1198
  }
526
1199
 
1200
+ // src/RuleDetailsModal.tsx
1201
+ init_effectStyles();
1202
+ init_i18n();
1203
+
527
1204
  // src/programRuleDetailQuery.ts
528
1205
  var PROGRAM_RULE_ACTION_DETAIL_FIELDS = [
529
1206
  "id",
@@ -704,860 +1381,300 @@ function BasicDetailCell({ label, value, mono }) {
704
1381
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
705
1382
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-xs text-dhis2-grey-600", children: label }),
706
1383
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: `m-0 mt-[2px] text-sm text-dhis2-grey-900 ${mono ? "font-mono" : ""}`, children: value })
707
- ] });
708
- }
709
- var FEEDBACK_ACTION_TYPES = /* @__PURE__ */ new Set(["DISPLAYTEXT", "DISPLAYKEYVALUEPAIR"]);
710
- function resolveDataRow(action) {
711
- return action.data ? { label: translate("Data (expression)"), value: action.data, mono: true } : null;
712
- }
713
- function resolveContentRow(action) {
714
- const showContent = Boolean(action.content) && FEEDBACK_ACTION_TYPES.has(action.programRuleActionType);
715
- return showContent ? { label: translate("Content (static text)"), value: action.content ?? "" } : null;
716
- }
717
- function resolveLocationRow(action) {
718
- return action.location ? { label: translate("Location"), value: action.location, mono: true } : null;
719
- }
720
- function resolveActionRows(action) {
721
- const rows = [
722
- resolveActionTarget(action),
723
- resolveDataRow(action),
724
- resolveContentRow(action),
725
- resolveLocationRow(action)
726
- ];
727
- return rows.filter((row) => row !== null);
728
- }
729
- function ActionCard({ action, index }) {
730
- const type = action.programRuleActionType;
731
- const visual = getEffectVisual(type);
732
- const Icon = EFFECT_ICONS[visual.variant];
733
- const rows = resolveActionRows(action);
734
- return /* @__PURE__ */ jsxRuntime.jsxs(
735
- "div",
736
- {
737
- className: "relative rounded-[3px] border border-dhis2-grey-300 bg-white py-dp12 pe-dp16 ps-dp16 shadow-[0_1px_2px_rgb(0_0_0/4%)]",
738
- style: { borderInlineStartWidth: 3, borderInlineStartColor: visual.edgeStroke },
739
- children: [
740
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-dp12 flex items-center justify-between gap-dp8", children: [
741
- /* @__PURE__ */ jsxRuntime.jsxs(
742
- "span",
743
- {
744
- className: `inline-flex items-center gap-[4px] rounded-[4px] px-dp8 py-[2px] text-xs font-semibold ${visual.tagClassName}`,
745
- children: [
746
- /* @__PURE__ */ jsxRuntime.jsx(Icon, {}),
747
- type
748
- ]
749
- }
750
- ),
751
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] uppercase tracking-wide text-dhis2-grey-600", children: translate("Action {{n}}", { n: index + 1 }) })
752
- ] }),
753
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-dp10", children: rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[172px_minmax(0,1fr)] gap-dp10", children: [
754
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-dhis2-grey-600", children: row.label }),
755
- /* @__PURE__ */ jsxRuntime.jsx(
756
- "span",
757
- {
758
- className: `min-w-0 break-words text-sm ${row.mono ? "font-mono text-[12px]" : ""}`,
759
- children: row.value
760
- }
761
- )
762
- ] }, row.label)) })
763
- ]
764
- }
765
- );
766
- }
767
- function orDash(value) {
768
- return value ?? EM_DASH;
769
- }
770
- function resolveDetailDisplayName(rule) {
771
- return rule?.displayName;
772
- }
773
- function resolveDetailName(rule) {
774
- return rule?.name;
775
- }
776
- function resolveRuleDisplayName(rule, fallback) {
777
- const displayName = resolveDetailDisplayName(rule) ?? resolveDetailName(rule);
778
- return displayName ?? fallback;
779
- }
780
- function resolveRuleStageName(rule) {
781
- return rule?.programStage?.displayName;
782
- }
783
- function resolveStageName(rule, programStageName) {
784
- if (programStageName === null) {
785
- return translate("All stages (registration)");
786
- }
787
- return orDash(resolveRuleStageName(rule) ?? programStageName);
788
- }
789
- function resolveLastUpdatedByName(rule) {
790
- return rule?.lastUpdatedBy?.displayName;
791
- }
792
- function resolveLastUpdatedValue(rule) {
793
- const label = formatTimestamp(rule?.lastUpdated);
794
- if (!label) {
795
- return EM_DASH;
796
- }
797
- const by = resolveLastUpdatedByName(rule);
798
- return by ? `${label} \xB7 ${by}` : label;
799
- }
800
- function resolvePriorityCellValue(rule) {
801
- return rule?.priority != null ? String(rule.priority) : EM_DASH;
802
- }
803
- function resolveActionsCellValue(rule) {
804
- return translate("{{n}} action(s)", { n: rule?.programRuleActions?.length ?? 0 });
805
- }
806
- function resolveBasicDetailCells(rule, ruleName, programStageName) {
807
- return [
808
- { label: translate("Name"), value: resolveRuleDisplayName(rule, ruleName) },
809
- { label: translate("Code"), value: orDash(rule?.code), mono: true },
810
- { label: translate("Identifier"), value: orDash(rule?.id), mono: true },
811
- { label: translate("Program"), value: orDash(rule?.program?.displayName) },
812
- { label: translate("Program stage"), value: resolveStageName(rule, programStageName) },
813
- { label: translate("Priority"), value: resolvePriorityCellValue(rule) },
814
- { label: translate("Actions"), value: resolveActionsCellValue(rule) },
815
- { label: translate("Last updated"), value: resolveLastUpdatedValue(rule) }
816
- ];
817
- }
818
- function BasicDetails({
819
- rule,
820
- ruleName,
821
- programStageName
822
- }) {
823
- const cells = resolveBasicDetailCells(rule, ruleName, programStageName);
824
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-x-dp24 gap-y-dp12 rounded-[3px] border border-dhis2-grey-300 bg-dhis2-grey-050 p-dp16", children: cells.map((cell) => /* @__PURE__ */ jsxRuntime.jsx(BasicDetailCell, { ...cell }, cell.label)) });
825
- }
826
- var SECTION_HEADING_CLASS = "m-0 mb-dp8 text-[13px] font-bold uppercase tracking-wide text-dhis2-grey-700";
827
- function RuleDetailsHeader({
828
- title,
829
- chip,
830
- description
831
- }) {
832
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "-mx-dp24 -mt-dp24 mb-0 border-b border-dhis2-grey-300 px-dp24 pb-dp16 pe-[44px] pt-[20px]", children: [
833
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-[11px] font-bold uppercase tracking-[.09em] text-dhis2-grey-600", children: translate("Program rule") }),
834
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-[6px] flex flex-wrap items-center gap-dp8", children: [
835
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 text-xl font-medium leading-[1.3] text-dhis2-grey-900", children: title }),
836
- /* @__PURE__ */ jsxRuntime.jsx(
837
- "span",
838
- {
839
- className: `rounded-[4px] px-dp8 py-[2px] text-xs font-semibold ${chip.className}`,
840
- children: chip.label
841
- }
842
- )
843
- ] }),
844
- description ? /* @__PURE__ */ jsxRuntime.jsx(
845
- "p",
846
- {
847
- className: "m-0 mt-[6px] text-sm text-dhis2-grey-700",
848
- style: { textWrap: "pretty" },
849
- children: description
850
- }
851
- ) : null
852
- ] });
853
- }
854
- function ConditionSection({
855
- condition,
856
- variables
857
- }) {
858
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
859
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Condition") }),
860
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap break-words rounded-[3px] border border-dhis2-grey-300 bg-dhis2-grey-200 px-dp16 py-[14px] font-mono text-[13px] leading-[1.6] text-dhis2-grey-900", children: condition ?? EM_DASH }),
861
- variables.length ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-dp12", children: [
862
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 mb-dp8 text-xs text-dhis2-grey-600", children: translate("Variables referenced") }),
863
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-dp8", children: variables.map((variable) => /* @__PURE__ */ jsxRuntime.jsxs(
864
- "span",
865
- {
866
- className: `inline-flex items-center gap-[4px] rounded-full px-dp8 py-[2px] text-xs ${variable.className}`,
867
- children: [
868
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-medium", children: variable.label }),
869
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-70", children: translate(variable.kind) })
870
- ]
871
- },
872
- variable.token
873
- )) })
874
- ] }) : null
875
- ] });
876
- }
877
- function ActionsSection({ actions }) {
878
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
879
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Actions") }),
880
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-dp12", children: actions.map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(ActionCard, { action, index }, action.id)) })
881
- ] });
882
- }
883
- function RuleDetailsContent({
884
- detail,
885
- ruleName,
886
- programStageName,
887
- variables
888
- }) {
889
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-dp24 pt-dp16", children: [
890
- /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
891
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Basic details") }),
892
- /* @__PURE__ */ jsxRuntime.jsx(
893
- BasicDetails,
894
- {
895
- rule: detail,
896
- ruleName,
897
- programStageName
898
- }
899
- )
900
- ] }),
901
- /* @__PURE__ */ jsxRuntime.jsx(ConditionSection, { condition: detail.condition, variables }),
902
- /* @__PURE__ */ jsxRuntime.jsx(ActionsSection, { actions: detail.programRuleActions ?? [] })
903
- ] });
904
- }
905
- function RuleDetailsBody({
906
- detail,
907
- loading,
908
- error,
909
- ruleName,
910
- programStageName,
911
- variables
912
- }) {
913
- if (loading) {
914
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[280px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.CircularLoader, { small: true }) });
915
- }
916
- if (error) {
917
- return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm text-dhis2-red-700", children: translate("Could not load this rule: {{message}}", { message: error.message }) });
918
- }
919
- if (!detail) {
920
- return null;
921
- }
922
- return /* @__PURE__ */ jsxRuntime.jsx(
923
- RuleDetailsContent,
924
- {
925
- detail,
926
- ruleName,
927
- programStageName,
928
- variables
929
- }
930
- );
1384
+ ] });
931
1385
  }
932
- function RuleDetailsFooter({ onClose }) {
1386
+ var FEEDBACK_ACTION_TYPES = /* @__PURE__ */ new Set(["DISPLAYTEXT", "DISPLAYKEYVALUEPAIR"]);
1387
+ function resolveDataRow(action) {
1388
+ return action.data ? { label: translate("Data (expression)"), value: action.data, mono: true } : null;
1389
+ }
1390
+ function resolveContentRow(action) {
1391
+ const showContent = Boolean(action.content) && FEEDBACK_ACTION_TYPES.has(action.programRuleActionType);
1392
+ return showContent ? { label: translate("Content (static text)"), value: action.content ?? "" } : null;
1393
+ }
1394
+ function resolveLocationRow(action) {
1395
+ return action.location ? { label: translate("Location"), value: action.location, mono: true } : null;
1396
+ }
1397
+ function resolveActionRows(action) {
1398
+ const rows = [
1399
+ resolveActionTarget(action),
1400
+ resolveDataRow(action),
1401
+ resolveContentRow(action),
1402
+ resolveLocationRow(action)
1403
+ ];
1404
+ return rows.filter((row) => row !== null);
1405
+ }
1406
+ function ActionCard({ action, index }) {
1407
+ const type = action.programRuleActionType;
1408
+ const visual = getEffectVisual(type);
1409
+ const Icon = exports.EFFECT_ICONS[visual.variant];
1410
+ const rows = resolveActionRows(action);
933
1411
  return /* @__PURE__ */ jsxRuntime.jsxs(
934
1412
  "div",
935
1413
  {
936
- style: { order: 3 },
937
- className: "-mx-dp24 -mb-dp24 mt-dp16 flex items-center justify-between gap-dp12 border-t border-dhis2-grey-300 bg-dhis2-grey-050 px-dp24 py-[14px]",
1414
+ className: "relative rounded-[3px] border border-dhis2-grey-300 bg-white py-dp12 pe-dp16 ps-dp16 shadow-[0_1px_2px_rgb(0_0_0/4%)]",
1415
+ style: { borderInlineStartWidth: 3, borderInlineStartColor: visual.edgeStroke },
938
1416
  children: [
939
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm text-dhis2-grey-600", children: translate("Read-only view. Edit rules in the Maintenance app.") }),
940
- /* @__PURE__ */ jsxRuntime.jsx(ui.ButtonStrip, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { secondary: true, onClick: onClose, children: translate("Close") }) })
1417
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-dp12 flex items-center justify-between gap-dp8", children: [
1418
+ /* @__PURE__ */ jsxRuntime.jsxs(
1419
+ "span",
1420
+ {
1421
+ className: `inline-flex items-center gap-[4px] rounded-[4px] px-dp8 py-[2px] text-xs font-semibold ${visual.tagClassName}`,
1422
+ children: [
1423
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, {}),
1424
+ type
1425
+ ]
1426
+ }
1427
+ ),
1428
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] uppercase tracking-wide text-dhis2-grey-600", children: translate("Action {{n}}", { n: index + 1 }) })
1429
+ ] }),
1430
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-dp10", children: rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[172px_minmax(0,1fr)] gap-dp10", children: [
1431
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-dhis2-grey-600", children: row.label }),
1432
+ /* @__PURE__ */ jsxRuntime.jsx(
1433
+ "span",
1434
+ {
1435
+ className: `min-w-0 break-words text-sm ${row.mono ? "font-mono text-[12px]" : ""}`,
1436
+ children: row.value
1437
+ }
1438
+ )
1439
+ ] }, row.label)) })
941
1440
  ]
942
1441
  }
943
1442
  );
944
1443
  }
945
- function resolveActiveRuleId(open, ruleId) {
946
- return open ? ruleId : null;
947
- }
948
- function shouldRenderModal(open, ruleId) {
949
- return open && ruleId != null;
950
- }
951
- function RuleDetailsModal({
952
- open,
953
- onClose,
954
- ruleId,
955
- ruleName,
956
- status,
957
- programStageName,
958
- programRuleVariables
959
- }) {
960
- const { detail, loading, error } = useProgramRuleDetail(resolveActiveRuleId(open, ruleId));
961
- if (!shouldRenderModal(open, ruleId)) {
962
- return null;
963
- }
964
- const chip = resolveStatusChip(status);
965
- const variables = parseConditionVariables(detail?.condition, programRuleVariables);
966
- const title = resolveRuleDisplayName(detail, ruleName);
967
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Modal, { position: "middle", onClose, children: [
968
- /* @__PURE__ */ jsxRuntime.jsx(RuleDetailsHeader, { title, chip, description: detail?.description }),
969
- /* @__PURE__ */ jsxRuntime.jsx(ui.ModalContent, { children: /* @__PURE__ */ jsxRuntime.jsx(
970
- RuleDetailsBody,
971
- {
972
- detail,
973
- loading,
974
- error,
975
- ruleName,
976
- programStageName,
977
- variables
978
- }
979
- ) }),
980
- /* @__PURE__ */ jsxRuntime.jsx(RuleDetailsFooter, { onClose })
981
- ] });
1444
+ function orDash(value) {
1445
+ return value ?? EM_DASH;
982
1446
  }
983
-
984
- // src/graphLayout.ts
985
- var ROLE_COLUMNS = {
986
- source: 0,
987
- rule: 280,
988
- target: 560
989
- };
990
- var FEEDBACK_COLUMN = 720;
991
- var LANE_HEIGHT = 104;
992
- var STAGGER = 24;
993
- function median(values) {
994
- const sorted = [...values].sort((a, b) => a - b);
995
- const mid = Math.floor(sorted.length / 2);
996
- return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
1447
+ function resolveDetailDisplayName(rule) {
1448
+ return rule?.displayName;
997
1449
  }
998
- function staggerOffset(indexInBucket) {
999
- if (indexInBucket === 0) {
1000
- return 0;
1001
- }
1002
- const magnitude = Math.ceil(indexInBucket / 2) * STAGGER;
1003
- return indexInBucket % 2 === 1 ? magnitude : -magnitude;
1450
+ function resolveDetailName(rule) {
1451
+ return rule?.name;
1004
1452
  }
1005
- function columnFor(node) {
1006
- if (node.role === "target" && node.kind === "feedback") {
1007
- return FEEDBACK_COLUMN;
1008
- }
1009
- return ROLE_COLUMNS[node.role];
1453
+ function resolveRuleDisplayName(rule, fallback) {
1454
+ const displayName = resolveDetailDisplayName(rule) ?? resolveDetailName(rule);
1455
+ return displayName ?? fallback;
1010
1456
  }
1011
- function prepareFlowGraph(graph) {
1012
- const readSources = /* @__PURE__ */ new Set();
1013
- const effectTargets = /* @__PURE__ */ new Set();
1014
- for (const edge of graph.edges) {
1015
- if (edge.effectType === "read") {
1016
- readSources.add(edge.source);
1017
- } else {
1018
- effectTargets.add(edge.target);
1019
- }
1020
- }
1021
- const flowNodes = [];
1022
- const endpointByGraphId = /* @__PURE__ */ new Map();
1023
- for (const node of graph.nodes) {
1024
- if (node.kind === "rule") {
1025
- flowNodes.push({
1026
- id: node.id,
1027
- graphNodeId: node.id,
1028
- kind: node.kind,
1029
- label: node.label,
1030
- role: "rule"
1031
- });
1032
- continue;
1033
- }
1034
- const isSource = readSources.has(node.id);
1035
- const isTarget = effectTargets.has(node.id);
1036
- const endpoints = {};
1037
- if (isSource) {
1038
- const flowId = isTarget ? `${node.id}@src` : node.id;
1039
- endpoints.source = flowId;
1040
- flowNodes.push({
1041
- id: flowId,
1042
- graphNodeId: node.id,
1043
- kind: node.kind,
1044
- label: node.label,
1045
- role: "source"
1046
- });
1047
- }
1048
- if (isTarget) {
1049
- const flowId = isSource ? `${node.id}@tgt` : node.id;
1050
- endpoints.target = flowId;
1051
- flowNodes.push({
1052
- id: flowId,
1053
- graphNodeId: node.id,
1054
- kind: node.kind,
1055
- label: node.label,
1056
- role: "target"
1057
- });
1058
- }
1059
- endpointByGraphId.set(node.id, endpoints);
1060
- }
1061
- const flowEdges = graph.edges.map((edge) => {
1062
- if (edge.effectType === "read") {
1063
- const endpoints2 = endpointByGraphId.get(edge.source);
1064
- return { ...edge, source: endpoints2?.source ?? edge.source };
1065
- }
1066
- const endpoints = endpointByGraphId.get(edge.target);
1067
- return { ...edge, target: endpoints?.target ?? edge.target };
1068
- });
1069
- return { nodes: flowNodes, edges: flowEdges };
1457
+ function resolveRuleStageName(rule) {
1458
+ return rule?.programStage?.displayName;
1070
1459
  }
1071
- function computeGraphLayout(flowNodes, flowEdges) {
1072
- const positions = /* @__PURE__ */ new Map();
1073
- const ruleLane = /* @__PURE__ */ new Map();
1074
- flowNodes.filter((node) => node.role === "rule").forEach((node, index) => {
1075
- ruleLane.set(node.id, index);
1076
- positions.set(node.id, { x: ROLE_COLUMNS.rule, y: index * LANE_HEIGHT });
1077
- });
1078
- const connectedRuleLanes = /* @__PURE__ */ new Map();
1079
- const addLane = (nodeId, lane) => {
1080
- const lanes = connectedRuleLanes.get(nodeId) ?? /* @__PURE__ */ new Set();
1081
- lanes.add(lane);
1082
- connectedRuleLanes.set(nodeId, lanes);
1083
- };
1084
- for (const edge of flowEdges) {
1085
- const sourceLane = ruleLane.get(edge.source);
1086
- const targetLane = ruleLane.get(edge.target);
1087
- if (targetLane !== void 0 && sourceLane === void 0) {
1088
- addLane(edge.source, targetLane);
1089
- }
1090
- if (sourceLane !== void 0 && targetLane === void 0) {
1091
- addLane(edge.target, sourceLane);
1092
- }
1093
- }
1094
- const bucketCounts = /* @__PURE__ */ new Map();
1095
- for (const node of flowNodes) {
1096
- if (node.role === "rule") {
1097
- continue;
1098
- }
1099
- const lanes = connectedRuleLanes.get(node.id);
1100
- const lane = lanes && lanes.size > 0 ? median([...lanes]) : 0;
1101
- const column = columnFor(node);
1102
- const bucketKey = `${String(column)}:${String(lane)}`;
1103
- const indexInBucket = bucketCounts.get(bucketKey) ?? 0;
1104
- bucketCounts.set(bucketKey, indexInBucket + 1);
1105
- positions.set(node.id, {
1106
- x: column,
1107
- y: lane * LANE_HEIGHT + staggerOffset(indexInBucket)
1108
- });
1460
+ function resolveStageName(rule, programStageName) {
1461
+ if (programStageName === null) {
1462
+ return translate("All stages (registration)");
1109
1463
  }
1110
- return positions;
1111
- }
1112
-
1113
- // src/graphNodeStyles.ts
1114
- var GRAPH_NODE_CLASSES = {
1115
- field: "border-dhis2-blue-500",
1116
- rule: "border-dhis2-teal-600 bg-dhis2-teal-050",
1117
- section: "border-dhis2-grey-600",
1118
- feedback: "border-dhis2-purple-500"
1119
- };
1120
- var LEGEND_SWATCH_CLASSES = {
1121
- field: "bg-dhis2-blue-500",
1122
- rule: "bg-dhis2-teal-600",
1123
- section: "bg-dhis2-grey-600",
1124
- feedback: "bg-dhis2-purple-500"
1125
- };
1126
- function getGraphNodeClassName(kind, highlighted) {
1127
- return `p-dp8 rounded border bg-white min-w-28 max-w-[180px] text-xs shadow-[0_1px_2px_rgb(0_0_0/6%)] transition-opacity duration-150 ease-out cursor-grab active:cursor-grabbing ${GRAPH_NODE_CLASSES[kind]} ${highlighted ? "" : "opacity-35"}`;
1464
+ return orDash(resolveRuleStageName(rule) ?? programStageName);
1128
1465
  }
1129
- function getLegendSwatchClassName(kind) {
1130
- return `size-2.5 shrink-0 rounded-sm ${LEGEND_SWATCH_CLASSES[kind]}`;
1466
+ function resolveLastUpdatedByName(rule) {
1467
+ return rule?.lastUpdatedBy?.displayName;
1131
1468
  }
1132
-
1133
- // src/traceEntry.ts
1134
- function resolveGraphTraceEntry(entries, selectedEntryId) {
1135
- if (!entries.length) {
1136
- return null;
1137
- }
1138
- if (selectedEntryId) {
1139
- return entries.find((entry) => entry.id === selectedEntryId) ?? entries[entries.length - 1];
1469
+ function resolveLastUpdatedValue(rule) {
1470
+ const label = formatTimestamp(rule?.lastUpdated);
1471
+ if (!label) {
1472
+ return EM_DASH;
1140
1473
  }
1141
- return entries[entries.length - 1];
1474
+ const by = resolveLastUpdatedByName(rule);
1475
+ return by ? `${label} \xB7 ${by}` : label;
1142
1476
  }
1143
- function getActiveRuleIds(entries) {
1144
- const latest = resolveGraphTraceEntry(entries);
1145
- if (!latest) {
1146
- return /* @__PURE__ */ new Set();
1147
- }
1148
- return new Set(latest.ruleResults.map((result) => result.ruleId));
1477
+ function resolvePriorityCellValue(rule) {
1478
+ return rule?.priority != null ? String(rule.priority) : EM_DASH;
1149
1479
  }
1150
- var KIND_LABELS = {
1151
- field: "Field",
1152
- rule: "Rule",
1153
- section: "Section",
1154
- feedback: "Feedback"
1155
- };
1156
- var READ_LABEL_EDGE_THRESHOLD = 4;
1157
- var DEFAULT_EDGE_OPTIONS = {
1158
- type: "ruleGraphEdge",
1159
- style: { stroke: getEffectEdgeStroke("default") },
1160
- markerEnd: {
1161
- type: react$1.MarkerType.ArrowClosed,
1162
- color: getEffectEdgeStroke("default")
1163
- }
1164
- };
1165
- function RuleGraphNode({ data }) {
1166
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: getGraphNodeClassName(data.kind, data.highlighted), children: [
1167
- /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "target", position: react$1.Position.Left }),
1168
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-dp4 block text-[0.625rem] font-semibold uppercase leading-none tracking-wide text-dhis2-grey-600", children: KIND_LABELS[data.kind] }),
1169
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "break-words font-semibold leading-[1.3]", children: data.label }),
1170
- data.value !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-dp4 border-t border-dhis2-grey-200 pt-dp4 font-mono text-[0.6875rem] break-all text-dhis2-grey-700", children: data.value }) : null,
1171
- /* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "source", position: react$1.Position.Right })
1172
- ] });
1480
+ function resolveActionsCellValue(rule) {
1481
+ return translate("{{n}} action(s)", { n: rule?.programRuleActions?.length ?? 0 });
1173
1482
  }
1174
- var nodeTypes = {
1175
- ruleGraphNode: react.memo(RuleGraphNode)
1176
- };
1177
- function RuleGraphEdge({
1178
- id,
1179
- sourceX,
1180
- sourceY,
1181
- sourcePosition,
1182
- targetX,
1183
- targetY,
1184
- targetPosition,
1185
- markerEnd,
1186
- style,
1187
- data
1483
+ function resolveBasicDetailCells(rule, ruleName, programStageName) {
1484
+ return [
1485
+ { label: translate("Name"), value: resolveRuleDisplayName(rule, ruleName) },
1486
+ { label: translate("Code"), value: orDash(rule?.code), mono: true },
1487
+ { label: translate("Identifier"), value: orDash(rule?.id), mono: true },
1488
+ { label: translate("Program"), value: orDash(rule?.program?.displayName) },
1489
+ { label: translate("Program stage"), value: resolveStageName(rule, programStageName) },
1490
+ { label: translate("Priority"), value: resolvePriorityCellValue(rule) },
1491
+ { label: translate("Actions"), value: resolveActionsCellValue(rule) },
1492
+ { label: translate("Last updated"), value: resolveLastUpdatedValue(rule) }
1493
+ ];
1494
+ }
1495
+ function BasicDetails({
1496
+ rule,
1497
+ ruleName,
1498
+ programStageName
1188
1499
  }) {
1189
- const [hovered, setHovered] = react.useState(false);
1190
- const [edgePath, labelX, labelY] = react$1.getSmoothStepPath({
1191
- sourceX,
1192
- sourceY,
1193
- sourcePosition,
1194
- targetX,
1195
- targetY,
1196
- targetPosition,
1197
- offset: data?.offset ?? 20
1198
- });
1199
- const showLabel = data ? data.showLabel || hovered : false;
1200
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1201
- /* @__PURE__ */ jsxRuntime.jsx(react$1.BaseEdge, { id, path: edgePath, markerEnd, style }),
1202
- /* @__PURE__ */ jsxRuntime.jsx(
1203
- "path",
1204
- {
1205
- d: edgePath,
1206
- fill: "none",
1207
- stroke: "transparent",
1208
- strokeWidth: 16,
1209
- onMouseEnter: () => {
1210
- setHovered(true);
1211
- },
1212
- onMouseLeave: () => {
1213
- setHovered(false);
1500
+ const cells = resolveBasicDetailCells(rule, ruleName, programStageName);
1501
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-x-dp24 gap-y-dp12 rounded-[3px] border border-dhis2-grey-300 bg-dhis2-grey-050 p-dp16", children: cells.map((cell) => /* @__PURE__ */ jsxRuntime.jsx(BasicDetailCell, { ...cell }, cell.label)) });
1502
+ }
1503
+ var SECTION_HEADING_CLASS = "m-0 mb-dp8 text-[13px] font-bold uppercase tracking-wide text-dhis2-grey-700";
1504
+ function RuleDetailsHeader({
1505
+ title,
1506
+ chip,
1507
+ description
1508
+ }) {
1509
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "-mx-dp24 -mt-dp24 mb-0 border-b border-dhis2-grey-300 px-dp24 pb-dp16 pe-[44px] pt-[20px]", children: [
1510
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-[11px] font-bold uppercase tracking-[.09em] text-dhis2-grey-600", children: translate("Program rule") }),
1511
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-[6px] flex flex-wrap items-center gap-dp8", children: [
1512
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 text-xl font-medium leading-[1.3] text-dhis2-grey-900", children: title }),
1513
+ /* @__PURE__ */ jsxRuntime.jsx(
1514
+ "span",
1515
+ {
1516
+ className: `rounded-[4px] px-dp8 py-[2px] text-xs font-semibold ${chip.className}`,
1517
+ children: chip.label
1214
1518
  }
1215
- }
1216
- ),
1217
- showLabel && data ? /* @__PURE__ */ jsxRuntime.jsx(react$1.EdgeLabelRenderer, { children: /* @__PURE__ */ jsxRuntime.jsx(
1218
- "div",
1519
+ )
1520
+ ] }),
1521
+ description ? /* @__PURE__ */ jsxRuntime.jsx(
1522
+ "p",
1219
1523
  {
1220
- className: "nodrag nopan absolute rounded-sm px-dp4 text-[10px] font-semibold",
1221
- style: {
1222
- transform: `translate(-50%, -50%) translate(${String(labelX)}px, ${String(labelY)}px)`,
1223
- background: "rgba(255,255,255,0.92)",
1224
- color: data.stroke,
1225
- pointerEvents: "all"
1226
- },
1227
- onMouseEnter: () => {
1228
- setHovered(true);
1229
- },
1230
- onMouseLeave: () => {
1231
- setHovered(false);
1232
- },
1233
- children: data.label
1524
+ className: "m-0 mt-[6px] text-sm text-dhis2-grey-700",
1525
+ style: { textWrap: "pretty" },
1526
+ children: description
1234
1527
  }
1235
- ) }) : null
1528
+ ) : null
1236
1529
  ] });
1237
1530
  }
1238
- var edgeTypes = {
1239
- ruleGraphEdge: react.memo(RuleGraphEdge)
1240
- };
1241
- function effectNodeKey(effect) {
1242
- const kind = effect.type === "HIDESECTION" ? "section" : effect.type === "DISPLAYTEXT" || effect.type === "DISPLAYKEYVALUEPAIR" ? "feedback" : "field";
1243
- return `${kind}:${effect.targetId}`;
1244
- }
1245
- function entryGraphKeys(entry) {
1246
- const keys = /* @__PURE__ */ new Set();
1247
- for (const fieldId of entry.changedFields) {
1248
- keys.add(`field:${fieldId}`);
1249
- }
1250
- for (const result of entry.ruleResults) {
1251
- keys.add(`rule:${result.ruleId}`);
1252
- for (const effect of result.effects) {
1253
- keys.add(effectNodeKey(effect));
1254
- }
1255
- }
1256
- return keys;
1257
- }
1258
- function ruleGraphKeys(entry, ruleId) {
1259
- const keys = /* @__PURE__ */ new Set([`rule:${ruleId}`]);
1260
- for (const result of entry.ruleResults) {
1261
- if (result.ruleId !== ruleId) {
1262
- continue;
1263
- }
1264
- for (const effect of result.effects) {
1265
- keys.add(effectNodeKey(effect));
1266
- }
1267
- }
1268
- return keys;
1531
+ function ConditionSection({
1532
+ condition,
1533
+ variables
1534
+ }) {
1535
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
1536
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Condition") }),
1537
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap break-words rounded-[3px] border border-dhis2-grey-300 bg-dhis2-grey-200 px-dp16 py-[14px] font-mono text-[13px] leading-[1.6] text-dhis2-grey-900", children: condition ?? EM_DASH }),
1538
+ variables.length ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-dp12", children: [
1539
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 mb-dp8 text-xs text-dhis2-grey-600", children: translate("Variables referenced") }),
1540
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-dp8", children: variables.map((variable) => /* @__PURE__ */ jsxRuntime.jsxs(
1541
+ "span",
1542
+ {
1543
+ className: `inline-flex items-center gap-[4px] rounded-full px-dp8 py-[2px] text-xs ${variable.className}`,
1544
+ children: [
1545
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono font-medium", children: variable.label }),
1546
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-70", children: translate(variable.kind) })
1547
+ ]
1548
+ },
1549
+ variable.token
1550
+ )) })
1551
+ ] }) : null
1552
+ ] });
1269
1553
  }
1270
- var formatDisplayValue = (value) => {
1271
- if (value === void 0 || value === null || value === "") {
1272
- return void 0;
1273
- }
1274
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1275
- return String(value);
1276
- }
1277
- return JSON.stringify(value);
1278
- };
1279
- function toFlowGraph(graph, fieldState, formValues, highlightedKeys) {
1280
- const { nodes: flowNodes, edges: flowEdges } = prepareFlowGraph(graph);
1281
- const positions = computeGraphLayout(flowNodes, flowEdges);
1282
- const graphNodeIdByFlowId = new Map(flowNodes.map((node) => [node.id, node.graphNodeId]));
1283
- const nodes = flowNodes.map((node) => {
1284
- const position = positions.get(node.id) ?? { x: 0, y: 0 };
1285
- const rawFieldId = node.kind === "field" ? node.graphNodeId.slice("field:".length) : void 0;
1286
- const assignedValue = rawFieldId && rawFieldId in fieldState ? fieldState[rawFieldId].assignedValue : void 0;
1287
- const formValue = rawFieldId ? formValues[rawFieldId] : void 0;
1288
- const displayValue = formatDisplayValue(assignedValue) ?? formatDisplayValue(formValue);
1289
- return {
1290
- id: node.id,
1291
- type: "ruleGraphNode",
1292
- position,
1293
- data: {
1294
- label: node.label,
1295
- kind: node.kind,
1296
- graphNodeId: node.graphNodeId,
1297
- value: displayValue,
1298
- highlighted: highlightedKeys ? highlightedKeys.has(node.graphNodeId) : true
1299
- }
1300
- };
1301
- });
1302
- const dense = flowEdges.length > READ_LABEL_EDGE_THRESHOLD;
1303
- const sourceFanOut = /* @__PURE__ */ new Map();
1304
- const edges = flowEdges.map((edge) => {
1305
- const sourceGraphId = graphNodeIdByFlowId.get(edge.source) ?? edge.source;
1306
- const targetGraphId = graphNodeIdByFlowId.get(edge.target) ?? edge.target;
1307
- const isHighlighted = highlightedKeys ? highlightedKeys.has(sourceGraphId) && highlightedKeys.has(targetGraphId) : true;
1308
- const effectType = edge.effectType ?? "default";
1309
- const isRead = effectType === "read";
1310
- const stroke = getEffectEdgeStroke(effectType, isHighlighted);
1311
- const label = getEffectShortLabel(effectType);
1312
- const showLabel = !(isRead && dense);
1313
- const fanIndex = sourceFanOut.get(edge.source) ?? 0;
1314
- sourceFanOut.set(edge.source, fanIndex + 1);
1315
- const offset = 20 + fanIndex * 14;
1316
- return {
1317
- id: edge.id,
1318
- source: edge.source,
1319
- target: edge.target,
1320
- type: "ruleGraphEdge",
1321
- animated: isHighlighted && !isRead,
1322
- zIndex: isHighlighted ? 1 : 0,
1323
- style: {
1324
- stroke,
1325
- strokeWidth: isRead ? 1 : Math.min(1 + edge.fireCount, 4),
1326
- opacity: isHighlighted ? isRead ? 0.5 : 1 : 0.35
1327
- },
1328
- markerEnd: {
1329
- type: react$1.MarkerType.ArrowClosed,
1330
- color: stroke
1331
- },
1332
- data: {
1333
- label,
1334
- stroke,
1335
- showLabel,
1336
- offset
1337
- }
1338
- };
1339
- });
1340
- return { nodes, edges };
1554
+ function ActionsSection({ actions }) {
1555
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
1556
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Actions") }),
1557
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-dp12", children: actions.map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(ActionCard, { action, index }, action.id)) })
1558
+ ] });
1341
1559
  }
1342
- function GraphToolbar({
1343
- nodeCount,
1344
- edgeCount,
1345
- headerActions,
1346
- activeEffectVariants
1560
+ function RuleDetailsContent({
1561
+ detail,
1562
+ ruleName,
1563
+ programStageName,
1564
+ variables
1347
1565
  }) {
1348
- const items = [
1349
- { kind: "field", label: translate("Field") },
1350
- { kind: "rule", label: translate("Rule") },
1351
- { kind: "section", label: translate("Section") },
1352
- { kind: "feedback", label: translate("Feedback") }
1353
- ];
1354
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b border-dhis2-grey-200 bg-white", children: [
1355
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-dp8 px-dp12 py-dp8", children: [
1566
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-dp24 pt-dp16", children: [
1567
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
1568
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Basic details") }),
1356
1569
  /* @__PURE__ */ jsxRuntime.jsx(
1357
- "div",
1570
+ BasicDetails,
1358
1571
  {
1359
- className: "flex min-w-0 flex-wrap items-center gap-x-dp12 gap-y-dp8",
1360
- "aria-hidden": "true",
1361
- children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
1362
- "span",
1363
- {
1364
- className: "inline-flex items-center gap-dp8 text-xs text-dhis2-grey-800",
1365
- children: [
1366
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: getLegendSwatchClassName(item.kind) }),
1367
- item.label
1368
- ]
1369
- },
1370
- item.kind
1371
- ))
1572
+ rule: detail,
1573
+ ruleName,
1574
+ programStageName
1372
1575
  }
1373
- ),
1374
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-dp8", children: [
1375
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs tabular-nums text-dhis2-grey-600", children: translate("{{nodes}} nodes \xB7 {{edges}} edges", {
1376
- nodes: nodeCount,
1377
- edges: edgeCount
1378
- }) }),
1379
- headerActions
1380
- ] })
1576
+ )
1381
1577
  ] }),
1382
- /* @__PURE__ */ jsxRuntime.jsx(EffectLegend, { activeVariants: activeEffectVariants })
1578
+ /* @__PURE__ */ jsxRuntime.jsx(ConditionSection, { condition: detail.condition, variables }),
1579
+ /* @__PURE__ */ jsxRuntime.jsx(ActionsSection, { actions: detail.programRuleActions ?? [] })
1383
1580
  ] });
1384
1581
  }
1385
- function FitViewOnChange({
1386
- layoutKey,
1387
- nodeCount,
1388
- edgeCount
1389
- }) {
1390
- const { fitView } = react$1.useReactFlow();
1391
- react.useEffect(() => {
1392
- const frameId = requestAnimationFrame(() => {
1393
- void fitView({ padding: 0.2, duration: 150 });
1394
- });
1395
- return () => {
1396
- cancelAnimationFrame(frameId);
1397
- };
1398
- }, [edgeCount, fitView, layoutKey, nodeCount]);
1399
- return null;
1400
- }
1401
- function mergeNodePositions(next, current) {
1402
- const currentById = new Map(current.map((node) => [node.id, node]));
1403
- return next.map((node) => {
1404
- const existing = currentById.get(node.id);
1405
- if (existing) {
1406
- return { ...node, position: existing.position };
1407
- }
1408
- return node;
1409
- });
1410
- }
1411
- function RuleGraphCanvas({
1412
- nodes: layoutNodes,
1413
- edges: layoutEdges,
1414
- layoutKey
1582
+ function RuleDetailsBody({
1583
+ detail,
1584
+ loading,
1585
+ error,
1586
+ ruleName,
1587
+ programStageName,
1588
+ variables
1415
1589
  }) {
1416
- const [nodes, setNodes, onNodesChange] = react$1.useNodesState(layoutNodes);
1417
- const [edges, setEdges, onEdgesChange] = react$1.useEdgesState(layoutEdges);
1418
- react.useEffect(() => {
1419
- setNodes((current) => mergeNodePositions(layoutNodes, current));
1420
- }, [layoutNodes, setNodes]);
1421
- react.useEffect(() => {
1422
- setEdges(layoutEdges);
1423
- }, [layoutEdges, setEdges]);
1424
- return /* @__PURE__ */ jsxRuntime.jsxs(
1425
- react$1.ReactFlow,
1590
+ if (loading) {
1591
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[280px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.CircularLoader, { small: true }) });
1592
+ }
1593
+ if (error) {
1594
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm text-dhis2-red-700", children: translate("Could not load this rule: {{message}}", { message: error.message }) });
1595
+ }
1596
+ if (!detail) {
1597
+ return null;
1598
+ }
1599
+ return /* @__PURE__ */ jsxRuntime.jsx(
1600
+ RuleDetailsContent,
1426
1601
  {
1427
- nodes,
1428
- edges,
1429
- nodeTypes,
1430
- edgeTypes,
1431
- onNodesChange,
1432
- onEdgesChange,
1433
- nodesDraggable: true,
1434
- defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
1435
- elevateEdgesOnSelect: true,
1436
- fitView: true,
1437
- proOptions: { hideAttribution: true },
1438
- className: "h-full w-full",
1439
- children: [
1440
- /* @__PURE__ */ jsxRuntime.jsx(
1441
- FitViewOnChange,
1442
- {
1443
- layoutKey,
1444
- nodeCount: nodes.length,
1445
- edgeCount: edges.length
1446
- }
1447
- ),
1448
- /* @__PURE__ */ jsxRuntime.jsx(react$1.Background, { gap: 16, size: 1 }),
1449
- /* @__PURE__ */ jsxRuntime.jsx(react$1.Controls, { showInteractive: false })
1450
- ]
1602
+ detail,
1603
+ ruleName,
1604
+ programStageName,
1605
+ variables
1451
1606
  }
1452
1607
  );
1453
1608
  }
1454
- function RuleGraphView({
1455
- entries,
1456
- fieldState,
1457
- formValues,
1458
- selectedEntryId,
1459
- highlightRuleId,
1460
- labelLookup,
1461
- className,
1462
- layoutKey,
1463
- headerActions,
1464
- minHeightClassName = "min-h-[360px]"
1465
- }) {
1466
- const graphEntry = react.useMemo(
1467
- () => resolveGraphTraceEntry(entries, selectedEntryId),
1468
- [entries, selectedEntryId]
1469
- );
1470
- const graph = react.useMemo(
1471
- () => graphEntry ? buildGraphFromTrace([graphEntry], labelLookup) : { nodes: [], edges: [] },
1472
- [graphEntry, labelLookup]
1473
- );
1474
- const highlightedKeys = react.useMemo(() => {
1475
- if (!graphEntry || !selectedEntryId && !highlightRuleId) {
1476
- return null;
1477
- }
1478
- const keys = /* @__PURE__ */ new Set();
1479
- if (selectedEntryId) {
1480
- for (const key of entryGraphKeys(graphEntry)) {
1481
- keys.add(key);
1482
- }
1483
- }
1484
- if (highlightRuleId) {
1485
- for (const key of ruleGraphKeys(graphEntry, highlightRuleId)) {
1486
- keys.add(key);
1487
- }
1488
- }
1489
- return keys;
1490
- }, [graphEntry, highlightRuleId, selectedEntryId]);
1491
- const { nodes, edges } = react.useMemo(
1492
- () => toFlowGraph(graph, fieldState, formValues, highlightedKeys),
1493
- [graph, fieldState, formValues, highlightedKeys]
1494
- );
1495
- const activeEffectVariants = react.useMemo(() => {
1496
- const variants = /* @__PURE__ */ new Set();
1497
- for (const edge of graph.edges) {
1498
- if (edge.effectType) {
1499
- variants.add(getEffectVariant(edge.effectType));
1500
- }
1501
- }
1502
- return variants;
1503
- }, [graph.edges]);
1504
- if (!graph.nodes.length) {
1505
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-dp8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.NoticeBox, { title: translate("No rule relationships yet"), children: translate(
1506
- "Interact with the form to build the dependency graph. Only rules active in the latest evaluation are shown. Select a trace entry to inspect a past evaluation. Connections flow Field \u2192 Rule \u2192 Target (read / effect)."
1507
- ) }) });
1508
- }
1609
+ function RuleDetailsFooter({ onClose }) {
1509
1610
  return /* @__PURE__ */ jsxRuntime.jsxs(
1510
1611
  "div",
1511
1612
  {
1512
- className: `flex h-full min-h-[480px] flex-1 flex-col overflow-hidden rounded-md border border-dhis2-grey-200 bg-white ${className ?? ""}`,
1613
+ style: { order: 3 },
1614
+ className: "-mx-dp24 -mb-dp24 mt-dp16 flex items-center justify-between gap-dp12 border-t border-dhis2-grey-300 bg-dhis2-grey-050 px-dp24 py-[14px]",
1513
1615
  children: [
1514
- /* @__PURE__ */ jsxRuntime.jsx(
1515
- GraphToolbar,
1516
- {
1517
- nodeCount: graph.nodes.length,
1518
- edgeCount: graph.edges.length,
1519
- headerActions,
1520
- activeEffectVariants
1521
- }
1522
- ),
1523
- /* @__PURE__ */ jsxRuntime.jsx(
1524
- "div",
1525
- {
1526
- className: `${minHeightClassName} flex-1 bg-white`,
1527
- style: { width: "100%", height: "100%" },
1528
- children: /* @__PURE__ */ jsxRuntime.jsx(react$1.ReactFlowProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(RuleGraphCanvas, { nodes, edges, layoutKey }) })
1529
- }
1530
- )
1616
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm text-dhis2-grey-600", children: translate("Read-only view. Edit rules in the Maintenance app.") }),
1617
+ /* @__PURE__ */ jsxRuntime.jsx(ui.ButtonStrip, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { secondary: true, onClick: onClose, children: translate("Close") }) })
1531
1618
  ]
1532
1619
  }
1533
1620
  );
1534
1621
  }
1535
- function RuleGraphModal({
1622
+ function resolveActiveRuleId(open, ruleId) {
1623
+ return open ? ruleId : null;
1624
+ }
1625
+ function shouldRenderModal(open, ruleId) {
1626
+ return open && ruleId != null;
1627
+ }
1628
+ function RuleDetailsModal({
1536
1629
  open,
1537
1630
  onClose,
1538
- subtitle,
1539
- layoutKey,
1540
- ...graphProps
1631
+ ruleId,
1632
+ ruleName,
1633
+ status,
1634
+ programStageName,
1635
+ programRuleVariables
1541
1636
  }) {
1542
- if (!open) {
1637
+ const { detail, loading, error } = useProgramRuleDetail(resolveActiveRuleId(open, ruleId));
1638
+ if (!shouldRenderModal(open, ruleId)) {
1543
1639
  return null;
1544
1640
  }
1545
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Modal, { large: true, fluid: true, onClose, children: [
1546
- /* @__PURE__ */ jsxRuntime.jsxs(ui.ModalTitle, { children: [
1547
- translate("Rule dependency graph"),
1548
- subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-dp4 block text-sm font-normal text-dhis2-grey-700", children: subtitle }) : null
1549
- ] }),
1550
- /* @__PURE__ */ jsxRuntime.jsx(ui.ModalContent, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(85vh,900px)] w-full min-w-[min(1200px,92vw)]", children: /* @__PURE__ */ jsxRuntime.jsx(
1551
- RuleGraphView,
1641
+ const chip = resolveStatusChip(status);
1642
+ const variables = parseConditionVariables(detail?.condition, programRuleVariables);
1643
+ const title = resolveRuleDisplayName(detail, ruleName);
1644
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Modal, { position: "middle", onClose, children: [
1645
+ /* @__PURE__ */ jsxRuntime.jsx(RuleDetailsHeader, { title, chip, description: detail?.description }),
1646
+ /* @__PURE__ */ jsxRuntime.jsx(ui.ModalContent, { children: /* @__PURE__ */ jsxRuntime.jsx(
1647
+ RuleDetailsBody,
1552
1648
  {
1553
- ...graphProps,
1554
- layoutKey: layoutKey ?? "modal",
1555
- className: "h-full min-h-0 rounded-none border-0",
1556
- minHeightClassName: "min-h-0 h-full"
1649
+ detail,
1650
+ loading,
1651
+ error,
1652
+ ruleName,
1653
+ programStageName,
1654
+ variables
1557
1655
  }
1558
- ) }) })
1656
+ ) }),
1657
+ /* @__PURE__ */ jsxRuntime.jsx(RuleDetailsFooter, { onClose })
1559
1658
  ] });
1560
1659
  }
1660
+
1661
+ // src/RulesPanelGraphFallback.tsx
1662
+ init_i18n();
1663
+ function RulesPanelGraphFallback() {
1664
+ return /* @__PURE__ */ jsxRuntime.jsx(
1665
+ "div",
1666
+ {
1667
+ className: "flex min-h-[200px] items-center justify-center",
1668
+ role: "status",
1669
+ "aria-label": translate("Loading graph"),
1670
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.CircularLoader, { small: true })
1671
+ }
1672
+ );
1673
+ }
1674
+
1675
+ // src/TraceTimeline.tsx
1676
+ init_EffectBadge();
1677
+ init_i18n();
1561
1678
  function resolveLabel(id, resolver) {
1562
1679
  const label = resolver?.(id) ?? id;
1563
1680
  return { label, showId: label !== id };
@@ -1774,6 +1891,9 @@ function TraceTimeline({
1774
1891
  ) }, entry.id);
1775
1892
  }) });
1776
1893
  }
1894
+
1895
+ // src/RulesPanel.tsx
1896
+ init_traceEntry();
1777
1897
  var REORDER_DURATION_MS = 220;
1778
1898
  var REORDER_EASING = "ease-out";
1779
1899
  function prefersReducedMotion() {
@@ -1820,11 +1940,20 @@ function useFlipReorder(orderedIds, containerRef) {
1820
1940
  positionsRef.current = nextPositions;
1821
1941
  }, [orderedIds.join("|")]);
1822
1942
  }
1823
- function resolveScopeStageId(metadata) {
1943
+ var LazyRuleGraphView = react.lazy(
1944
+ () => Promise.resolve().then(() => (init_RuleGraphView(), RuleGraphView_exports)).then((module) => ({ default: module.RuleGraphView }))
1945
+ );
1946
+ var LazyRuleGraphModal = react.lazy(
1947
+ () => Promise.resolve().then(() => (init_RuleGraphModal(), RuleGraphModal_exports)).then((module) => ({ default: module.RuleGraphModal }))
1948
+ );
1949
+ function resolveScopeStageId(metadata, activeProgramStageId) {
1950
+ if (activeProgramStageId !== void 0) {
1951
+ return activeProgramStageId;
1952
+ }
1824
1953
  return metadata.formKind === "event" ? metadata.programStageId : null;
1825
1954
  }
1826
1955
  function isRuleInScope(rule, scopeStageId) {
1827
- return rule.programStageId === null || rule.programStageId === scopeStageId;
1956
+ return rule.programStageId === scopeStageId;
1828
1957
  }
1829
1958
  function resolveAccentClassName(inScope, firing, selected) {
1830
1959
  if (selected) {
@@ -1847,6 +1976,11 @@ function resolveDetailsStatus(inScope, firing) {
1847
1976
  }
1848
1977
  return firing ? "firing" : "idle";
1849
1978
  }
1979
+ function resolveAppliesToCaption(rule, labelLookup) {
1980
+ return rule.programStageId === null ? translate("Applies to registration") : translate("Applies to {{stage}}", {
1981
+ stage: labelLookup.resolveStageName(rule.programStageId)
1982
+ });
1983
+ }
1850
1984
  function formatActionLabel(action) {
1851
1985
  if (action.targetLabel && action.detail) {
1852
1986
  return `${action.type} \xB7 ${action.targetLabel} = ${action.detail}`;
@@ -1880,7 +2014,11 @@ function resolveTabLabel(key) {
1880
2014
  return translate("Graph");
1881
2015
  }
1882
2016
  }
1883
- function RulesPanel({ metadata, showConditions = true }) {
2017
+ function RulesPanel({
2018
+ metadata,
2019
+ showConditions = true,
2020
+ activeProgramStageId
2021
+ }) {
1884
2022
  const formStore = dhis2FormUtilsHooks.useFormStore();
1885
2023
  const { form } = dhis2FormUtilsHooks.useFormStateContext();
1886
2024
  const traceStore = useRuleTraceStore();
@@ -1892,7 +2030,10 @@ function RulesPanel({ metadata, showConditions = true }) {
1892
2030
  const [detailsRuleId, setDetailsRuleId] = react.useState(null);
1893
2031
  const labelLookup = react.useMemo(() => createLabelLookup(metadata), [metadata]);
1894
2032
  const catalog = react.useMemo(() => resolveProgramRulesList(metadata), [metadata]);
1895
- const scopeStageId = react.useMemo(() => resolveScopeStageId(metadata), [metadata]);
2033
+ const scopeStageId = react.useMemo(
2034
+ () => resolveScopeStageId(metadata, activeProgramStageId),
2035
+ [metadata, activeProgramStageId]
2036
+ );
1896
2037
  const entries = react.useSyncExternalStore(
1897
2038
  react.useCallback((listener) => traceStore.subscribe(listener), [traceStore]),
1898
2039
  react.useCallback(() => traceStore.getSnapshot(), [traceStore]),
@@ -2076,19 +2217,19 @@ function RulesPanel({ metadata, showConditions = true }) {
2076
2217
  },
2077
2218
  labelLookup
2078
2219
  }
2079
- ) : /* @__PURE__ */ jsxRuntime.jsx(
2080
- RuleGraphView,
2220
+ ) : /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(RulesPanelGraphFallback, {}), children: /* @__PURE__ */ jsxRuntime.jsx(
2221
+ LazyRuleGraphView,
2081
2222
  {
2082
2223
  ...graphProps,
2083
2224
  headerActions: graphHasNodes ? expandGraphButton : void 0
2084
2225
  }
2085
- ) }),
2226
+ ) }) }),
2086
2227
  entries.length > 0 && tab !== "rules" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sr-only", "aria-live": "polite", children: translate("{{evaluations}} evaluations \xB7 {{rules}} rules observed", {
2087
2228
  evaluations: entries.length,
2088
2229
  rules: observedRuleCount
2089
2230
  }) }) : null,
2090
- /* @__PURE__ */ jsxRuntime.jsx(
2091
- RuleGraphModal,
2231
+ graphModalOpen ? /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(
2232
+ LazyRuleGraphModal,
2092
2233
  {
2093
2234
  ...graphProps,
2094
2235
  open: graphModalOpen,
@@ -2096,9 +2237,9 @@ function RulesPanel({ metadata, showConditions = true }) {
2096
2237
  setGraphModalOpen(false);
2097
2238
  },
2098
2239
  subtitle: graphSubtitle,
2099
- layoutKey: graphModalOpen ? "open" : "closed"
2240
+ layoutKey: "open"
2100
2241
  }
2101
- ),
2242
+ ) }) : null,
2102
2243
  /* @__PURE__ */ jsxRuntime.jsx(
2103
2244
  RuleDetailsModal,
2104
2245
  {
@@ -2234,16 +2375,16 @@ function RulesTab({
2234
2375
  children: rule.condition
2235
2376
  }
2236
2377
  ) : null,
2237
- !inScope && rule.programStageId ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-dhis2-grey-600", children: translate("Applies to {{stage}}", {
2238
- stage: labelLookup.resolveStageName(rule.programStageId)
2239
- }) }) : null
2378
+ !inScope ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-dhis2-grey-600", children: resolveAppliesToCaption(rule, labelLookup) }) : null
2240
2379
  ]
2241
2380
  }
2242
2381
  ) }, rule.id);
2243
2382
  }) });
2244
2383
  }
2245
2384
 
2246
- exports.EFFECT_ICONS = EFFECT_ICONS;
2385
+ // src/index.ts
2386
+ init_effectStyles();
2387
+
2247
2388
  exports.RuleDetailsModal = RuleDetailsModal;
2248
2389
  exports.RuleDevtoolsScope = RuleDevtoolsScope;
2249
2390
  exports.RulesPanel = RulesPanel;