@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.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { IconInfo16, IconLink16, IconMail16, IconMessages16, IconErrorFilled16, IconWarningFilled16, IconStarFilled16, IconEdit16, IconView16, IconViewOff16, IconFullscreen16, Button, NoticeBox, Card, IconChevronDown16, IconChevronRight16, Tag, Divider, Chip,
|
|
1
|
+
import { IconInfo16, IconLink16, IconMail16, IconMessages16, IconErrorFilled16, IconWarningFilled16, IconStarFilled16, IconEdit16, IconView16, IconViewOff16, Modal, ModalContent, IconFullscreen16, SegmentedControl, Button, CircularLoader, ButtonStrip, NoticeBox, Card, IconChevronDown16, IconChevronRight16, Tag, Divider, Chip, ModalTitle } from '@dhis2/ui';
|
|
2
2
|
import { useFormStore, useFormStateContext } from '@nnkogift/dhis2-form-utils-hooks';
|
|
3
|
-
import { createContext, memo, useState, useMemo, useEffect, useSyncExternalStore, useCallback, useContext } from 'react';
|
|
3
|
+
import { createContext, memo, useState, useMemo, useEffect, useSyncExternalStore, useCallback, useRef, useContext, useLayoutEffect } from 'react';
|
|
4
4
|
import { selectProgramStage } from '@nnkogift/dhis2-form-utils-metadata';
|
|
5
5
|
import i18n from '@dhis2/d2-i18n';
|
|
6
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
import { useDataQuery } from '@dhis2/app-runtime';
|
|
7
8
|
import { MarkerType, Handle, Position, getSmoothStepPath, BaseEdge, EdgeLabelRenderer, ReactFlowProvider, useNodesState, useEdgesState, ReactFlow, Background, Controls, useReactFlow } from '@xyflow/react';
|
|
8
9
|
|
|
9
10
|
// src/RulesPanel.tsx
|
|
@@ -517,6 +518,463 @@ function useRuleTraceStore() {
|
|
|
517
518
|
return store;
|
|
518
519
|
}
|
|
519
520
|
|
|
521
|
+
// src/programRuleDetailQuery.ts
|
|
522
|
+
var PROGRAM_RULE_ACTION_DETAIL_FIELDS = [
|
|
523
|
+
"id",
|
|
524
|
+
"programRuleActionType",
|
|
525
|
+
"priority",
|
|
526
|
+
"content",
|
|
527
|
+
"data",
|
|
528
|
+
"location",
|
|
529
|
+
"dataElement[id,displayName]",
|
|
530
|
+
"trackedEntityAttribute[id,displayName]",
|
|
531
|
+
"option[id,displayName]",
|
|
532
|
+
"optionGroup[id,displayName]",
|
|
533
|
+
"programStageSection[id,displayName]",
|
|
534
|
+
"programStage[id,displayName]",
|
|
535
|
+
"programSection[id,displayName]"
|
|
536
|
+
].join(",");
|
|
537
|
+
var PROGRAM_RULE_DETAIL_FIELDS = [
|
|
538
|
+
"id",
|
|
539
|
+
"name",
|
|
540
|
+
"displayName",
|
|
541
|
+
"code",
|
|
542
|
+
"description",
|
|
543
|
+
"condition",
|
|
544
|
+
"priority",
|
|
545
|
+
"lastUpdated",
|
|
546
|
+
"lastUpdatedBy[displayName]",
|
|
547
|
+
"program[id,displayName]",
|
|
548
|
+
"programStage[id,displayName]",
|
|
549
|
+
`programRuleActions[${PROGRAM_RULE_ACTION_DETAIL_FIELDS}]`
|
|
550
|
+
].join(",");
|
|
551
|
+
var programRuleDetailQuery = {
|
|
552
|
+
programRule: {
|
|
553
|
+
resource: "programRules",
|
|
554
|
+
id: (variables) => variables.ruleId,
|
|
555
|
+
params: {
|
|
556
|
+
fields: PROGRAM_RULE_DETAIL_FIELDS
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// src/useProgramRuleDetail.ts
|
|
562
|
+
function isCurrentDetail(ruleId, detail) {
|
|
563
|
+
return ruleId != null && detail?.id === ruleId;
|
|
564
|
+
}
|
|
565
|
+
function resolveDetailState(ruleId, detail, hasError) {
|
|
566
|
+
const isCurrent = isCurrentDetail(ruleId, detail);
|
|
567
|
+
return {
|
|
568
|
+
detail: isCurrent ? detail : void 0,
|
|
569
|
+
loading: ruleId != null && !isCurrent && !hasError
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function useProgramRuleDetail(ruleId) {
|
|
573
|
+
const { data, error, refetch } = useDataQuery(
|
|
574
|
+
programRuleDetailQuery,
|
|
575
|
+
{ lazy: true }
|
|
576
|
+
);
|
|
577
|
+
useEffect(() => {
|
|
578
|
+
if (ruleId) {
|
|
579
|
+
void refetch({ ruleId });
|
|
580
|
+
}
|
|
581
|
+
}, [ruleId, refetch]);
|
|
582
|
+
return { ...resolveDetailState(ruleId, data?.programRule, Boolean(error)), error };
|
|
583
|
+
}
|
|
584
|
+
var EM_DASH = "\u2014";
|
|
585
|
+
function formatTimestamp(value) {
|
|
586
|
+
if (!value) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
const date = new Date(value);
|
|
590
|
+
if (Number.isNaN(date.getTime())) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
const day = String(date.getDate());
|
|
594
|
+
const month = date.toLocaleString("en-GB", { month: "short" });
|
|
595
|
+
const year = String(date.getFullYear());
|
|
596
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
597
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
598
|
+
return `${day} ${month} ${year} ${hours}:${minutes}`;
|
|
599
|
+
}
|
|
600
|
+
function resolveStatusChip(status) {
|
|
601
|
+
switch (status) {
|
|
602
|
+
case "firing":
|
|
603
|
+
return {
|
|
604
|
+
label: translate("Firing"),
|
|
605
|
+
className: "bg-dhis2-teal-100 text-dhis2-teal-900"
|
|
606
|
+
};
|
|
607
|
+
case "idle":
|
|
608
|
+
return { label: translate("Idle"), className: "bg-dhis2-grey-200 text-dhis2-grey-700" };
|
|
609
|
+
case "out-of-scope":
|
|
610
|
+
return {
|
|
611
|
+
label: translate("Out of scope"),
|
|
612
|
+
className: "bg-dhis2-grey-200 text-dhis2-grey-600"
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
var VARIABLE_PATTERNS = [
|
|
617
|
+
{
|
|
618
|
+
pattern: /#\{[^}]*\}/g,
|
|
619
|
+
kind: "Data element",
|
|
620
|
+
className: "bg-dhis2-blue-100 text-dhis2-blue-900"
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
pattern: /A\{[^}]*\}/g,
|
|
624
|
+
kind: "Tracked entity attribute",
|
|
625
|
+
className: "bg-dhis2-teal-100 text-dhis2-teal-900"
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
pattern: /V\{[^}]*\}/g,
|
|
629
|
+
kind: "Environment variable",
|
|
630
|
+
className: "bg-dhis2-grey-200 text-dhis2-grey-900"
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
pattern: /d2:\w+(?=\()/g,
|
|
634
|
+
kind: "Function",
|
|
635
|
+
className: "bg-dhis2-yellow-100 text-dhis2-yellow-900"
|
|
636
|
+
}
|
|
637
|
+
];
|
|
638
|
+
var RESOLVABLE_VARIABLE_KINDS = /* @__PURE__ */ new Set([
|
|
639
|
+
"Data element",
|
|
640
|
+
"Tracked entity attribute"
|
|
641
|
+
]);
|
|
642
|
+
function resolveVariableDisplayName(name, programRuleVariables) {
|
|
643
|
+
const variable = programRuleVariables.find((candidate) => candidate.name === name);
|
|
644
|
+
return variable?.dataElement?.displayName ?? variable?.trackedEntityAttribute?.displayName;
|
|
645
|
+
}
|
|
646
|
+
function resolveVariableToken(token, kind, programRuleVariables) {
|
|
647
|
+
if (kind === "Function") {
|
|
648
|
+
return `${token}()`;
|
|
649
|
+
}
|
|
650
|
+
if (!RESOLVABLE_VARIABLE_KINDS.has(kind)) {
|
|
651
|
+
return token;
|
|
652
|
+
}
|
|
653
|
+
return resolveVariableDisplayName(token.slice(2, -1), programRuleVariables) ?? token;
|
|
654
|
+
}
|
|
655
|
+
function matchDistinctTokens(condition, pattern, seen) {
|
|
656
|
+
const matches = condition.match(pattern) ?? [];
|
|
657
|
+
const distinct = matches.filter((token) => !seen.has(token));
|
|
658
|
+
for (const token of distinct) {
|
|
659
|
+
seen.add(token);
|
|
660
|
+
}
|
|
661
|
+
return distinct;
|
|
662
|
+
}
|
|
663
|
+
function parseConditionVariables(condition, programRuleVariables) {
|
|
664
|
+
if (!condition) {
|
|
665
|
+
return [];
|
|
666
|
+
}
|
|
667
|
+
const seen = /* @__PURE__ */ new Set();
|
|
668
|
+
return VARIABLE_PATTERNS.flatMap(
|
|
669
|
+
({ pattern, kind, className }) => matchDistinctTokens(condition, pattern, seen).map((token) => ({
|
|
670
|
+
token,
|
|
671
|
+
kind,
|
|
672
|
+
label: resolveVariableToken(token, kind, programRuleVariables),
|
|
673
|
+
className
|
|
674
|
+
}))
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
var ACTION_TARGET_RESOLVERS = [
|
|
678
|
+
{ label: "Data element", getRef: (action) => action.dataElement },
|
|
679
|
+
{ label: "Tracked entity attribute", getRef: (action) => action.trackedEntityAttribute },
|
|
680
|
+
{ label: "Program stage section", getRef: (action) => action.programStageSection },
|
|
681
|
+
{ label: "Program stage", getRef: (action) => action.programStage },
|
|
682
|
+
{ label: "Option", getRef: (action) => action.option },
|
|
683
|
+
{ label: "Option group", getRef: (action) => action.optionGroup }
|
|
684
|
+
];
|
|
685
|
+
function formatActionTargetValue(ref) {
|
|
686
|
+
return `${ref.displayName ?? ref.id} \xB7 ${ref.id}`;
|
|
687
|
+
}
|
|
688
|
+
function resolveActionTarget(action) {
|
|
689
|
+
for (const { label, getRef } of ACTION_TARGET_RESOLVERS) {
|
|
690
|
+
const ref = getRef(action);
|
|
691
|
+
if (ref?.id) {
|
|
692
|
+
return { label: translate(label), value: formatActionTargetValue(ref) };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
function BasicDetailCell({ label, value, mono }) {
|
|
698
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
699
|
+
/* @__PURE__ */ jsx("p", { className: "m-0 text-xs text-dhis2-grey-600", children: label }),
|
|
700
|
+
/* @__PURE__ */ jsx("p", { className: `m-0 mt-[2px] text-sm text-dhis2-grey-900 ${mono ? "font-mono" : ""}`, children: value })
|
|
701
|
+
] });
|
|
702
|
+
}
|
|
703
|
+
var FEEDBACK_ACTION_TYPES = /* @__PURE__ */ new Set(["DISPLAYTEXT", "DISPLAYKEYVALUEPAIR"]);
|
|
704
|
+
function resolveDataRow(action) {
|
|
705
|
+
return action.data ? { label: translate("Data (expression)"), value: action.data, mono: true } : null;
|
|
706
|
+
}
|
|
707
|
+
function resolveContentRow(action) {
|
|
708
|
+
const showContent = Boolean(action.content) && FEEDBACK_ACTION_TYPES.has(action.programRuleActionType);
|
|
709
|
+
return showContent ? { label: translate("Content (static text)"), value: action.content ?? "" } : null;
|
|
710
|
+
}
|
|
711
|
+
function resolveLocationRow(action) {
|
|
712
|
+
return action.location ? { label: translate("Location"), value: action.location, mono: true } : null;
|
|
713
|
+
}
|
|
714
|
+
function resolveActionRows(action) {
|
|
715
|
+
const rows = [
|
|
716
|
+
resolveActionTarget(action),
|
|
717
|
+
resolveDataRow(action),
|
|
718
|
+
resolveContentRow(action),
|
|
719
|
+
resolveLocationRow(action)
|
|
720
|
+
];
|
|
721
|
+
return rows.filter((row) => row !== null);
|
|
722
|
+
}
|
|
723
|
+
function ActionCard({ action, index }) {
|
|
724
|
+
const type = action.programRuleActionType;
|
|
725
|
+
const visual = getEffectVisual(type);
|
|
726
|
+
const Icon = EFFECT_ICONS[visual.variant];
|
|
727
|
+
const rows = resolveActionRows(action);
|
|
728
|
+
return /* @__PURE__ */ jsxs(
|
|
729
|
+
"div",
|
|
730
|
+
{
|
|
731
|
+
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%)]",
|
|
732
|
+
style: { borderInlineStartWidth: 3, borderInlineStartColor: visual.edgeStroke },
|
|
733
|
+
children: [
|
|
734
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-dp12 flex items-center justify-between gap-dp8", children: [
|
|
735
|
+
/* @__PURE__ */ jsxs(
|
|
736
|
+
"span",
|
|
737
|
+
{
|
|
738
|
+
className: `inline-flex items-center gap-[4px] rounded-[4px] px-dp8 py-[2px] text-xs font-semibold ${visual.tagClassName}`,
|
|
739
|
+
children: [
|
|
740
|
+
/* @__PURE__ */ jsx(Icon, {}),
|
|
741
|
+
type
|
|
742
|
+
]
|
|
743
|
+
}
|
|
744
|
+
),
|
|
745
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] uppercase tracking-wide text-dhis2-grey-600", children: translate("Action {{n}}", { n: index + 1 }) })
|
|
746
|
+
] }),
|
|
747
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-dp10", children: rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[172px_minmax(0,1fr)] gap-dp10", children: [
|
|
748
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-dhis2-grey-600", children: row.label }),
|
|
749
|
+
/* @__PURE__ */ jsx(
|
|
750
|
+
"span",
|
|
751
|
+
{
|
|
752
|
+
className: `min-w-0 break-words text-sm ${row.mono ? "font-mono text-[12px]" : ""}`,
|
|
753
|
+
children: row.value
|
|
754
|
+
}
|
|
755
|
+
)
|
|
756
|
+
] }, row.label)) })
|
|
757
|
+
]
|
|
758
|
+
}
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
function orDash(value) {
|
|
762
|
+
return value ?? EM_DASH;
|
|
763
|
+
}
|
|
764
|
+
function resolveDetailDisplayName(rule) {
|
|
765
|
+
return rule?.displayName;
|
|
766
|
+
}
|
|
767
|
+
function resolveDetailName(rule) {
|
|
768
|
+
return rule?.name;
|
|
769
|
+
}
|
|
770
|
+
function resolveRuleDisplayName(rule, fallback) {
|
|
771
|
+
const displayName = resolveDetailDisplayName(rule) ?? resolveDetailName(rule);
|
|
772
|
+
return displayName ?? fallback;
|
|
773
|
+
}
|
|
774
|
+
function resolveRuleStageName(rule) {
|
|
775
|
+
return rule?.programStage?.displayName;
|
|
776
|
+
}
|
|
777
|
+
function resolveStageName(rule, programStageName) {
|
|
778
|
+
if (programStageName === null) {
|
|
779
|
+
return translate("All stages (registration)");
|
|
780
|
+
}
|
|
781
|
+
return orDash(resolveRuleStageName(rule) ?? programStageName);
|
|
782
|
+
}
|
|
783
|
+
function resolveLastUpdatedByName(rule) {
|
|
784
|
+
return rule?.lastUpdatedBy?.displayName;
|
|
785
|
+
}
|
|
786
|
+
function resolveLastUpdatedValue(rule) {
|
|
787
|
+
const label = formatTimestamp(rule?.lastUpdated);
|
|
788
|
+
if (!label) {
|
|
789
|
+
return EM_DASH;
|
|
790
|
+
}
|
|
791
|
+
const by = resolveLastUpdatedByName(rule);
|
|
792
|
+
return by ? `${label} \xB7 ${by}` : label;
|
|
793
|
+
}
|
|
794
|
+
function resolvePriorityCellValue(rule) {
|
|
795
|
+
return rule?.priority != null ? String(rule.priority) : EM_DASH;
|
|
796
|
+
}
|
|
797
|
+
function resolveActionsCellValue(rule) {
|
|
798
|
+
return translate("{{n}} action(s)", { n: rule?.programRuleActions?.length ?? 0 });
|
|
799
|
+
}
|
|
800
|
+
function resolveBasicDetailCells(rule, ruleName, programStageName) {
|
|
801
|
+
return [
|
|
802
|
+
{ label: translate("Name"), value: resolveRuleDisplayName(rule, ruleName) },
|
|
803
|
+
{ label: translate("Code"), value: orDash(rule?.code), mono: true },
|
|
804
|
+
{ label: translate("Identifier"), value: orDash(rule?.id), mono: true },
|
|
805
|
+
{ label: translate("Program"), value: orDash(rule?.program?.displayName) },
|
|
806
|
+
{ label: translate("Program stage"), value: resolveStageName(rule, programStageName) },
|
|
807
|
+
{ label: translate("Priority"), value: resolvePriorityCellValue(rule) },
|
|
808
|
+
{ label: translate("Actions"), value: resolveActionsCellValue(rule) },
|
|
809
|
+
{ label: translate("Last updated"), value: resolveLastUpdatedValue(rule) }
|
|
810
|
+
];
|
|
811
|
+
}
|
|
812
|
+
function BasicDetails({
|
|
813
|
+
rule,
|
|
814
|
+
ruleName,
|
|
815
|
+
programStageName
|
|
816
|
+
}) {
|
|
817
|
+
const cells = resolveBasicDetailCells(rule, ruleName, programStageName);
|
|
818
|
+
return /* @__PURE__ */ 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__ */ jsx(BasicDetailCell, { ...cell }, cell.label)) });
|
|
819
|
+
}
|
|
820
|
+
var SECTION_HEADING_CLASS = "m-0 mb-dp8 text-[13px] font-bold uppercase tracking-wide text-dhis2-grey-700";
|
|
821
|
+
function RuleDetailsHeader({
|
|
822
|
+
title,
|
|
823
|
+
chip,
|
|
824
|
+
description
|
|
825
|
+
}) {
|
|
826
|
+
return /* @__PURE__ */ jsxs("div", { className: "-mx-dp24 -mt-dp24 mb-0 border-b border-dhis2-grey-300 px-dp24 pb-dp16 pe-[44px] pt-[20px]", children: [
|
|
827
|
+
/* @__PURE__ */ jsx("p", { className: "m-0 text-[11px] font-bold uppercase tracking-[.09em] text-dhis2-grey-600", children: translate("Program rule") }),
|
|
828
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-[6px] flex flex-wrap items-center gap-dp8", children: [
|
|
829
|
+
/* @__PURE__ */ jsx("h2", { className: "m-0 text-xl font-medium leading-[1.3] text-dhis2-grey-900", children: title }),
|
|
830
|
+
/* @__PURE__ */ jsx(
|
|
831
|
+
"span",
|
|
832
|
+
{
|
|
833
|
+
className: `rounded-[4px] px-dp8 py-[2px] text-xs font-semibold ${chip.className}`,
|
|
834
|
+
children: chip.label
|
|
835
|
+
}
|
|
836
|
+
)
|
|
837
|
+
] }),
|
|
838
|
+
description ? /* @__PURE__ */ jsx(
|
|
839
|
+
"p",
|
|
840
|
+
{
|
|
841
|
+
className: "m-0 mt-[6px] text-sm text-dhis2-grey-700",
|
|
842
|
+
style: { textWrap: "pretty" },
|
|
843
|
+
children: description
|
|
844
|
+
}
|
|
845
|
+
) : null
|
|
846
|
+
] });
|
|
847
|
+
}
|
|
848
|
+
function ConditionSection({
|
|
849
|
+
condition,
|
|
850
|
+
variables
|
|
851
|
+
}) {
|
|
852
|
+
return /* @__PURE__ */ jsxs("section", { children: [
|
|
853
|
+
/* @__PURE__ */ jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Condition") }),
|
|
854
|
+
/* @__PURE__ */ 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 }),
|
|
855
|
+
variables.length ? /* @__PURE__ */ jsxs("div", { className: "mt-dp12", children: [
|
|
856
|
+
/* @__PURE__ */ jsx("p", { className: "m-0 mb-dp8 text-xs text-dhis2-grey-600", children: translate("Variables referenced") }),
|
|
857
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-dp8", children: variables.map((variable) => /* @__PURE__ */ jsxs(
|
|
858
|
+
"span",
|
|
859
|
+
{
|
|
860
|
+
className: `inline-flex items-center gap-[4px] rounded-full px-dp8 py-[2px] text-xs ${variable.className}`,
|
|
861
|
+
children: [
|
|
862
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: variable.label }),
|
|
863
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-70", children: translate(variable.kind) })
|
|
864
|
+
]
|
|
865
|
+
},
|
|
866
|
+
variable.token
|
|
867
|
+
)) })
|
|
868
|
+
] }) : null
|
|
869
|
+
] });
|
|
870
|
+
}
|
|
871
|
+
function ActionsSection({ actions }) {
|
|
872
|
+
return /* @__PURE__ */ jsxs("section", { children: [
|
|
873
|
+
/* @__PURE__ */ jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Actions") }),
|
|
874
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-dp12", children: actions.map((action, index) => /* @__PURE__ */ jsx(ActionCard, { action, index }, action.id)) })
|
|
875
|
+
] });
|
|
876
|
+
}
|
|
877
|
+
function RuleDetailsContent({
|
|
878
|
+
detail,
|
|
879
|
+
ruleName,
|
|
880
|
+
programStageName,
|
|
881
|
+
variables
|
|
882
|
+
}) {
|
|
883
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-dp24 pt-dp16", children: [
|
|
884
|
+
/* @__PURE__ */ jsxs("section", { children: [
|
|
885
|
+
/* @__PURE__ */ jsx("h3", { className: SECTION_HEADING_CLASS, children: translate("Basic details") }),
|
|
886
|
+
/* @__PURE__ */ jsx(
|
|
887
|
+
BasicDetails,
|
|
888
|
+
{
|
|
889
|
+
rule: detail,
|
|
890
|
+
ruleName,
|
|
891
|
+
programStageName
|
|
892
|
+
}
|
|
893
|
+
)
|
|
894
|
+
] }),
|
|
895
|
+
/* @__PURE__ */ jsx(ConditionSection, { condition: detail.condition, variables }),
|
|
896
|
+
/* @__PURE__ */ jsx(ActionsSection, { actions: detail.programRuleActions ?? [] })
|
|
897
|
+
] });
|
|
898
|
+
}
|
|
899
|
+
function RuleDetailsBody({
|
|
900
|
+
detail,
|
|
901
|
+
loading,
|
|
902
|
+
error,
|
|
903
|
+
ruleName,
|
|
904
|
+
programStageName,
|
|
905
|
+
variables
|
|
906
|
+
}) {
|
|
907
|
+
if (loading) {
|
|
908
|
+
return /* @__PURE__ */ jsx("div", { className: "flex min-h-[280px] items-center justify-center", children: /* @__PURE__ */ jsx(CircularLoader, { small: true }) });
|
|
909
|
+
}
|
|
910
|
+
if (error) {
|
|
911
|
+
return /* @__PURE__ */ jsx("p", { className: "m-0 text-sm text-dhis2-red-700", children: translate("Could not load this rule: {{message}}", { message: error.message }) });
|
|
912
|
+
}
|
|
913
|
+
if (!detail) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
return /* @__PURE__ */ jsx(
|
|
917
|
+
RuleDetailsContent,
|
|
918
|
+
{
|
|
919
|
+
detail,
|
|
920
|
+
ruleName,
|
|
921
|
+
programStageName,
|
|
922
|
+
variables
|
|
923
|
+
}
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
function RuleDetailsFooter({ onClose }) {
|
|
927
|
+
return /* @__PURE__ */ jsxs(
|
|
928
|
+
"div",
|
|
929
|
+
{
|
|
930
|
+
style: { order: 3 },
|
|
931
|
+
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]",
|
|
932
|
+
children: [
|
|
933
|
+
/* @__PURE__ */ jsx("p", { className: "m-0 text-sm text-dhis2-grey-600", children: translate("Read-only view. Edit rules in the Maintenance app.") }),
|
|
934
|
+
/* @__PURE__ */ jsx(ButtonStrip, { children: /* @__PURE__ */ jsx(Button, { secondary: true, onClick: onClose, children: translate("Close") }) })
|
|
935
|
+
]
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function resolveActiveRuleId(open, ruleId) {
|
|
940
|
+
return open ? ruleId : null;
|
|
941
|
+
}
|
|
942
|
+
function shouldRenderModal(open, ruleId) {
|
|
943
|
+
return open && ruleId != null;
|
|
944
|
+
}
|
|
945
|
+
function RuleDetailsModal({
|
|
946
|
+
open,
|
|
947
|
+
onClose,
|
|
948
|
+
ruleId,
|
|
949
|
+
ruleName,
|
|
950
|
+
status,
|
|
951
|
+
programStageName,
|
|
952
|
+
programRuleVariables
|
|
953
|
+
}) {
|
|
954
|
+
const { detail, loading, error } = useProgramRuleDetail(resolveActiveRuleId(open, ruleId));
|
|
955
|
+
if (!shouldRenderModal(open, ruleId)) {
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
const chip = resolveStatusChip(status);
|
|
959
|
+
const variables = parseConditionVariables(detail?.condition, programRuleVariables);
|
|
960
|
+
const title = resolveRuleDisplayName(detail, ruleName);
|
|
961
|
+
return /* @__PURE__ */ jsxs(Modal, { position: "middle", onClose, children: [
|
|
962
|
+
/* @__PURE__ */ jsx(RuleDetailsHeader, { title, chip, description: detail?.description }),
|
|
963
|
+
/* @__PURE__ */ jsx(ModalContent, { children: /* @__PURE__ */ jsx(
|
|
964
|
+
RuleDetailsBody,
|
|
965
|
+
{
|
|
966
|
+
detail,
|
|
967
|
+
loading,
|
|
968
|
+
error,
|
|
969
|
+
ruleName,
|
|
970
|
+
programStageName,
|
|
971
|
+
variables
|
|
972
|
+
}
|
|
973
|
+
) }),
|
|
974
|
+
/* @__PURE__ */ jsx(RuleDetailsFooter, { onClose })
|
|
975
|
+
] });
|
|
976
|
+
}
|
|
977
|
+
|
|
520
978
|
// src/graphLayout.ts
|
|
521
979
|
var ROLE_COLUMNS = {
|
|
522
980
|
source: 0,
|
|
@@ -1310,6 +1768,52 @@ function TraceTimeline({
|
|
|
1310
1768
|
) }, entry.id);
|
|
1311
1769
|
}) });
|
|
1312
1770
|
}
|
|
1771
|
+
var REORDER_DURATION_MS = 220;
|
|
1772
|
+
var REORDER_EASING = "ease-out";
|
|
1773
|
+
function prefersReducedMotion() {
|
|
1774
|
+
return typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1775
|
+
}
|
|
1776
|
+
function useFlipReorder(orderedIds, containerRef) {
|
|
1777
|
+
const positionsRef = useRef(/* @__PURE__ */ new Map());
|
|
1778
|
+
useLayoutEffect(() => {
|
|
1779
|
+
const container = containerRef.current;
|
|
1780
|
+
if (!container) {
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
const previousPositions = positionsRef.current;
|
|
1784
|
+
const items = container.querySelectorAll("[data-rule-id]");
|
|
1785
|
+
if (!prefersReducedMotion()) {
|
|
1786
|
+
items.forEach((item) => {
|
|
1787
|
+
const id = item.dataset.ruleId;
|
|
1788
|
+
if (!id) {
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
const previousRect = previousPositions.get(id);
|
|
1792
|
+
if (!previousRect) {
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
const currentRect = item.getBoundingClientRect();
|
|
1796
|
+
const deltaY = previousRect.top - currentRect.top;
|
|
1797
|
+
if (deltaY === 0) {
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
item.style.transition = "none";
|
|
1801
|
+
item.style.transform = `translateY(${String(deltaY)}px)`;
|
|
1802
|
+
item.getBoundingClientRect();
|
|
1803
|
+
item.style.transition = `transform ${String(REORDER_DURATION_MS)}ms ${REORDER_EASING}`;
|
|
1804
|
+
item.style.transform = "";
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
const nextPositions = /* @__PURE__ */ new Map();
|
|
1808
|
+
items.forEach((item) => {
|
|
1809
|
+
const id = item.dataset.ruleId;
|
|
1810
|
+
if (id) {
|
|
1811
|
+
nextPositions.set(id, item.getBoundingClientRect());
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1814
|
+
positionsRef.current = nextPositions;
|
|
1815
|
+
}, [orderedIds.join("|")]);
|
|
1816
|
+
}
|
|
1313
1817
|
function resolveScopeStageId(metadata) {
|
|
1314
1818
|
return metadata.formKind === "event" ? metadata.programStageId : null;
|
|
1315
1819
|
}
|
|
@@ -1331,6 +1835,12 @@ function resolveCardStatus(inScope, firing) {
|
|
|
1331
1835
|
}
|
|
1332
1836
|
return firing ? { label: translate("Firing"), className: "text-dhis2-teal-700" } : { label: translate("Idle"), className: "text-dhis2-grey-600" };
|
|
1333
1837
|
}
|
|
1838
|
+
function resolveDetailsStatus(inScope, firing) {
|
|
1839
|
+
if (!inScope) {
|
|
1840
|
+
return "out-of-scope";
|
|
1841
|
+
}
|
|
1842
|
+
return firing ? "firing" : "idle";
|
|
1843
|
+
}
|
|
1334
1844
|
function formatActionLabel(action) {
|
|
1335
1845
|
if (action.targetLabel && action.detail) {
|
|
1336
1846
|
return `${action.type} \xB7 ${action.targetLabel} = ${action.detail}`;
|
|
@@ -1350,6 +1860,10 @@ function countObservedRules(entries) {
|
|
|
1350
1860
|
return ruleIds.size;
|
|
1351
1861
|
}
|
|
1352
1862
|
var PANEL_TAB_KEYS = ["rules", "trace", "graph"];
|
|
1863
|
+
var SCOPE_FILTER_OPTIONS = [
|
|
1864
|
+
{ label: translate("In scope"), value: "scoped" },
|
|
1865
|
+
{ label: translate("All"), value: "all" }
|
|
1866
|
+
];
|
|
1353
1867
|
function resolveTabLabel(key) {
|
|
1354
1868
|
switch (key) {
|
|
1355
1869
|
case "rules":
|
|
@@ -1368,6 +1882,8 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1368
1882
|
const [selectedEntryId, setSelectedEntryId] = useState(null);
|
|
1369
1883
|
const [highlightRuleId, setHighlightRuleId] = useState(null);
|
|
1370
1884
|
const [graphModalOpen, setGraphModalOpen] = useState(false);
|
|
1885
|
+
const [scopeFilter, setScopeFilter] = useState("scoped");
|
|
1886
|
+
const [detailsRuleId, setDetailsRuleId] = useState(null);
|
|
1371
1887
|
const labelLookup = useMemo(() => createLabelLookup(metadata), [metadata]);
|
|
1372
1888
|
const catalog = useMemo(() => resolveProgramRulesList(metadata), [metadata]);
|
|
1373
1889
|
const scopeStageId = useMemo(() => resolveScopeStageId(metadata), [metadata]);
|
|
@@ -1422,6 +1938,15 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1422
1938
|
}
|
|
1423
1939
|
return null;
|
|
1424
1940
|
}, [highlightedRuleName, selectedEntry]);
|
|
1941
|
+
const detailsRule = useMemo(
|
|
1942
|
+
() => catalog.find((rule) => rule.id === detailsRuleId) ?? null,
|
|
1943
|
+
[catalog, detailsRuleId]
|
|
1944
|
+
);
|
|
1945
|
+
const detailsStatus = detailsRule ? resolveDetailsStatus(
|
|
1946
|
+
isRuleInScope(detailsRule, scopeStageId),
|
|
1947
|
+
activeRuleIds.has(detailsRule.id)
|
|
1948
|
+
) : "idle";
|
|
1949
|
+
const detailsProgramStageName = detailsRule ? detailsRule.programStageId === null ? null : labelLookup.resolveStageName(detailsRule.programStageId) : void 0;
|
|
1425
1950
|
const graphProps = {
|
|
1426
1951
|
entries,
|
|
1427
1952
|
fieldState,
|
|
@@ -1460,10 +1985,22 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1460
1985
|
),
|
|
1461
1986
|
/* @__PURE__ */ jsx("h2", { className: "m-0 text-base font-bold leading-[1.35] text-dhis2-grey-900", children: translate("Rules") })
|
|
1462
1987
|
] }),
|
|
1463
|
-
/* @__PURE__ */
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1988
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-dp8", children: [
|
|
1989
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-dhis2-grey-600", children: translate("{{scoped}} in scope \xB7 {{firing}} firing", {
|
|
1990
|
+
scoped: scopedRules.length,
|
|
1991
|
+
firing: firingCount
|
|
1992
|
+
}) }),
|
|
1993
|
+
/* @__PURE__ */ jsx(
|
|
1994
|
+
SegmentedControl,
|
|
1995
|
+
{
|
|
1996
|
+
options: SCOPE_FILTER_OPTIONS,
|
|
1997
|
+
selected: scopeFilter,
|
|
1998
|
+
onChange: ({ value }) => {
|
|
1999
|
+
setScopeFilter(value);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
)
|
|
2003
|
+
] })
|
|
1467
2004
|
] }),
|
|
1468
2005
|
/* @__PURE__ */ jsx("div", { className: "flex", children: PANEL_TAB_KEYS.map((key) => /* @__PURE__ */ jsx(
|
|
1469
2006
|
"button",
|
|
@@ -1503,6 +2040,7 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1503
2040
|
{
|
|
1504
2041
|
catalog,
|
|
1505
2042
|
scopeStageId,
|
|
2043
|
+
scopeFilter,
|
|
1506
2044
|
activeRuleIds,
|
|
1507
2045
|
selectedRuleId: highlightRuleId,
|
|
1508
2046
|
showConditions,
|
|
@@ -1510,6 +2048,9 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1510
2048
|
onSelectRule: (ruleId) => {
|
|
1511
2049
|
setHighlightRuleId(ruleId);
|
|
1512
2050
|
setTab("graph");
|
|
2051
|
+
},
|
|
2052
|
+
onOpenDetails: (ruleId) => {
|
|
2053
|
+
setDetailsRuleId(ruleId);
|
|
1513
2054
|
}
|
|
1514
2055
|
}
|
|
1515
2056
|
) : tab === "trace" ? /* @__PURE__ */ jsx(
|
|
@@ -1551,29 +2092,66 @@ function RulesPanel({ metadata, showConditions = true }) {
|
|
|
1551
2092
|
subtitle: graphSubtitle,
|
|
1552
2093
|
layoutKey: graphModalOpen ? "open" : "closed"
|
|
1553
2094
|
}
|
|
2095
|
+
),
|
|
2096
|
+
/* @__PURE__ */ jsx(
|
|
2097
|
+
RuleDetailsModal,
|
|
2098
|
+
{
|
|
2099
|
+
open: detailsRuleId != null,
|
|
2100
|
+
onClose: () => {
|
|
2101
|
+
setDetailsRuleId(null);
|
|
2102
|
+
},
|
|
2103
|
+
ruleId: detailsRuleId,
|
|
2104
|
+
ruleName: detailsRule?.name ?? "",
|
|
2105
|
+
status: detailsStatus,
|
|
2106
|
+
programStageName: detailsProgramStageName,
|
|
2107
|
+
programRuleVariables: metadata.metadata.programRuleVariables
|
|
2108
|
+
}
|
|
1554
2109
|
)
|
|
1555
2110
|
]
|
|
1556
2111
|
}
|
|
1557
2112
|
);
|
|
1558
2113
|
}
|
|
2114
|
+
function sortRulesFiringFirst(visibleRules, activeRuleIds) {
|
|
2115
|
+
const firing = [];
|
|
2116
|
+
const idle = [];
|
|
2117
|
+
for (const rule of visibleRules) {
|
|
2118
|
+
(activeRuleIds.has(rule.id) ? firing : idle).push(rule);
|
|
2119
|
+
}
|
|
2120
|
+
return [...firing, ...idle];
|
|
2121
|
+
}
|
|
1559
2122
|
function RulesTab({
|
|
1560
2123
|
catalog,
|
|
1561
2124
|
scopeStageId,
|
|
2125
|
+
scopeFilter,
|
|
1562
2126
|
activeRuleIds,
|
|
1563
2127
|
selectedRuleId,
|
|
1564
2128
|
showConditions,
|
|
1565
2129
|
labelLookup,
|
|
1566
|
-
onSelectRule
|
|
2130
|
+
onSelectRule,
|
|
2131
|
+
onOpenDetails
|
|
1567
2132
|
}) {
|
|
2133
|
+
const listRef = useRef(null);
|
|
2134
|
+
const visibleRules = scopeFilter === "all" ? catalog : catalog.filter((rule) => isRuleInScope(rule, scopeStageId));
|
|
2135
|
+
const sortedRules = useMemo(
|
|
2136
|
+
() => sortRulesFiringFirst(visibleRules, activeRuleIds),
|
|
2137
|
+
[visibleRules, activeRuleIds]
|
|
2138
|
+
);
|
|
2139
|
+
useFlipReorder(
|
|
2140
|
+
useMemo(() => sortedRules.map((rule) => rule.id), [sortedRules]),
|
|
2141
|
+
listRef
|
|
2142
|
+
);
|
|
1568
2143
|
if (!catalog.length) {
|
|
1569
2144
|
return /* @__PURE__ */ jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("This program has no rules.") });
|
|
1570
2145
|
}
|
|
1571
|
-
|
|
2146
|
+
if (!sortedRules.length) {
|
|
2147
|
+
return /* @__PURE__ */ jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("No rules are in scope for this stage.") });
|
|
2148
|
+
}
|
|
2149
|
+
return /* @__PURE__ */ jsx("ul", { ref: listRef, className: "m-0 flex list-none flex-col gap-[10px] p-0", children: sortedRules.map((rule) => {
|
|
1572
2150
|
const inScope = isRuleInScope(rule, scopeStageId);
|
|
1573
2151
|
const firing = activeRuleIds.has(rule.id);
|
|
1574
2152
|
const isSelected = selectedRuleId === rule.id;
|
|
1575
2153
|
const status = resolveCardStatus(inScope, firing);
|
|
1576
|
-
return /* @__PURE__ */ jsx("li", { className: "m-0 shrink-0", children: /* @__PURE__ */ jsxs(
|
|
2154
|
+
return /* @__PURE__ */ jsx("li", { "data-rule-id": rule.id, className: "m-0 shrink-0", children: /* @__PURE__ */ jsxs(
|
|
1577
2155
|
"article",
|
|
1578
2156
|
{
|
|
1579
2157
|
role: "button",
|
|
@@ -1604,13 +2182,29 @@ function RulesTab({
|
|
|
1604
2182
|
children: rule.name
|
|
1605
2183
|
}
|
|
1606
2184
|
),
|
|
1607
|
-
/* @__PURE__ */
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
2185
|
+
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-[6px]", children: [
|
|
2186
|
+
/* @__PURE__ */ jsx(
|
|
2187
|
+
"span",
|
|
2188
|
+
{
|
|
2189
|
+
className: `text-[11px] font-semibold ${status.className}`,
|
|
2190
|
+
children: status.label
|
|
2191
|
+
}
|
|
2192
|
+
),
|
|
2193
|
+
/* @__PURE__ */ jsx(
|
|
2194
|
+
"button",
|
|
2195
|
+
{
|
|
2196
|
+
type: "button",
|
|
2197
|
+
title: translate("Program rule details"),
|
|
2198
|
+
"aria-label": translate("Program rule details"),
|
|
2199
|
+
onClick: (event) => {
|
|
2200
|
+
event.stopPropagation();
|
|
2201
|
+
onOpenDetails(rule.id);
|
|
2202
|
+
},
|
|
2203
|
+
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",
|
|
2204
|
+
children: /* @__PURE__ */ jsx(IconInfo16, {})
|
|
2205
|
+
}
|
|
2206
|
+
)
|
|
2207
|
+
] })
|
|
1614
2208
|
] }),
|
|
1615
2209
|
rule.programRuleActions.length ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-[6px]", children: rule.programRuleActions.map((action, index) => {
|
|
1616
2210
|
const summary = formatRuleActionSummary(
|
|
@@ -1643,6 +2237,6 @@ function RulesTab({
|
|
|
1643
2237
|
}) });
|
|
1644
2238
|
}
|
|
1645
2239
|
|
|
1646
|
-
export { EFFECT_ICONS, RuleDevtoolsScope, RulesPanel, createLabelLookup, getEffectEdgeStroke, getEffectShortLabel, getEffectTagRenderProps, getEffectTagRenderPropsForVariant, getEffectVariant, getEffectVisual };
|
|
2240
|
+
export { EFFECT_ICONS, RuleDetailsModal, RuleDevtoolsScope, RulesPanel, createLabelLookup, getEffectEdgeStroke, getEffectShortLabel, getEffectTagRenderProps, getEffectTagRenderPropsForVariant, getEffectVariant, getEffectVisual };
|
|
1647
2241
|
//# sourceMappingURL=index.js.map
|
|
1648
2242
|
//# sourceMappingURL=index.js.map
|