@nnkogift/dhis2-form-utils-devtools 0.1.0-alpha.3 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +609 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +611 -17
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/index.cjs
CHANGED
|
@@ -6,6 +6,7 @@ var react = require('react');
|
|
|
6
6
|
var dhis2FormUtilsMetadata = require('@nnkogift/dhis2-form-utils-metadata');
|
|
7
7
|
var i18n = require('@dhis2/d2-i18n');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var appRuntime = require('@dhis2/app-runtime');
|
|
9
10
|
var react$1 = require('@xyflow/react');
|
|
10
11
|
|
|
11
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -523,6 +524,463 @@ function useRuleTraceStore() {
|
|
|
523
524
|
return store;
|
|
524
525
|
}
|
|
525
526
|
|
|
527
|
+
// src/programRuleDetailQuery.ts
|
|
528
|
+
var PROGRAM_RULE_ACTION_DETAIL_FIELDS = [
|
|
529
|
+
"id",
|
|
530
|
+
"programRuleActionType",
|
|
531
|
+
"priority",
|
|
532
|
+
"content",
|
|
533
|
+
"data",
|
|
534
|
+
"location",
|
|
535
|
+
"dataElement[id,displayName]",
|
|
536
|
+
"trackedEntityAttribute[id,displayName]",
|
|
537
|
+
"option[id,displayName]",
|
|
538
|
+
"optionGroup[id,displayName]",
|
|
539
|
+
"programStageSection[id,displayName]",
|
|
540
|
+
"programStage[id,displayName]",
|
|
541
|
+
"programSection[id,displayName]"
|
|
542
|
+
].join(",");
|
|
543
|
+
var PROGRAM_RULE_DETAIL_FIELDS = [
|
|
544
|
+
"id",
|
|
545
|
+
"name",
|
|
546
|
+
"displayName",
|
|
547
|
+
"code",
|
|
548
|
+
"description",
|
|
549
|
+
"condition",
|
|
550
|
+
"priority",
|
|
551
|
+
"lastUpdated",
|
|
552
|
+
"lastUpdatedBy[displayName]",
|
|
553
|
+
"program[id,displayName]",
|
|
554
|
+
"programStage[id,displayName]",
|
|
555
|
+
`programRuleActions[${PROGRAM_RULE_ACTION_DETAIL_FIELDS}]`
|
|
556
|
+
].join(",");
|
|
557
|
+
var programRuleDetailQuery = {
|
|
558
|
+
programRule: {
|
|
559
|
+
resource: "programRules",
|
|
560
|
+
id: (variables) => variables.ruleId,
|
|
561
|
+
params: {
|
|
562
|
+
fields: PROGRAM_RULE_DETAIL_FIELDS
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// src/useProgramRuleDetail.ts
|
|
568
|
+
function isCurrentDetail(ruleId, detail) {
|
|
569
|
+
return ruleId != null && detail?.id === ruleId;
|
|
570
|
+
}
|
|
571
|
+
function resolveDetailState(ruleId, detail, hasError) {
|
|
572
|
+
const isCurrent = isCurrentDetail(ruleId, detail);
|
|
573
|
+
return {
|
|
574
|
+
detail: isCurrent ? detail : void 0,
|
|
575
|
+
loading: ruleId != null && !isCurrent && !hasError
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function useProgramRuleDetail(ruleId) {
|
|
579
|
+
const { data, error, refetch } = appRuntime.useDataQuery(
|
|
580
|
+
programRuleDetailQuery,
|
|
581
|
+
{ lazy: true }
|
|
582
|
+
);
|
|
583
|
+
react.useEffect(() => {
|
|
584
|
+
if (ruleId) {
|
|
585
|
+
void refetch({ ruleId });
|
|
586
|
+
}
|
|
587
|
+
}, [ruleId, refetch]);
|
|
588
|
+
return { ...resolveDetailState(ruleId, data?.programRule, Boolean(error)), error };
|
|
589
|
+
}
|
|
590
|
+
var EM_DASH = "\u2014";
|
|
591
|
+
function formatTimestamp(value) {
|
|
592
|
+
if (!value) {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
const date = new Date(value);
|
|
596
|
+
if (Number.isNaN(date.getTime())) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
const day = String(date.getDate());
|
|
600
|
+
const month = date.toLocaleString("en-GB", { month: "short" });
|
|
601
|
+
const year = String(date.getFullYear());
|
|
602
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
603
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
604
|
+
return `${day} ${month} ${year} ${hours}:${minutes}`;
|
|
605
|
+
}
|
|
606
|
+
function resolveStatusChip(status) {
|
|
607
|
+
switch (status) {
|
|
608
|
+
case "firing":
|
|
609
|
+
return {
|
|
610
|
+
label: translate("Firing"),
|
|
611
|
+
className: "bg-dhis2-teal-100 text-dhis2-teal-900"
|
|
612
|
+
};
|
|
613
|
+
case "idle":
|
|
614
|
+
return { label: translate("Idle"), className: "bg-dhis2-grey-200 text-dhis2-grey-700" };
|
|
615
|
+
case "out-of-scope":
|
|
616
|
+
return {
|
|
617
|
+
label: translate("Out of scope"),
|
|
618
|
+
className: "bg-dhis2-grey-200 text-dhis2-grey-600"
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
var VARIABLE_PATTERNS = [
|
|
623
|
+
{
|
|
624
|
+
pattern: /#\{[^}]*\}/g,
|
|
625
|
+
kind: "Data element",
|
|
626
|
+
className: "bg-dhis2-blue-100 text-dhis2-blue-900"
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
pattern: /A\{[^}]*\}/g,
|
|
630
|
+
kind: "Tracked entity attribute",
|
|
631
|
+
className: "bg-dhis2-teal-100 text-dhis2-teal-900"
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
pattern: /V\{[^}]*\}/g,
|
|
635
|
+
kind: "Environment variable",
|
|
636
|
+
className: "bg-dhis2-grey-200 text-dhis2-grey-900"
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
pattern: /d2:\w+(?=\()/g,
|
|
640
|
+
kind: "Function",
|
|
641
|
+
className: "bg-dhis2-yellow-100 text-dhis2-yellow-900"
|
|
642
|
+
}
|
|
643
|
+
];
|
|
644
|
+
var RESOLVABLE_VARIABLE_KINDS = /* @__PURE__ */ new Set([
|
|
645
|
+
"Data element",
|
|
646
|
+
"Tracked entity attribute"
|
|
647
|
+
]);
|
|
648
|
+
function resolveVariableDisplayName(name, programRuleVariables) {
|
|
649
|
+
const variable = programRuleVariables.find((candidate) => candidate.name === name);
|
|
650
|
+
return variable?.dataElement?.displayName ?? variable?.trackedEntityAttribute?.displayName;
|
|
651
|
+
}
|
|
652
|
+
function resolveVariableToken(token, kind, programRuleVariables) {
|
|
653
|
+
if (kind === "Function") {
|
|
654
|
+
return `${token}()`;
|
|
655
|
+
}
|
|
656
|
+
if (!RESOLVABLE_VARIABLE_KINDS.has(kind)) {
|
|
657
|
+
return token;
|
|
658
|
+
}
|
|
659
|
+
return resolveVariableDisplayName(token.slice(2, -1), programRuleVariables) ?? token;
|
|
660
|
+
}
|
|
661
|
+
function matchDistinctTokens(condition, pattern, seen) {
|
|
662
|
+
const matches = condition.match(pattern) ?? [];
|
|
663
|
+
const distinct = matches.filter((token) => !seen.has(token));
|
|
664
|
+
for (const token of distinct) {
|
|
665
|
+
seen.add(token);
|
|
666
|
+
}
|
|
667
|
+
return distinct;
|
|
668
|
+
}
|
|
669
|
+
function parseConditionVariables(condition, programRuleVariables) {
|
|
670
|
+
if (!condition) {
|
|
671
|
+
return [];
|
|
672
|
+
}
|
|
673
|
+
const seen = /* @__PURE__ */ new Set();
|
|
674
|
+
return VARIABLE_PATTERNS.flatMap(
|
|
675
|
+
({ pattern, kind, className }) => matchDistinctTokens(condition, pattern, seen).map((token) => ({
|
|
676
|
+
token,
|
|
677
|
+
kind,
|
|
678
|
+
label: resolveVariableToken(token, kind, programRuleVariables),
|
|
679
|
+
className
|
|
680
|
+
}))
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
var ACTION_TARGET_RESOLVERS = [
|
|
684
|
+
{ label: "Data element", getRef: (action) => action.dataElement },
|
|
685
|
+
{ label: "Tracked entity attribute", getRef: (action) => action.trackedEntityAttribute },
|
|
686
|
+
{ label: "Program stage section", getRef: (action) => action.programStageSection },
|
|
687
|
+
{ label: "Program stage", getRef: (action) => action.programStage },
|
|
688
|
+
{ label: "Option", getRef: (action) => action.option },
|
|
689
|
+
{ label: "Option group", getRef: (action) => action.optionGroup }
|
|
690
|
+
];
|
|
691
|
+
function formatActionTargetValue(ref) {
|
|
692
|
+
return `${ref.displayName ?? ref.id} \xB7 ${ref.id}`;
|
|
693
|
+
}
|
|
694
|
+
function resolveActionTarget(action) {
|
|
695
|
+
for (const { label, getRef } of ACTION_TARGET_RESOLVERS) {
|
|
696
|
+
const ref = getRef(action);
|
|
697
|
+
if (ref?.id) {
|
|
698
|
+
return { label: translate(label), value: formatActionTargetValue(ref) };
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
function BasicDetailCell({ label, value, mono }) {
|
|
704
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
705
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-xs text-dhis2-grey-600", children: label }),
|
|
706
|
+
/* @__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
|
+
);
|
|
931
|
+
}
|
|
932
|
+
function RuleDetailsFooter({ onClose }) {
|
|
933
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
934
|
+
"div",
|
|
935
|
+
{
|
|
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]",
|
|
938
|
+
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") }) })
|
|
941
|
+
]
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
}
|
|
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
|
+
] });
|
|
982
|
+
}
|
|
983
|
+
|
|
526
984
|
// src/graphLayout.ts
|
|
527
985
|
var ROLE_COLUMNS = {
|
|
528
986
|
source: 0,
|
|
@@ -1316,6 +1774,52 @@ function TraceTimeline({
|
|
|
1316
1774
|
) }, entry.id);
|
|
1317
1775
|
}) });
|
|
1318
1776
|
}
|
|
1777
|
+
var REORDER_DURATION_MS = 220;
|
|
1778
|
+
var REORDER_EASING = "ease-out";
|
|
1779
|
+
function prefersReducedMotion() {
|
|
1780
|
+
return typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1781
|
+
}
|
|
1782
|
+
function useFlipReorder(orderedIds, containerRef) {
|
|
1783
|
+
const positionsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
1784
|
+
react.useLayoutEffect(() => {
|
|
1785
|
+
const container = containerRef.current;
|
|
1786
|
+
if (!container) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const previousPositions = positionsRef.current;
|
|
1790
|
+
const items = container.querySelectorAll("[data-rule-id]");
|
|
1791
|
+
if (!prefersReducedMotion()) {
|
|
1792
|
+
items.forEach((item) => {
|
|
1793
|
+
const id = item.dataset.ruleId;
|
|
1794
|
+
if (!id) {
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const previousRect = previousPositions.get(id);
|
|
1798
|
+
if (!previousRect) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
const currentRect = item.getBoundingClientRect();
|
|
1802
|
+
const deltaY = previousRect.top - currentRect.top;
|
|
1803
|
+
if (deltaY === 0) {
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
item.style.transition = "none";
|
|
1807
|
+
item.style.transform = `translateY(${String(deltaY)}px)`;
|
|
1808
|
+
item.getBoundingClientRect();
|
|
1809
|
+
item.style.transition = `transform ${String(REORDER_DURATION_MS)}ms ${REORDER_EASING}`;
|
|
1810
|
+
item.style.transform = "";
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
const nextPositions = /* @__PURE__ */ new Map();
|
|
1814
|
+
items.forEach((item) => {
|
|
1815
|
+
const id = item.dataset.ruleId;
|
|
1816
|
+
if (id) {
|
|
1817
|
+
nextPositions.set(id, item.getBoundingClientRect());
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
positionsRef.current = nextPositions;
|
|
1821
|
+
}, [orderedIds.join("|")]);
|
|
1822
|
+
}
|
|
1319
1823
|
function resolveScopeStageId(metadata) {
|
|
1320
1824
|
return metadata.formKind === "event" ? metadata.programStageId : null;
|
|
1321
1825
|
}
|
|
@@ -1337,6 +1841,12 @@ function resolveCardStatus(inScope, firing) {
|
|
|
1337
1841
|
}
|
|
1338
1842
|
return firing ? { label: translate("Firing"), className: "text-dhis2-teal-700" } : { label: translate("Idle"), className: "text-dhis2-grey-600" };
|
|
1339
1843
|
}
|
|
1844
|
+
function resolveDetailsStatus(inScope, firing) {
|
|
1845
|
+
if (!inScope) {
|
|
1846
|
+
return "out-of-scope";
|
|
1847
|
+
}
|
|
1848
|
+
return firing ? "firing" : "idle";
|
|
1849
|
+
}
|
|
1340
1850
|
function formatActionLabel(action) {
|
|
1341
1851
|
if (action.targetLabel && action.detail) {
|
|
1342
1852
|
return `${action.type} \xB7 ${action.targetLabel} = ${action.detail}`;
|
|
@@ -1356,6 +1866,10 @@ function countObservedRules(entries) {
|
|
|
1356
1866
|
return ruleIds.size;
|
|
1357
1867
|
}
|
|
1358
1868
|
var PANEL_TAB_KEYS = ["rules", "trace", "graph"];
|
|
1869
|
+
var SCOPE_FILTER_OPTIONS = [
|
|
1870
|
+
{ label: translate("In scope"), value: "scoped" },
|
|
1871
|
+
{ label: translate("All"), value: "all" }
|
|
1872
|
+
];
|
|
1359
1873
|
function resolveTabLabel(key) {
|
|
1360
1874
|
switch (key) {
|
|
1361
1875
|
case "rules":
|
|
@@ -1374,6 +1888,8 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1374
1888
|
const [selectedEntryId, setSelectedEntryId] = react.useState(null);
|
|
1375
1889
|
const [highlightRuleId, setHighlightRuleId] = react.useState(null);
|
|
1376
1890
|
const [graphModalOpen, setGraphModalOpen] = react.useState(false);
|
|
1891
|
+
const [scopeFilter, setScopeFilter] = react.useState("scoped");
|
|
1892
|
+
const [detailsRuleId, setDetailsRuleId] = react.useState(null);
|
|
1377
1893
|
const labelLookup = react.useMemo(() => createLabelLookup(metadata), [metadata]);
|
|
1378
1894
|
const catalog = react.useMemo(() => resolveProgramRulesList(metadata), [metadata]);
|
|
1379
1895
|
const scopeStageId = react.useMemo(() => resolveScopeStageId(metadata), [metadata]);
|
|
@@ -1428,6 +1944,15 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1428
1944
|
}
|
|
1429
1945
|
return null;
|
|
1430
1946
|
}, [highlightedRuleName, selectedEntry]);
|
|
1947
|
+
const detailsRule = react.useMemo(
|
|
1948
|
+
() => catalog.find((rule) => rule.id === detailsRuleId) ?? null,
|
|
1949
|
+
[catalog, detailsRuleId]
|
|
1950
|
+
);
|
|
1951
|
+
const detailsStatus = detailsRule ? resolveDetailsStatus(
|
|
1952
|
+
isRuleInScope(detailsRule, scopeStageId),
|
|
1953
|
+
activeRuleIds.has(detailsRule.id)
|
|
1954
|
+
) : "idle";
|
|
1955
|
+
const detailsProgramStageName = detailsRule ? detailsRule.programStageId === null ? null : labelLookup.resolveStageName(detailsRule.programStageId) : void 0;
|
|
1431
1956
|
const graphProps = {
|
|
1432
1957
|
entries,
|
|
1433
1958
|
fieldState,
|
|
@@ -1466,10 +1991,22 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1466
1991
|
),
|
|
1467
1992
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 text-base font-bold leading-[1.35] text-dhis2-grey-900", children: translate("Rules") })
|
|
1468
1993
|
] }),
|
|
1469
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1994
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-dp8", children: [
|
|
1995
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-dhis2-grey-600", children: translate("{{scoped}} in scope \xB7 {{firing}} firing", {
|
|
1996
|
+
scoped: scopedRules.length,
|
|
1997
|
+
firing: firingCount
|
|
1998
|
+
}) }),
|
|
1999
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2000
|
+
ui.SegmentedControl,
|
|
2001
|
+
{
|
|
2002
|
+
options: SCOPE_FILTER_OPTIONS,
|
|
2003
|
+
selected: scopeFilter,
|
|
2004
|
+
onChange: ({ value }) => {
|
|
2005
|
+
setScopeFilter(value);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
)
|
|
2009
|
+
] })
|
|
1473
2010
|
] }),
|
|
1474
2011
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: PANEL_TAB_KEYS.map((key) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1475
2012
|
"button",
|
|
@@ -1509,6 +2046,7 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1509
2046
|
{
|
|
1510
2047
|
catalog,
|
|
1511
2048
|
scopeStageId,
|
|
2049
|
+
scopeFilter,
|
|
1512
2050
|
activeRuleIds,
|
|
1513
2051
|
selectedRuleId: highlightRuleId,
|
|
1514
2052
|
showConditions,
|
|
@@ -1516,6 +2054,9 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1516
2054
|
onSelectRule: (ruleId) => {
|
|
1517
2055
|
setHighlightRuleId(ruleId);
|
|
1518
2056
|
setTab("graph");
|
|
2057
|
+
},
|
|
2058
|
+
onOpenDetails: (ruleId) => {
|
|
2059
|
+
setDetailsRuleId(ruleId);
|
|
1519
2060
|
}
|
|
1520
2061
|
}
|
|
1521
2062
|
) : tab === "trace" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1557,29 +2098,66 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1557
2098
|
subtitle: graphSubtitle,
|
|
1558
2099
|
layoutKey: graphModalOpen ? "open" : "closed"
|
|
1559
2100
|
}
|
|
2101
|
+
),
|
|
2102
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2103
|
+
RuleDetailsModal,
|
|
2104
|
+
{
|
|
2105
|
+
open: detailsRuleId != null,
|
|
2106
|
+
onClose: () => {
|
|
2107
|
+
setDetailsRuleId(null);
|
|
2108
|
+
},
|
|
2109
|
+
ruleId: detailsRuleId,
|
|
2110
|
+
ruleName: detailsRule?.name ?? "",
|
|
2111
|
+
status: detailsStatus,
|
|
2112
|
+
programStageName: detailsProgramStageName,
|
|
2113
|
+
programRuleVariables: metadata.metadata.programRuleVariables
|
|
2114
|
+
}
|
|
1560
2115
|
)
|
|
1561
2116
|
]
|
|
1562
2117
|
}
|
|
1563
2118
|
);
|
|
1564
2119
|
}
|
|
2120
|
+
function sortRulesFiringFirst(visibleRules, activeRuleIds) {
|
|
2121
|
+
const firing = [];
|
|
2122
|
+
const idle = [];
|
|
2123
|
+
for (const rule of visibleRules) {
|
|
2124
|
+
(activeRuleIds.has(rule.id) ? firing : idle).push(rule);
|
|
2125
|
+
}
|
|
2126
|
+
return [...firing, ...idle];
|
|
2127
|
+
}
|
|
1565
2128
|
function RulesTab({
|
|
1566
2129
|
catalog,
|
|
1567
2130
|
scopeStageId,
|
|
2131
|
+
scopeFilter,
|
|
1568
2132
|
activeRuleIds,
|
|
1569
2133
|
selectedRuleId,
|
|
1570
2134
|
showConditions,
|
|
1571
2135
|
labelLookup,
|
|
1572
|
-
onSelectRule
|
|
2136
|
+
onSelectRule,
|
|
2137
|
+
onOpenDetails
|
|
1573
2138
|
}) {
|
|
2139
|
+
const listRef = react.useRef(null);
|
|
2140
|
+
const visibleRules = scopeFilter === "all" ? catalog : catalog.filter((rule) => isRuleInScope(rule, scopeStageId));
|
|
2141
|
+
const sortedRules = react.useMemo(
|
|
2142
|
+
() => sortRulesFiringFirst(visibleRules, activeRuleIds),
|
|
2143
|
+
[visibleRules, activeRuleIds]
|
|
2144
|
+
);
|
|
2145
|
+
useFlipReorder(
|
|
2146
|
+
react.useMemo(() => sortedRules.map((rule) => rule.id), [sortedRules]),
|
|
2147
|
+
listRef
|
|
2148
|
+
);
|
|
1574
2149
|
if (!catalog.length) {
|
|
1575
2150
|
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("This program has no rules.") });
|
|
1576
2151
|
}
|
|
1577
|
-
|
|
2152
|
+
if (!sortedRules.length) {
|
|
2153
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("No rules are in scope for this stage.") });
|
|
2154
|
+
}
|
|
2155
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ul", { ref: listRef, className: "m-0 flex list-none flex-col gap-[10px] p-0", children: sortedRules.map((rule) => {
|
|
1578
2156
|
const inScope = isRuleInScope(rule, scopeStageId);
|
|
1579
2157
|
const firing = activeRuleIds.has(rule.id);
|
|
1580
2158
|
const isSelected = selectedRuleId === rule.id;
|
|
1581
2159
|
const status = resolveCardStatus(inScope, firing);
|
|
1582
|
-
return /* @__PURE__ */ jsxRuntime.jsx("li", { className: "m-0 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2160
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { "data-rule-id": rule.id, className: "m-0 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1583
2161
|
"article",
|
|
1584
2162
|
{
|
|
1585
2163
|
role: "button",
|
|
@@ -1610,13 +2188,29 @@ function RulesTab({
|
|
|
1610
2188
|
children: rule.name
|
|
1611
2189
|
}
|
|
1612
2190
|
),
|
|
1613
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
2191
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-[6px]", children: [
|
|
2192
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2193
|
+
"span",
|
|
2194
|
+
{
|
|
2195
|
+
className: `text-[11px] font-semibold ${status.className}`,
|
|
2196
|
+
children: status.label
|
|
2197
|
+
}
|
|
2198
|
+
),
|
|
2199
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2200
|
+
"button",
|
|
2201
|
+
{
|
|
2202
|
+
type: "button",
|
|
2203
|
+
title: translate("Program rule details"),
|
|
2204
|
+
"aria-label": translate("Program rule details"),
|
|
2205
|
+
onClick: (event) => {
|
|
2206
|
+
event.stopPropagation();
|
|
2207
|
+
onOpenDetails(rule.id);
|
|
2208
|
+
},
|
|
2209
|
+
className: "inline-flex size-[22px] shrink-0 cursor-pointer items-center justify-center rounded-[3px] border-0 bg-transparent text-dhis2-grey-600 hover:bg-dhis2-grey-200 hover:text-dhis2-blue-600",
|
|
2210
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconInfo16, {})
|
|
2211
|
+
}
|
|
2212
|
+
)
|
|
2213
|
+
] })
|
|
1620
2214
|
] }),
|
|
1621
2215
|
rule.programRuleActions.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-[6px]", children: rule.programRuleActions.map((action, index) => {
|
|
1622
2216
|
const summary = formatRuleActionSummary(
|
|
@@ -1650,6 +2244,7 @@ function RulesTab({
|
|
|
1650
2244
|
}
|
|
1651
2245
|
|
|
1652
2246
|
exports.EFFECT_ICONS = EFFECT_ICONS;
|
|
2247
|
+
exports.RuleDetailsModal = RuleDetailsModal;
|
|
1653
2248
|
exports.RuleDevtoolsScope = RuleDevtoolsScope;
|
|
1654
2249
|
exports.RulesPanel = RulesPanel;
|
|
1655
2250
|
exports.createLabelLookup = createLabelLookup;
|