@masterteam/governance 0.0.6 → 0.0.8

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.
@@ -1,22 +1,22 @@
1
1
  import { CommonModule } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, Injectable, computed, input, effect, Component, viewChild, linkedSignal, signal } from '@angular/core';
3
+ import { inject, Injectable, computed, input, viewChild, effect, untracked, ChangeDetectionStrategy, Component, linkedSignal, signal } from '@angular/core';
4
4
  import { Table } from '@masterteam/components/table';
5
- import * as i1 from 'primeng/skeleton';
5
+ import * as i2 from 'primeng/skeleton';
6
6
  import { SkeletonModule } from 'primeng/skeleton';
7
7
  import { Action, Selector, State, Store, select } from '@ngxs/store';
8
8
  import { HttpClient } from '@angular/common/http';
9
- import { CrudStateBase, handleApiRequest, TextFieldConfig, NumberFieldConfig, TextareaFieldConfig, SelectFieldConfig, ToggleFieldConfig, ValidatorConfig, DateFieldConfig, MultiSelectFieldConfig } from '@masterteam/components';
9
+ import { CrudStateBase, handleApiRequest, TextFieldConfig, TextareaFieldConfig, RadioCardsFieldConfig, SelectFieldConfig, NumberFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig, DateFieldConfig } from '@masterteam/components';
10
10
  import { TranslocoService, TranslocoDirective, TranslocoPipe } from '@jsverse/transloco';
11
11
  import { ModalService } from '@masterteam/components/modal';
12
- import * as i2 from '@angular/forms';
12
+ import * as i1 from '@angular/forms';
13
13
  import { FormControl, ReactiveFormsModule } from '@angular/forms';
14
14
  import { toSignal } from '@angular/core/rxjs-interop';
15
+ import { map, finalize } from 'rxjs';
15
16
  import { DynamicForm } from '@masterteam/forms/dynamic-form';
16
17
  import { Button } from '@masterteam/components/button';
17
- import { ModalRef, DialogService } from '@masterteam/components/dialog';
18
- import { finalize } from 'rxjs';
19
- import { Breadcrumb } from '@masterteam/components/breadcrumb';
18
+ import { ModalRef } from '@masterteam/components/dialog';
19
+ import { ToastService } from '@masterteam/components/toast';
20
20
  import { Icon } from '@masterteam/icons';
21
21
  import { Chip } from '@masterteam/components/chip';
22
22
 
@@ -594,623 +594,1927 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
594
594
  args: [{ providedIn: 'root' }]
595
595
  }] });
596
596
 
597
+ const GOVERNANCE_RULE_RECIPE_DEFINITIONS = [
598
+ {
599
+ key: 'require-module',
600
+ ruleTypeKeys: ['ModuleExists'],
601
+ labelKey: 'wizard.recipes.require-module.label',
602
+ },
603
+ {
604
+ key: 'prevent-module',
605
+ ruleTypeKeys: ['ModuleNotExists', 'ModuleDoesNotExist'],
606
+ labelKey: 'wizard.recipes.prevent-module.label',
607
+ },
608
+ {
609
+ key: 'module-max-count',
610
+ ruleTypeKeys: ['ModuleMaxCount'],
611
+ labelKey: 'wizard.recipes.module-max-count.label',
612
+ },
613
+ {
614
+ key: 'module-min-count',
615
+ ruleTypeKeys: ['ModuleMinCount'],
616
+ labelKey: 'wizard.recipes.module-min-count.label',
617
+ },
618
+ {
619
+ key: 'phase-gate-completed',
620
+ ruleTypeKeys: ['PhaseGateCompleted'],
621
+ labelKey: 'wizard.recipes.phase-gate-completed.label',
622
+ },
623
+ {
624
+ key: 'phase-gate-module-required',
625
+ ruleTypeKeys: ['PhaseGateModuleRequired'],
626
+ labelKey: 'wizard.recipes.phase-gate-module-required.label',
627
+ },
628
+ {
629
+ key: 'no-active-request',
630
+ ruleTypeKeys: ['NoActiveRequest'],
631
+ labelKey: 'wizard.recipes.no-active-request.label',
632
+ },
633
+ {
634
+ key: 'property-value',
635
+ ruleTypeKeys: ['PropertyValueEquals', 'FieldValue'],
636
+ labelKey: 'wizard.recipes.property-value.label',
637
+ },
638
+ ];
639
+ const GOVERNANCE_APPLIES_TO_OPTIONS = [
640
+ {
641
+ value: 'level',
642
+ labelKey: 'wizard.applies-to.level.label',
643
+ },
644
+ {
645
+ value: 'any-module',
646
+ labelKey: 'wizard.applies-to.any-module.label',
647
+ },
648
+ {
649
+ value: 'specific-module',
650
+ labelKey: 'wizard.applies-to.specific-module.label',
651
+ },
652
+ {
653
+ value: 'phase-gate',
654
+ labelKey: 'wizard.applies-to.phase-gate.label',
655
+ },
656
+ ];
657
+ const GOVERNANCE_ACTION_OPTIONS = [
658
+ {
659
+ value: 'create',
660
+ labelKey: 'wizard.actions.create',
661
+ },
662
+ {
663
+ value: 'update',
664
+ labelKey: 'wizard.actions.update',
665
+ },
666
+ {
667
+ value: 'delete',
668
+ labelKey: 'wizard.actions.delete',
669
+ },
670
+ ];
671
+ const GOVERNANCE_OPERATOR_OPTIONS = [
672
+ {
673
+ value: 'Equals',
674
+ labelKey: 'wizard.operators.Equals',
675
+ },
676
+ {
677
+ value: 'NotEquals',
678
+ labelKey: 'wizard.operators.NotEquals',
679
+ },
680
+ {
681
+ value: 'Contains',
682
+ labelKey: 'wizard.operators.Contains',
683
+ },
684
+ {
685
+ value: 'GreaterThan',
686
+ labelKey: 'wizard.operators.GreaterThan',
687
+ },
688
+ {
689
+ value: 'LessThan',
690
+ labelKey: 'wizard.operators.LessThan',
691
+ },
692
+ ];
693
+ const GOVERNANCE_PROPERTY_OPERATORS = [
694
+ 'Equals',
695
+ 'NotEquals',
696
+ 'Contains',
697
+ 'GreaterThan',
698
+ 'LessThan',
699
+ ];
700
+ const DEFAULT_BLOCKING_STATUSES = [
701
+ 'Pending',
702
+ 'Draft',
703
+ 'InProgress',
704
+ ];
705
+ const DEFAULT_GOVERNANCE_RULE_FORM_VALUE = {
706
+ key: '',
707
+ name: { en: '', ar: '' },
708
+ description: { en: '', ar: '' },
709
+ errorMessage: { en: '', ar: '' },
710
+ appliesTo: 'any-module',
711
+ selectedModuleKey: null,
712
+ actionKey: null,
713
+ recipeKey: null,
714
+ requiredModuleKey: null,
715
+ blockedModuleKey: null,
716
+ countedModuleKey: null,
717
+ countValue: 1,
718
+ phaseGateSchemaId: null,
719
+ requestSchemaId: null,
720
+ blockingStatuses: [],
721
+ propertyKey: null,
722
+ operator: 'Equals',
723
+ expectedValue: '',
724
+ priority: 10,
725
+ stopOnFailure: true,
726
+ isActive: true,
727
+ };
728
+ function readGovernanceRuleTypeKey(ruleType) {
729
+ return String(ruleType.key ?? ruleType.type ?? '').trim();
730
+ }
731
+ function readGovernanceRuleKey(rule) {
732
+ return String(rule.ruleTypeKey ?? '').trim();
733
+ }
734
+ function resolveRecipeDefinition(recipeKey) {
735
+ return (GOVERNANCE_RULE_RECIPE_DEFINITIONS.find((definition) => definition.key === recipeKey) ?? null);
736
+ }
737
+ function resolveRecipeByRuleTypeKey(ruleTypeKey) {
738
+ const normalized = String(ruleTypeKey ?? '').trim();
739
+ if (!normalized) {
740
+ return null;
741
+ }
742
+ return (GOVERNANCE_RULE_RECIPE_DEFINITIONS.find((definition) => definition.ruleTypeKeys.some((key) => key === normalized))?.key ?? null);
743
+ }
744
+ function findRuleTypeByRecipe(ruleTypes, recipeKey) {
745
+ const definition = resolveRecipeDefinition(recipeKey);
746
+ if (!definition) {
747
+ return null;
748
+ }
749
+ return (ruleTypes.find((ruleType) => definition.ruleTypeKeys.includes(readGovernanceRuleTypeKey(ruleType))) ?? null);
750
+ }
751
+ function findConfigurationSchemaField(ruleType, fieldKey) {
752
+ if (!ruleType) {
753
+ return null;
754
+ }
755
+ return (ruleType.configurationSchema.find((field) => field.key === fieldKey) ?? null);
756
+ }
757
+
758
+ function mapAppliesToToTargetScope(appliesTo) {
759
+ switch (appliesTo) {
760
+ case 'level':
761
+ return 'Level';
762
+ case 'phase-gate':
763
+ return 'PhaseGate';
764
+ case 'any-module':
765
+ case 'specific-module':
766
+ default:
767
+ return 'Module';
768
+ }
769
+ }
770
+ function mapActionToOperationKey(actionKey) {
771
+ switch (actionKey) {
772
+ case 'update':
773
+ return 'Update';
774
+ case 'delete':
775
+ return 'Delete';
776
+ case 'create':
777
+ default:
778
+ return 'Create';
779
+ }
780
+ }
781
+ function mapOperationKeyToAction(operationKey) {
782
+ switch (String(operationKey ?? '')
783
+ .trim()
784
+ .toLowerCase()) {
785
+ case 'create':
786
+ return 'create';
787
+ case 'update':
788
+ return 'update';
789
+ case 'delete':
790
+ return 'delete';
791
+ default:
792
+ return null;
793
+ }
794
+ }
795
+ function toNullableString(value) {
796
+ if (value === null || value === undefined) {
797
+ return null;
798
+ }
799
+ const text = String(value).trim();
800
+ return text ? text : null;
801
+ }
802
+ function toNullableNumber(value) {
803
+ if (value === null || value === undefined || value === '') {
804
+ return null;
805
+ }
806
+ const numeric = typeof value === 'number'
807
+ ? value
808
+ : Number(String(value).replace(/[^\d.-]/g, ''));
809
+ return Number.isFinite(numeric) ? numeric : null;
810
+ }
811
+ function toStringArray(value) {
812
+ if (!Array.isArray(value)) {
813
+ return [];
814
+ }
815
+ return value
816
+ .map((item) => toNullableString(item))
817
+ .filter((item) => !!item);
818
+ }
819
+ function trimBilingualValue(value) {
820
+ return {
821
+ en: value.en.trim(),
822
+ ar: value.ar.trim(),
823
+ };
824
+ }
825
+ function buildRecipeConfiguration(formValue) {
826
+ switch (formValue.recipeKey) {
827
+ case 'require-module': {
828
+ const requiredModuleKey = toNullableString(formValue.requiredModuleKey);
829
+ if (!requiredModuleKey) {
830
+ return undefined;
831
+ }
832
+ const minCount = toNullableNumber(formValue.countValue);
833
+ return minCount && minCount > 1
834
+ ? { requiredModuleKey, minCount }
835
+ : { requiredModuleKey };
836
+ }
837
+ case 'prevent-module': {
838
+ const moduleKey = toNullableString(formValue.blockedModuleKey);
839
+ return moduleKey ? { moduleKey } : undefined;
840
+ }
841
+ case 'module-max-count': {
842
+ const moduleKey = toNullableString(formValue.countedModuleKey);
843
+ const maxCount = toNullableNumber(formValue.countValue);
844
+ return moduleKey && maxCount !== null
845
+ ? { moduleKey, maxCount }
846
+ : undefined;
847
+ }
848
+ case 'module-min-count': {
849
+ const moduleKey = toNullableString(formValue.countedModuleKey);
850
+ const minCount = toNullableNumber(formValue.countValue);
851
+ return moduleKey && minCount !== null
852
+ ? { moduleKey, minCount }
853
+ : undefined;
854
+ }
855
+ case 'phase-gate-completed': {
856
+ const phaseGateSchemaId = toNullableNumber(formValue.phaseGateSchemaId);
857
+ return phaseGateSchemaId !== null ? { phaseGateSchemaId } : undefined;
858
+ }
859
+ case 'phase-gate-module-required': {
860
+ const requiredModuleKey = toNullableString(formValue.requiredModuleKey);
861
+ if (!requiredModuleKey) {
862
+ return undefined;
863
+ }
864
+ const phaseGateSchemaId = toNullableNumber(formValue.phaseGateSchemaId);
865
+ return phaseGateSchemaId !== null
866
+ ? { requiredModuleKey, phaseGateSchemaId }
867
+ : { requiredModuleKey };
868
+ }
869
+ case 'no-active-request': {
870
+ const requestSchemaId = toNullableNumber(formValue.requestSchemaId);
871
+ const blockingStatuses = toStringArray(formValue.blockingStatuses);
872
+ const configuration = {};
873
+ if (requestSchemaId !== null) {
874
+ configuration['requestSchemaId'] = requestSchemaId;
875
+ }
876
+ if (blockingStatuses.length > 0) {
877
+ configuration['blockingStatuses'] = blockingStatuses;
878
+ }
879
+ return Object.keys(configuration).length > 0 ? configuration : undefined;
880
+ }
881
+ case 'property-value': {
882
+ const propertyKey = toNullableString(formValue.propertyKey);
883
+ const operator = toNullableString(formValue.operator);
884
+ const expectedValue = toNullableString(formValue.expectedValue);
885
+ return propertyKey && operator && expectedValue !== null
886
+ ? { propertyKey, operator, expectedValue }
887
+ : undefined;
888
+ }
889
+ default:
890
+ return undefined;
891
+ }
892
+ }
893
+ function buildCreatePayload(formValue, ruleType) {
894
+ const name = trimBilingualValue(formValue.name);
895
+ const description = trimBilingualValue(formValue.description);
896
+ const errorMessage = trimBilingualValue(formValue.errorMessage);
897
+ return {
898
+ key: formValue.key.trim(),
899
+ name,
900
+ description: description.en || description.ar ? description : undefined,
901
+ errorMessage,
902
+ ruleType: readGovernanceRuleTypeKey(ruleType),
903
+ targetScope: mapAppliesToToTargetScope(formValue.appliesTo),
904
+ targetModuleKey: formValue.appliesTo === 'specific-module'
905
+ ? (toNullableString(formValue.selectedModuleKey) ?? undefined)
906
+ : undefined,
907
+ targetOperationKey: formValue.actionKey !== null
908
+ ? mapActionToOperationKey(formValue.actionKey)
909
+ : undefined,
910
+ configuration: buildRecipeConfiguration(formValue),
911
+ priority: toNullableNumber(formValue.priority) ?? 10,
912
+ stopOnFailure: formValue.stopOnFailure ?? true,
913
+ };
914
+ }
915
+ function buildUpdatePayload(formValue, ruleType) {
916
+ return {
917
+ ...buildCreatePayload(formValue, ruleType),
918
+ isActive: formValue.isActive ?? true,
919
+ };
920
+ }
921
+ function mapRuleToWizardFormValue(rule) {
922
+ const recipeKey = resolveRecipeByRuleTypeKey(readGovernanceRuleKey(rule));
923
+ if (!recipeKey) {
924
+ return null;
925
+ }
926
+ const appliesTo = resolveAppliesTo(rule, recipeKey);
927
+ const actionKey = mapOperationKeyToAction(rule.targetOperationKey);
928
+ if (!actionKey) {
929
+ return null;
930
+ }
931
+ const configuration = rule.configuration ?? {};
932
+ const formValue = {
933
+ ...DEFAULT_GOVERNANCE_RULE_FORM_VALUE,
934
+ key: rule.key ?? '',
935
+ name: {
936
+ en: rule.name?.en ?? '',
937
+ ar: rule.name?.ar ?? '',
938
+ },
939
+ description: {
940
+ en: rule.description?.en ?? '',
941
+ ar: rule.description?.ar ?? '',
942
+ },
943
+ errorMessage: {
944
+ en: rule.errorMessage?.en ?? '',
945
+ ar: rule.errorMessage?.ar ?? '',
946
+ },
947
+ appliesTo,
948
+ selectedModuleKey: appliesTo === 'specific-module'
949
+ ? toNullableString(rule.targetModuleKey)
950
+ : null,
951
+ actionKey,
952
+ recipeKey,
953
+ priority: rule.priority ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.priority,
954
+ stopOnFailure: rule.stopOnFailure ?? true,
955
+ isActive: rule.isActive ?? true,
956
+ requiredModuleKey: toNullableString(configuration['requiredModuleKey'] ?? configuration['moduleKey']),
957
+ blockedModuleKey: toNullableString(configuration['moduleKey']),
958
+ countedModuleKey: toNullableString(configuration['moduleKey']),
959
+ countValue: toNullableNumber(configuration['maxCount'] ??
960
+ configuration['minCount'] ??
961
+ DEFAULT_GOVERNANCE_RULE_FORM_VALUE.countValue) ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.countValue,
962
+ phaseGateSchemaId: toNullableString(configuration['phaseGateSchemaId']),
963
+ requestSchemaId: toNullableString(configuration['requestSchemaId']),
964
+ blockingStatuses: toStringArray(configuration['blockingStatuses']),
965
+ propertyKey: toNullableString(configuration['propertyKey'] ?? configuration['fieldKey']),
966
+ operator: toNullableString(configuration['operator']) ??
967
+ DEFAULT_GOVERNANCE_RULE_FORM_VALUE.operator,
968
+ expectedValue: toNullableString(configuration['expectedValue'] ?? configuration['value']) ?? '',
969
+ };
970
+ if (recipeKey === 'prevent-module') {
971
+ formValue.blockedModuleKey = toNullableString(configuration['moduleKey']);
972
+ formValue.requiredModuleKey = null;
973
+ }
974
+ if (recipeKey === 'phase-gate-completed') {
975
+ formValue.requiredModuleKey = null;
976
+ }
977
+ if (recipeKey === 'no-active-request') {
978
+ formValue.requiredModuleKey = null;
979
+ formValue.blockedModuleKey = null;
980
+ formValue.countedModuleKey = null;
981
+ }
982
+ if (recipeKey === 'property-value') {
983
+ formValue.requiredModuleKey = null;
984
+ formValue.blockedModuleKey = null;
985
+ formValue.countedModuleKey = null;
986
+ }
987
+ return formValue;
988
+ }
989
+ function resolveAppliesTo(rule, recipeKey) {
990
+ const scope = String(rule.targetScope ?? '').trim();
991
+ const targetModuleKey = String(rule.targetModuleKey ?? '').trim();
992
+ if (scope === 'Level') {
993
+ return 'level';
994
+ }
995
+ if (scope === 'PhaseGate' ||
996
+ ((recipeKey === 'phase-gate-completed' ||
997
+ recipeKey === 'phase-gate-module-required') &&
998
+ targetModuleKey.toLowerCase() === 'phasegate')) {
999
+ return 'phase-gate';
1000
+ }
1001
+ return targetModuleKey ? 'specific-module' : 'any-module';
1002
+ }
1003
+ function mapRuleToLegacyFormValue(rule) {
1004
+ const formData = {
1005
+ key: rule.key,
1006
+ priority: rule.priority,
1007
+ name: {
1008
+ en: rule.name?.en ?? '',
1009
+ ar: rule.name?.ar ?? '',
1010
+ },
1011
+ description: {
1012
+ en: rule.description?.en ?? '',
1013
+ ar: rule.description?.ar ?? '',
1014
+ },
1015
+ errorMessage: {
1016
+ en: rule.errorMessage?.en ?? '',
1017
+ ar: rule.errorMessage?.ar ?? '',
1018
+ },
1019
+ ruleType: rule.ruleTypeKey,
1020
+ targetScope: rule.targetScope,
1021
+ targetModuleKey: rule.targetModuleKey ?? '',
1022
+ targetOperationKey: rule.targetOperationKey ?? '',
1023
+ stopOnFailure: rule.stopOnFailure,
1024
+ isActive: rule.isActive,
1025
+ };
1026
+ Object.entries(rule.configuration ?? {}).forEach(([key, value]) => {
1027
+ formData[`config_${key}`] = value;
1028
+ });
1029
+ return formData;
1030
+ }
1031
+ function packLegacyFormToCreateDto(formValue) {
1032
+ const name = trimBilingualValue(formValue.name);
1033
+ const description = trimBilingualValue(formValue.description);
1034
+ const errorMessage = trimBilingualValue(formValue.errorMessage);
1035
+ return {
1036
+ key: String(formValue.key ?? '').trim(),
1037
+ name,
1038
+ description: description.en || description.ar ? description : undefined,
1039
+ errorMessage,
1040
+ ruleType: String(formValue.ruleType ?? ''),
1041
+ targetScope: String(formValue.targetScope ?? ''),
1042
+ targetModuleKey: String(formValue.targetModuleKey ?? '') || undefined,
1043
+ targetOperationKey: String(formValue.targetOperationKey ?? '') || undefined,
1044
+ configuration: extractLegacyConfiguration(formValue),
1045
+ priority: Number(formValue.priority ?? 0),
1046
+ stopOnFailure: Boolean(formValue.stopOnFailure ?? true),
1047
+ };
1048
+ }
1049
+ function packLegacyFormToUpdateDto(formValue) {
1050
+ return {
1051
+ ...packLegacyFormToCreateDto(formValue),
1052
+ isActive: Boolean(formValue.isActive ?? true),
1053
+ };
1054
+ }
1055
+ function extractLegacyConfiguration(formValue) {
1056
+ const configuration = {};
1057
+ Object.entries(formValue).forEach(([key, value]) => {
1058
+ if (key.startsWith('config_') &&
1059
+ value !== null &&
1060
+ value !== undefined &&
1061
+ value !== '') {
1062
+ configuration[key.replace('config_', '')] = value;
1063
+ }
1064
+ });
1065
+ return Object.keys(configuration).length > 0 ? configuration : undefined;
1066
+ }
1067
+
1068
+ function buildWizardFormConfig({ translate, isEdit, recipeKey, appliesToOptions, actionOptions, recipeOptions, moduleOptions, requestSchemaOptions, propertyOptions, phaseGateOptions, blockingStatuses, }) {
1069
+ const statusOptions = blockingStatuses.map((status) => ({
1070
+ label: status,
1071
+ value: status,
1072
+ }));
1073
+ const operatorOptions = GOVERNANCE_OPERATOR_OPTIONS.map((option) => ({
1074
+ label: translate(option.labelKey),
1075
+ value: option.value,
1076
+ }));
1077
+ const countLabel = recipeKey === 'module-min-count'
1078
+ ? translate('wizard.min-count-label')
1079
+ : translate('wizard.max-count-label');
1080
+ return {
1081
+ sections: [
1082
+ {
1083
+ key: 'basicInfo',
1084
+ type: 'header',
1085
+ label: translate('basic-information'),
1086
+ order: 1,
1087
+ fields: [
1088
+ new TextFieldConfig({
1089
+ key: 'key',
1090
+ label: translate('rule-key'),
1091
+ validators: [
1092
+ ValidatorConfig.required(translate('wizard.required-message')),
1093
+ ValidatorConfig.pattern('^[a-z0-9-]+$', translate('wizard.key-pattern-message')),
1094
+ ],
1095
+ readonly: isEdit,
1096
+ order: 1,
1097
+ colSpan: 12,
1098
+ }),
1099
+ new TextFieldConfig({
1100
+ key: 'name.en',
1101
+ label: translate('name-en'),
1102
+ validators: [
1103
+ ValidatorConfig.required(translate('wizard.required-message')),
1104
+ ],
1105
+ order: 2,
1106
+ colSpan: 6,
1107
+ }),
1108
+ new TextFieldConfig({
1109
+ key: 'name.ar',
1110
+ label: translate('name-ar'),
1111
+ validators: [
1112
+ ValidatorConfig.required(translate('wizard.required-message')),
1113
+ ],
1114
+ order: 3,
1115
+ colSpan: 6,
1116
+ }),
1117
+ new TextareaFieldConfig({
1118
+ key: 'description.en',
1119
+ label: translate('description-en'),
1120
+ rows: 3,
1121
+ order: 4,
1122
+ colSpan: 6,
1123
+ }),
1124
+ new TextareaFieldConfig({
1125
+ key: 'description.ar',
1126
+ label: translate('description-ar'),
1127
+ rows: 3,
1128
+ order: 5,
1129
+ colSpan: 6,
1130
+ }),
1131
+ new TextareaFieldConfig({
1132
+ key: 'errorMessage.en',
1133
+ label: translate('error-message-en'),
1134
+ validators: [
1135
+ ValidatorConfig.required(translate('wizard.required-message')),
1136
+ ],
1137
+ rows: 3,
1138
+ order: 6,
1139
+ colSpan: 6,
1140
+ }),
1141
+ new TextareaFieldConfig({
1142
+ key: 'errorMessage.ar',
1143
+ label: translate('error-message-ar'),
1144
+ validators: [
1145
+ ValidatorConfig.required(translate('wizard.required-message')),
1146
+ ],
1147
+ rows: 3,
1148
+ order: 7,
1149
+ colSpan: 6,
1150
+ }),
1151
+ ],
1152
+ },
1153
+ {
1154
+ key: 'appliesTo',
1155
+ type: 'header',
1156
+ label: translate('wizard.where-question'),
1157
+ order: 2,
1158
+ fields: [
1159
+ new RadioCardsFieldConfig({
1160
+ key: 'appliesTo',
1161
+ options: appliesToOptions,
1162
+ optionLabel: 'name',
1163
+ optionValue: 'id',
1164
+ validators: [
1165
+ ValidatorConfig.required(translate('wizard.required-message')),
1166
+ ],
1167
+ order: 1,
1168
+ colSpan: 12,
1169
+ size: 'small',
1170
+ }),
1171
+ new SelectFieldConfig({
1172
+ key: 'selectedModuleKey',
1173
+ label: translate('wizard.specific-module-label'),
1174
+ placeholder: translate('wizard.select-placeholder'),
1175
+ options: moduleOptions,
1176
+ optionLabel: 'label',
1177
+ optionValue: 'value',
1178
+ validators: [
1179
+ ValidatorConfig.required(translate('wizard.required-message')),
1180
+ ],
1181
+ order: 2,
1182
+ colSpan: 6,
1183
+ relations: [
1184
+ {
1185
+ key: 'appliesTo',
1186
+ value: 'specific-module',
1187
+ action: 'show',
1188
+ },
1189
+ ],
1190
+ }),
1191
+ ],
1192
+ },
1193
+ {
1194
+ key: 'action',
1195
+ type: 'header',
1196
+ label: translate('wizard.when-question'),
1197
+ order: 3,
1198
+ fields: [
1199
+ new RadioCardsFieldConfig({
1200
+ key: 'actionKey',
1201
+ options: actionOptions,
1202
+ optionLabel: 'name',
1203
+ optionValue: 'id',
1204
+ validators: [
1205
+ ValidatorConfig.required(translate('wizard.required-message')),
1206
+ ],
1207
+ order: 1,
1208
+ colSpan: 12,
1209
+ size: 'small',
1210
+ }),
1211
+ ],
1212
+ },
1213
+ {
1214
+ key: 'recipe',
1215
+ type: 'header',
1216
+ label: translate('wizard.what-question'),
1217
+ order: 4,
1218
+ fields: [
1219
+ new RadioCardsFieldConfig({
1220
+ key: 'recipeKey',
1221
+ options: recipeOptions,
1222
+ optionLabel: 'name',
1223
+ optionValue: 'id',
1224
+ validators: [
1225
+ ValidatorConfig.required(translate('wizard.required-message')),
1226
+ ],
1227
+ order: 1,
1228
+ colSpan: 12,
1229
+ size: 'small',
1230
+ }),
1231
+ ],
1232
+ },
1233
+ {
1234
+ key: 'details',
1235
+ type: 'header',
1236
+ label: translate('wizard.details-question'),
1237
+ order: 5,
1238
+ fields: [
1239
+ new SelectFieldConfig({
1240
+ key: 'requiredModuleKey',
1241
+ label: translate('wizard.required-module-label'),
1242
+ placeholder: translate('wizard.select-placeholder'),
1243
+ options: moduleOptions,
1244
+ optionLabel: 'label',
1245
+ optionValue: 'value',
1246
+ validators: [
1247
+ ValidatorConfig.required(translate('wizard.required-message')),
1248
+ ],
1249
+ order: 1,
1250
+ colSpan: 6,
1251
+ relations: [
1252
+ {
1253
+ key: 'recipeKey',
1254
+ value: 'require-module',
1255
+ action: 'show',
1256
+ },
1257
+ {
1258
+ key: 'recipeKey',
1259
+ value: 'phase-gate-module-required',
1260
+ action: 'show',
1261
+ },
1262
+ ],
1263
+ }),
1264
+ new SelectFieldConfig({
1265
+ key: 'blockedModuleKey',
1266
+ label: translate('wizard.blocked-module-label'),
1267
+ placeholder: translate('wizard.select-placeholder'),
1268
+ options: moduleOptions,
1269
+ optionLabel: 'label',
1270
+ optionValue: 'value',
1271
+ validators: [
1272
+ ValidatorConfig.required(translate('wizard.required-message')),
1273
+ ],
1274
+ order: 2,
1275
+ colSpan: 6,
1276
+ relations: [
1277
+ {
1278
+ key: 'recipeKey',
1279
+ value: 'prevent-module',
1280
+ action: 'show',
1281
+ },
1282
+ ],
1283
+ }),
1284
+ new SelectFieldConfig({
1285
+ key: 'countedModuleKey',
1286
+ label: translate('wizard.counted-module-label'),
1287
+ placeholder: translate('wizard.select-placeholder'),
1288
+ options: moduleOptions,
1289
+ optionLabel: 'label',
1290
+ optionValue: 'value',
1291
+ validators: [
1292
+ ValidatorConfig.required(translate('wizard.required-message')),
1293
+ ],
1294
+ order: 3,
1295
+ colSpan: 6,
1296
+ relations: [
1297
+ {
1298
+ key: 'recipeKey',
1299
+ value: 'module-max-count',
1300
+ action: 'show',
1301
+ },
1302
+ {
1303
+ key: 'recipeKey',
1304
+ value: 'module-min-count',
1305
+ action: 'show',
1306
+ },
1307
+ ],
1308
+ }),
1309
+ new NumberFieldConfig({
1310
+ key: 'countValue',
1311
+ label: countLabel,
1312
+ validators: [
1313
+ ValidatorConfig.required(translate('wizard.required-message')),
1314
+ ValidatorConfig.min(1),
1315
+ ],
1316
+ min: 1,
1317
+ order: 4,
1318
+ colSpan: 6,
1319
+ relations: [
1320
+ {
1321
+ key: 'recipeKey',
1322
+ value: 'module-max-count',
1323
+ action: 'show',
1324
+ },
1325
+ {
1326
+ key: 'recipeKey',
1327
+ value: 'module-min-count',
1328
+ action: 'show',
1329
+ },
1330
+ ],
1331
+ }),
1332
+ new SelectFieldConfig({
1333
+ key: 'phaseGateCompletedSchemaId',
1334
+ label: translate('wizard.phase-gate-required-label'),
1335
+ placeholder: translate('wizard.select-placeholder'),
1336
+ options: phaseGateOptions,
1337
+ optionLabel: 'label',
1338
+ optionValue: 'value',
1339
+ validators: [
1340
+ ValidatorConfig.required(translate('wizard.required-message')),
1341
+ ],
1342
+ order: 5,
1343
+ colSpan: 6,
1344
+ relations: [
1345
+ {
1346
+ key: 'recipeKey',
1347
+ value: 'phase-gate-completed',
1348
+ action: 'show',
1349
+ },
1350
+ ],
1351
+ }),
1352
+ new SelectFieldConfig({
1353
+ key: 'phaseGateOptionalSchemaId',
1354
+ label: translate('wizard.phase-gate-optional-label'),
1355
+ placeholder: translate('wizard.select-placeholder'),
1356
+ options: phaseGateOptions,
1357
+ optionLabel: 'label',
1358
+ optionValue: 'value',
1359
+ order: 6,
1360
+ colSpan: 6,
1361
+ relations: [
1362
+ {
1363
+ key: 'recipeKey',
1364
+ value: 'phase-gate-module-required',
1365
+ action: 'show',
1366
+ },
1367
+ ],
1368
+ }),
1369
+ new SelectFieldConfig({
1370
+ key: 'requestSchemaId',
1371
+ label: translate('wizard.request-schema-label'),
1372
+ placeholder: translate('wizard.request-schema-hint'),
1373
+ options: requestSchemaOptions,
1374
+ optionLabel: 'label',
1375
+ optionValue: 'value',
1376
+ order: 7,
1377
+ colSpan: 6,
1378
+ relations: [
1379
+ {
1380
+ key: 'recipeKey',
1381
+ value: 'no-active-request',
1382
+ action: 'show',
1383
+ },
1384
+ ],
1385
+ }),
1386
+ new MultiSelectFieldConfig({
1387
+ key: 'blockingStatuses',
1388
+ label: translate('wizard.blocking-statuses-label'),
1389
+ placeholder: translate('wizard.select-placeholder'),
1390
+ options: statusOptions,
1391
+ optionLabel: 'label',
1392
+ optionValue: 'value',
1393
+ order: 8,
1394
+ colSpan: 6,
1395
+ relations: [
1396
+ {
1397
+ key: 'recipeKey',
1398
+ value: 'no-active-request',
1399
+ action: 'show',
1400
+ },
1401
+ ],
1402
+ }),
1403
+ new SelectFieldConfig({
1404
+ key: 'propertyKey',
1405
+ label: translate('wizard.property-label'),
1406
+ placeholder: translate('wizard.select-placeholder'),
1407
+ options: propertyOptions,
1408
+ optionLabel: 'label',
1409
+ optionValue: 'value',
1410
+ validators: [
1411
+ ValidatorConfig.required(translate('wizard.required-message')),
1412
+ ],
1413
+ order: 9,
1414
+ colSpan: 6,
1415
+ relations: [
1416
+ {
1417
+ key: 'recipeKey',
1418
+ value: 'property-value',
1419
+ action: 'show',
1420
+ },
1421
+ ],
1422
+ }),
1423
+ new SelectFieldConfig({
1424
+ key: 'operator',
1425
+ label: translate('wizard.operator-label'),
1426
+ placeholder: translate('wizard.select-placeholder'),
1427
+ options: operatorOptions,
1428
+ optionLabel: 'label',
1429
+ optionValue: 'value',
1430
+ validators: [
1431
+ ValidatorConfig.required(translate('wizard.required-message')),
1432
+ ],
1433
+ order: 10,
1434
+ colSpan: 6,
1435
+ relations: [
1436
+ {
1437
+ key: 'recipeKey',
1438
+ value: 'property-value',
1439
+ action: 'show',
1440
+ },
1441
+ ],
1442
+ }),
1443
+ new TextFieldConfig({
1444
+ key: 'expectedValue',
1445
+ label: translate('wizard.expected-value-label'),
1446
+ validators: [
1447
+ ValidatorConfig.required(translate('wizard.required-message')),
1448
+ ],
1449
+ order: 11,
1450
+ colSpan: 12,
1451
+ relations: [
1452
+ {
1453
+ key: 'recipeKey',
1454
+ value: 'property-value',
1455
+ action: 'show',
1456
+ },
1457
+ ],
1458
+ }),
1459
+ ],
1460
+ },
1461
+ {
1462
+ key: 'advanced',
1463
+ type: 'header',
1464
+ label: translate('execution-settings'),
1465
+ order: 6,
1466
+ fields: [
1467
+ new NumberFieldConfig({
1468
+ key: 'priority',
1469
+ label: translate('priority'),
1470
+ validators: [
1471
+ ValidatorConfig.required(translate('wizard.required-message')),
1472
+ ValidatorConfig.min(0),
1473
+ ],
1474
+ min: 0,
1475
+ max: 1000,
1476
+ order: 1,
1477
+ colSpan: 6,
1478
+ }),
1479
+ new ToggleFieldConfig({
1480
+ key: 'stopOnFailure',
1481
+ label: translate('stop-on-failure'),
1482
+ order: 2,
1483
+ colSpan: 12,
1484
+ }),
1485
+ new ToggleFieldConfig({
1486
+ key: 'isActive',
1487
+ label: translate('active-rule'),
1488
+ hidden: !isEdit,
1489
+ order: 3,
1490
+ colSpan: 12,
1491
+ }),
1492
+ ],
1493
+ },
1494
+ ],
1495
+ };
1496
+ }
1497
+ function buildLegacyFormConfig({ translate, isEdit, langCode, selectedRuleType, ruleTypeOptions, scopeOptions, operationOptions, moduleOptions, requestSchemaOptions, propertyOptions, }) {
1498
+ return {
1499
+ sections: [
1500
+ {
1501
+ key: 'basicInfo',
1502
+ type: 'header',
1503
+ label: translate('basic-information'),
1504
+ order: 1,
1505
+ fields: [
1506
+ new TextFieldConfig({
1507
+ key: 'key',
1508
+ label: translate('rule-key'),
1509
+ placeholder: translate('wizard.rule-key-placeholder'),
1510
+ validators: [
1511
+ ValidatorConfig.required(),
1512
+ ValidatorConfig.pattern('^[a-z0-9-]+$', translate('wizard.key-pattern-message')),
1513
+ ],
1514
+ order: 1,
1515
+ colSpan: 12,
1516
+ readonly: isEdit,
1517
+ }),
1518
+ new NumberFieldConfig({
1519
+ key: 'priority',
1520
+ label: translate('priority'),
1521
+ placeholder: '10',
1522
+ validators: [ValidatorConfig.required(), ValidatorConfig.min(0)],
1523
+ min: 0,
1524
+ max: 1000,
1525
+ order: 2,
1526
+ colSpan: 6,
1527
+ }),
1528
+ new TextFieldConfig({
1529
+ key: 'name.en',
1530
+ label: translate('name-en'),
1531
+ placeholder: translate('name-en-placeholder'),
1532
+ validators: [ValidatorConfig.required()],
1533
+ order: 3,
1534
+ colSpan: 6,
1535
+ }),
1536
+ new TextFieldConfig({
1537
+ key: 'name.ar',
1538
+ label: translate('name-ar'),
1539
+ placeholder: translate('name-ar-placeholder'),
1540
+ validators: [ValidatorConfig.required()],
1541
+ order: 4,
1542
+ colSpan: 6,
1543
+ }),
1544
+ new TextareaFieldConfig({
1545
+ key: 'description.en',
1546
+ label: translate('description-en'),
1547
+ placeholder: translate('description-en-placeholder'),
1548
+ order: 5,
1549
+ colSpan: 6,
1550
+ }),
1551
+ new TextareaFieldConfig({
1552
+ key: 'description.ar',
1553
+ label: translate('description-ar'),
1554
+ placeholder: translate('description-ar-placeholder'),
1555
+ order: 6,
1556
+ colSpan: 6,
1557
+ }),
1558
+ new TextareaFieldConfig({
1559
+ key: 'errorMessage.en',
1560
+ label: translate('error-message-en'),
1561
+ placeholder: translate('error-message-en-placeholder'),
1562
+ validators: [ValidatorConfig.required()],
1563
+ order: 7,
1564
+ colSpan: 6,
1565
+ }),
1566
+ new TextareaFieldConfig({
1567
+ key: 'errorMessage.ar',
1568
+ label: translate('error-message-ar'),
1569
+ placeholder: translate('error-message-ar-placeholder'),
1570
+ validators: [ValidatorConfig.required()],
1571
+ order: 8,
1572
+ colSpan: 6,
1573
+ }),
1574
+ ],
1575
+ },
1576
+ {
1577
+ key: 'ruleTarget',
1578
+ type: 'header',
1579
+ label: translate('rule-target'),
1580
+ order: 2,
1581
+ fields: [
1582
+ new SelectFieldConfig({
1583
+ key: 'ruleType',
1584
+ label: translate('rule-type'),
1585
+ options: ruleTypeOptions,
1586
+ optionLabel: 'label',
1587
+ optionValue: 'value',
1588
+ validators: [ValidatorConfig.required()],
1589
+ order: 1,
1590
+ colSpan: 6,
1591
+ readonly: isEdit,
1592
+ }),
1593
+ new SelectFieldConfig({
1594
+ key: 'targetScope',
1595
+ label: translate('target-scope'),
1596
+ options: scopeOptions,
1597
+ optionLabel: 'label',
1598
+ optionValue: 'value',
1599
+ validators: [ValidatorConfig.required()],
1600
+ order: 2,
1601
+ colSpan: 6,
1602
+ }),
1603
+ new SelectFieldConfig({
1604
+ key: 'targetModuleKey',
1605
+ label: translate('target-module'),
1606
+ options: moduleOptions,
1607
+ optionLabel: 'label',
1608
+ optionValue: 'value',
1609
+ order: 3,
1610
+ colSpan: 6,
1611
+ relations: [
1612
+ {
1613
+ key: 'targetScope',
1614
+ value: 'Module',
1615
+ action: 'show',
1616
+ },
1617
+ ],
1618
+ }),
1619
+ new SelectFieldConfig({
1620
+ key: 'targetOperationKey',
1621
+ label: translate('target-operation'),
1622
+ options: operationOptions,
1623
+ optionLabel: 'label',
1624
+ optionValue: 'value',
1625
+ order: 4,
1626
+ colSpan: 6,
1627
+ }),
1628
+ ],
1629
+ },
1630
+ ...(selectedRuleType
1631
+ ? [
1632
+ buildLegacyConfigurationSection({
1633
+ translate,
1634
+ ruleType: selectedRuleType,
1635
+ langCode,
1636
+ requestSchemaOptions,
1637
+ propertyOptions,
1638
+ }),
1639
+ ]
1640
+ : []),
1641
+ {
1642
+ key: 'executionSettings',
1643
+ type: 'header',
1644
+ label: translate('execution-settings'),
1645
+ order: 4,
1646
+ fields: [
1647
+ new ToggleFieldConfig({
1648
+ key: 'stopOnFailure',
1649
+ label: translate('stop-on-failure'),
1650
+ order: 1,
1651
+ colSpan: 12,
1652
+ }),
1653
+ new ToggleFieldConfig({
1654
+ key: 'isActive',
1655
+ label: translate('active-rule'),
1656
+ order: 2,
1657
+ colSpan: 12,
1658
+ }),
1659
+ ],
1660
+ },
1661
+ ],
1662
+ };
1663
+ }
1664
+ function buildLegacyConfigurationSection({ translate, ruleType, langCode, requestSchemaOptions, propertyOptions, }) {
1665
+ return {
1666
+ key: 'configuration',
1667
+ type: 'header',
1668
+ label: translate('rule-configuration'),
1669
+ order: 3,
1670
+ fields: ruleType.configurationSchema.map((field, index) => schemaFieldToFormField({
1671
+ field,
1672
+ order: index + 1,
1673
+ langCode,
1674
+ requestSchemaOptions,
1675
+ propertyOptions,
1676
+ })),
1677
+ };
1678
+ }
1679
+ function schemaFieldToFormField({ field, order, langCode, requestSchemaOptions, propertyOptions, }) {
1680
+ const label = field.label[langCode] || field.label.en;
1681
+ const colSpan = field.colSpan ?? 6;
1682
+ const validators = field.required ? [ValidatorConfig.required()] : [];
1683
+ switch (field.type) {
1684
+ case 'text':
1685
+ return new TextFieldConfig({
1686
+ key: `config_${field.key}`,
1687
+ label,
1688
+ validators,
1689
+ order,
1690
+ colSpan,
1691
+ });
1692
+ case 'textarea':
1693
+ return new TextareaFieldConfig({
1694
+ key: `config_${field.key}`,
1695
+ label,
1696
+ validators,
1697
+ order,
1698
+ colSpan,
1699
+ });
1700
+ case 'number':
1701
+ return new NumberFieldConfig({
1702
+ key: `config_${field.key}`,
1703
+ label,
1704
+ validators,
1705
+ min: field.min,
1706
+ max: field.max,
1707
+ order,
1708
+ colSpan,
1709
+ });
1710
+ case 'select':
1711
+ return new SelectFieldConfig({
1712
+ key: `config_${field.key}`,
1713
+ label,
1714
+ options: getLegacyOptionsForField(field, requestSchemaOptions, propertyOptions),
1715
+ optionLabel: 'label',
1716
+ optionValue: 'value',
1717
+ validators,
1718
+ order,
1719
+ colSpan,
1720
+ });
1721
+ case 'multi-select':
1722
+ return new MultiSelectFieldConfig({
1723
+ key: `config_${field.key}`,
1724
+ label,
1725
+ options: getLegacyOptionsForField(field, requestSchemaOptions, propertyOptions),
1726
+ optionLabel: 'label',
1727
+ optionValue: 'value',
1728
+ validators,
1729
+ order,
1730
+ colSpan,
1731
+ });
1732
+ case 'checkbox':
1733
+ return new ToggleFieldConfig({
1734
+ key: `config_${field.key}`,
1735
+ label,
1736
+ order,
1737
+ colSpan,
1738
+ });
1739
+ case 'date':
1740
+ return new DateFieldConfig({
1741
+ key: `config_${field.key}`,
1742
+ label,
1743
+ validators,
1744
+ order,
1745
+ colSpan,
1746
+ });
1747
+ default:
1748
+ return new TextFieldConfig({
1749
+ key: `config_${field.key}`,
1750
+ label,
1751
+ validators,
1752
+ order,
1753
+ colSpan,
1754
+ });
1755
+ }
1756
+ }
1757
+ function getLegacyOptionsForField(field, requestSchemaOptions, propertyOptions) {
1758
+ switch (field.optionsSource) {
1759
+ case 'requestSchema':
1760
+ return requestSchemaOptions;
1761
+ case 'properties':
1762
+ return propertyOptions;
1763
+ case 'auto-populated':
1764
+ case 'static':
1765
+ default:
1766
+ return field.options ?? [];
1767
+ }
1768
+ }
1769
+
1770
+ function buildActionPrompt(appliesTo, actionKey, translate) {
1771
+ return translate('wizard.summary.action', {
1772
+ object: translate(`wizard.summary.objects.${appliesTo}`),
1773
+ action: translate(`wizard.summary.actions.${actionKey}`),
1774
+ });
1775
+ }
1776
+ function buildSummary({ value, translate, ...context }) {
1777
+ if (!value.actionKey || !value.recipeKey) {
1778
+ return translate('wizard.summary-empty');
1779
+ }
1780
+ return translate('wizard.summary.full', {
1781
+ action: buildActionPrompt(value.appliesTo, value.actionKey, translate),
1782
+ detail: resolveRecipeSummary({ value, translate, ...context }),
1783
+ });
1784
+ }
1785
+ function resolveRecipeSummary({ value, moduleOptions, requestSchemaOptions, propertyOptions, phaseGateOptions, translate, }) {
1786
+ switch (value.recipeKey) {
1787
+ case 'require-module': {
1788
+ const moduleLabel = findOptionLabel(moduleOptions, value.requiredModuleKey);
1789
+ return translate('wizard.summary.require-module', {
1790
+ module: moduleLabel ??
1791
+ value.requiredModuleKey ??
1792
+ translate('wizard.summary.fallbacks.module'),
1793
+ });
1794
+ }
1795
+ case 'prevent-module': {
1796
+ const moduleLabel = findOptionLabel(moduleOptions, value.blockedModuleKey);
1797
+ return translate('wizard.summary.prevent-module', {
1798
+ module: moduleLabel ??
1799
+ value.blockedModuleKey ??
1800
+ translate('wizard.summary.fallbacks.selected-module'),
1801
+ });
1802
+ }
1803
+ case 'module-max-count':
1804
+ return translate('wizard.summary.module-max-count', {
1805
+ count: value.countValue ?? 0,
1806
+ module: findOptionLabel(moduleOptions, value.countedModuleKey) ??
1807
+ value.countedModuleKey ??
1808
+ translate('wizard.summary.fallbacks.modules'),
1809
+ });
1810
+ case 'module-min-count':
1811
+ return translate('wizard.summary.module-min-count', {
1812
+ count: value.countValue ?? 0,
1813
+ module: findOptionLabel(moduleOptions, value.countedModuleKey) ??
1814
+ value.countedModuleKey ??
1815
+ translate('wizard.summary.fallbacks.modules'),
1816
+ });
1817
+ case 'phase-gate-completed': {
1818
+ const phaseGateLabel = findOptionLabel(phaseGateOptions, value.phaseGateSchemaId);
1819
+ return translate('wizard.summary.phase-gate-completed', {
1820
+ phaseGate: phaseGateLabel ?? translate('wizard.summary.fallbacks.phase-gate'),
1821
+ });
1822
+ }
1823
+ case 'phase-gate-module-required': {
1824
+ const moduleLabel = findOptionLabel(moduleOptions, value.requiredModuleKey);
1825
+ return translate('wizard.summary.phase-gate-module-required', {
1826
+ module: moduleLabel ??
1827
+ value.requiredModuleKey ??
1828
+ translate('wizard.summary.fallbacks.required-module'),
1829
+ });
1830
+ }
1831
+ case 'no-active-request': {
1832
+ const requestLabel = findOptionLabel(requestSchemaOptions, value.requestSchemaId);
1833
+ return translate('wizard.summary.no-active-request', {
1834
+ request: requestLabel ?? translate('wizard.summary.fallbacks.request'),
1835
+ });
1836
+ }
1837
+ case 'property-value': {
1838
+ const propertyLabel = findOptionLabel(propertyOptions, value.propertyKey) ??
1839
+ value.propertyKey ??
1840
+ translate('wizard.summary.fallbacks.property');
1841
+ return translate('wizard.summary.property-value', {
1842
+ property: propertyLabel,
1843
+ operator: resolveOperatorText(value.operator, translate),
1844
+ expectedValue: value.expectedValue || '',
1845
+ }).trim();
1846
+ }
1847
+ default:
1848
+ return translate('wizard.summary-empty');
1849
+ }
1850
+ }
1851
+ function resolveOperatorText(operator, translate) {
1852
+ const option = GOVERNANCE_OPERATOR_OPTIONS.find((item) => item.value === operator);
1853
+ return option
1854
+ ? translate(option.labelKey).toLowerCase()
1855
+ : translate('wizard.summary.fallbacks.operator');
1856
+ }
1857
+ function findOptionLabel(options, value) {
1858
+ if (!value) {
1859
+ return null;
1860
+ }
1861
+ return options.find((option) => option.value === value)?.label ?? null;
1862
+ }
1863
+
1864
+ const DEFAULT_WIZARD_FORM_STATE = {
1865
+ ...DEFAULT_GOVERNANCE_RULE_FORM_VALUE,
1866
+ phaseGateCompletedSchemaId: null,
1867
+ phaseGateOptionalSchemaId: null,
1868
+ };
1869
+ function mapWizardFormValueToState(value) {
1870
+ return {
1871
+ ...DEFAULT_WIZARD_FORM_STATE,
1872
+ ...value,
1873
+ phaseGateCompletedSchemaId: value.recipeKey === 'phase-gate-completed'
1874
+ ? value.phaseGateSchemaId
1875
+ : null,
1876
+ phaseGateOptionalSchemaId: value.recipeKey === 'phase-gate-module-required'
1877
+ ? value.phaseGateSchemaId
1878
+ : null,
1879
+ };
1880
+ }
1881
+ function normalizeWizardFormState(value) {
1882
+ return {
1883
+ ...DEFAULT_WIZARD_FORM_STATE,
1884
+ ...value,
1885
+ name: {
1886
+ ...DEFAULT_GOVERNANCE_RULE_FORM_VALUE.name,
1887
+ ...value?.name,
1888
+ },
1889
+ description: {
1890
+ ...DEFAULT_GOVERNANCE_RULE_FORM_VALUE.description,
1891
+ ...value?.description,
1892
+ },
1893
+ errorMessage: {
1894
+ ...DEFAULT_GOVERNANCE_RULE_FORM_VALUE.errorMessage,
1895
+ ...value?.errorMessage,
1896
+ },
1897
+ selectedModuleKey: value?.selectedModuleKey ?? null,
1898
+ actionKey: value?.actionKey ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.actionKey,
1899
+ recipeKey: value?.recipeKey ?? null,
1900
+ requiredModuleKey: value?.requiredModuleKey ?? null,
1901
+ blockedModuleKey: value?.blockedModuleKey ?? null,
1902
+ countedModuleKey: value?.countedModuleKey ?? null,
1903
+ countValue: value?.countValue ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.countValue,
1904
+ phaseGateCompletedSchemaId: value?.phaseGateCompletedSchemaId ?? null,
1905
+ phaseGateOptionalSchemaId: value?.phaseGateOptionalSchemaId ?? null,
1906
+ phaseGateSchemaId: value?.phaseGateSchemaId ?? null,
1907
+ requestSchemaId: value?.requestSchemaId ?? null,
1908
+ blockingStatuses: Array.isArray(value?.blockingStatuses)
1909
+ ? value.blockingStatuses
1910
+ : [...DEFAULT_GOVERNANCE_RULE_FORM_VALUE.blockingStatuses],
1911
+ propertyKey: value?.propertyKey ?? null,
1912
+ operator: value?.operator ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.operator,
1913
+ expectedValue: value?.expectedValue ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.expectedValue,
1914
+ priority: value?.priority ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.priority,
1915
+ stopOnFailure: value?.stopOnFailure ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.stopOnFailure,
1916
+ isActive: value?.isActive ?? DEFAULT_GOVERNANCE_RULE_FORM_VALUE.isActive,
1917
+ };
1918
+ }
1919
+ function mapWizardStateToWizardValue(state) {
1920
+ return {
1921
+ key: state.key,
1922
+ name: {
1923
+ en: state.name.en,
1924
+ ar: state.name.ar,
1925
+ },
1926
+ description: {
1927
+ en: state.description.en,
1928
+ ar: state.description.ar,
1929
+ },
1930
+ errorMessage: {
1931
+ en: state.errorMessage.en,
1932
+ ar: state.errorMessage.ar,
1933
+ },
1934
+ appliesTo: state.appliesTo,
1935
+ selectedModuleKey: state.selectedModuleKey,
1936
+ actionKey: state.actionKey,
1937
+ recipeKey: state.recipeKey,
1938
+ requiredModuleKey: state.requiredModuleKey,
1939
+ blockedModuleKey: state.blockedModuleKey,
1940
+ countedModuleKey: state.countedModuleKey,
1941
+ countValue: state.countValue,
1942
+ phaseGateSchemaId: state.phaseGateCompletedSchemaId ??
1943
+ state.phaseGateOptionalSchemaId ??
1944
+ null,
1945
+ requestSchemaId: state.requestSchemaId,
1946
+ blockingStatuses: [...state.blockingStatuses],
1947
+ propertyKey: state.propertyKey,
1948
+ operator: state.operator,
1949
+ expectedValue: state.expectedValue,
1950
+ priority: state.priority,
1951
+ stopOnFailure: state.stopOnFailure,
1952
+ isActive: state.isActive,
1953
+ };
1954
+ }
1955
+ function resolveRecipeDefinitionForRuleType(ruleType) {
1956
+ return resolveRecipeDefinition(resolveRecipeDefinitionKeyForRuleType(ruleType));
1957
+ }
1958
+ function resolveRecipeDefinitionKeyForRuleType(ruleType) {
1959
+ switch (readGovernanceRuleTypeKey(ruleType)) {
1960
+ case 'ModuleExists':
1961
+ return 'require-module';
1962
+ case 'ModuleNotExists':
1963
+ case 'ModuleDoesNotExist':
1964
+ return 'prevent-module';
1965
+ case 'ModuleMaxCount':
1966
+ return 'module-max-count';
1967
+ case 'ModuleMinCount':
1968
+ return 'module-min-count';
1969
+ case 'PhaseGateCompleted':
1970
+ return 'phase-gate-completed';
1971
+ case 'PhaseGateModuleRequired':
1972
+ return 'phase-gate-module-required';
1973
+ case 'NoActiveRequest':
1974
+ return 'no-active-request';
1975
+ case 'PropertyValueEquals':
1976
+ case 'FieldValue':
1977
+ return 'property-value';
1978
+ default:
1979
+ return null;
1980
+ }
1981
+ }
1982
+ function hasCompatibleRecipe(ruleTypes, appliesTo, actionKey) {
1983
+ return ruleTypes.some((ruleType) => !!resolveRecipeDefinitionForRuleType(ruleType) &&
1984
+ isRuleTypeCompatible(ruleType, appliesTo, actionKey));
1985
+ }
1986
+ function isRuleTypeCompatible(ruleType, appliesTo, actionKey) {
1987
+ return (ruleType.supportedScopes.includes(mapAppliesToToTargetScope(appliesTo)) &&
1988
+ ruleType.supportedOperations.includes(mapActionToOperationKey(actionKey)));
1989
+ }
1990
+ function areWizardStatesEqual(current, next) {
1991
+ return (current.key === next.key &&
1992
+ current.name.en === next.name.en &&
1993
+ current.name.ar === next.name.ar &&
1994
+ current.description.en === next.description.en &&
1995
+ current.description.ar === next.description.ar &&
1996
+ current.errorMessage.en === next.errorMessage.en &&
1997
+ current.errorMessage.ar === next.errorMessage.ar &&
1998
+ current.appliesTo === next.appliesTo &&
1999
+ current.selectedModuleKey === next.selectedModuleKey &&
2000
+ current.actionKey === next.actionKey &&
2001
+ current.recipeKey === next.recipeKey &&
2002
+ current.requiredModuleKey === next.requiredModuleKey &&
2003
+ current.blockedModuleKey === next.blockedModuleKey &&
2004
+ current.countedModuleKey === next.countedModuleKey &&
2005
+ current.countValue === next.countValue &&
2006
+ current.phaseGateCompletedSchemaId === next.phaseGateCompletedSchemaId &&
2007
+ current.phaseGateOptionalSchemaId === next.phaseGateOptionalSchemaId &&
2008
+ current.phaseGateSchemaId === next.phaseGateSchemaId &&
2009
+ current.requestSchemaId === next.requestSchemaId &&
2010
+ current.propertyKey === next.propertyKey &&
2011
+ current.operator === next.operator &&
2012
+ current.expectedValue === next.expectedValue &&
2013
+ current.priority === next.priority &&
2014
+ current.stopOnFailure === next.stopOnFailure &&
2015
+ current.isActive === next.isActive &&
2016
+ areStringArraysEqual(current.blockingStatuses, next.blockingStatuses));
2017
+ }
2018
+ function areLegacyFormValuesEqual(current, next) {
2019
+ if (current === next) {
2020
+ return true;
2021
+ }
2022
+ if (!current || !next) {
2023
+ return current === next;
2024
+ }
2025
+ if (current.key !== next.key ||
2026
+ current.priority !== next.priority ||
2027
+ current.name.en !== next.name.en ||
2028
+ current.name.ar !== next.name.ar ||
2029
+ current.description.en !== next.description.en ||
2030
+ current.description.ar !== next.description.ar ||
2031
+ current.errorMessage.en !== next.errorMessage.en ||
2032
+ current.errorMessage.ar !== next.errorMessage.ar ||
2033
+ current.ruleType !== next.ruleType ||
2034
+ current.targetScope !== next.targetScope ||
2035
+ current.targetModuleKey !== next.targetModuleKey ||
2036
+ current.targetOperationKey !== next.targetOperationKey ||
2037
+ current.stopOnFailure !== next.stopOnFailure ||
2038
+ current.isActive !== next.isActive) {
2039
+ return false;
2040
+ }
2041
+ const currentKeys = Object.keys(current);
2042
+ const nextKeys = Object.keys(next);
2043
+ if (currentKeys.length !== nextKeys.length) {
2044
+ return false;
2045
+ }
2046
+ return currentKeys.every((key) => {
2047
+ if (key === 'key' ||
2048
+ key === 'priority' ||
2049
+ key === 'name' ||
2050
+ key === 'description' ||
2051
+ key === 'errorMessage' ||
2052
+ key === 'ruleType' ||
2053
+ key === 'targetScope' ||
2054
+ key === 'targetModuleKey' ||
2055
+ key === 'targetOperationKey' ||
2056
+ key === 'stopOnFailure' ||
2057
+ key === 'isActive') {
2058
+ return true;
2059
+ }
2060
+ const currentValue = current[key];
2061
+ const nextValue = next[key];
2062
+ if (Array.isArray(currentValue) || Array.isArray(nextValue)) {
2063
+ return (Array.isArray(currentValue) &&
2064
+ Array.isArray(nextValue) &&
2065
+ areUnknownArraysEqual(currentValue, nextValue));
2066
+ }
2067
+ return currentValue === nextValue;
2068
+ });
2069
+ }
2070
+ function areStringArraysEqual(current, next) {
2071
+ if (current.length !== next.length) {
2072
+ return false;
2073
+ }
2074
+ return current.every((value, index) => value === next[index]);
2075
+ }
2076
+ function areUnknownArraysEqual(current, next) {
2077
+ if (current.length !== next.length) {
2078
+ return false;
2079
+ }
2080
+ return current.every((value, index) => value === next[index]);
2081
+ }
2082
+
597
2083
  class GovernanceRuleForm {
598
- // ============================================================================
599
- // Injected Services
600
- // ============================================================================
601
2084
  modal = inject(ModalService);
602
2085
  ref = inject(ModalRef);
603
2086
  translocoService = inject(TranslocoService);
604
2087
  facade = inject(GovernanceFacade);
605
- // ============================================================================
606
- // Input - rule to edit (null for create)
607
- // ============================================================================
2088
+ toast = inject(ToastService);
608
2089
  ruleForEdit = input(null, ...(ngDevMode ? [{ debugName: "ruleForEdit" }] : []));
609
- // ============================================================================
610
- // Facade Data
611
- // ============================================================================
2090
+ dynamicFormRef = viewChild(DynamicForm, ...(ngDevMode ? [{ debugName: "dynamicFormRef" }] : []));
612
2091
  selectedRule = this.facade.selectedRule;
613
2092
  ruleTypes = this.facade.ruleTypes;
614
2093
  isLoadingRule = this.facade.isLoadingRule;
2094
+ isLoadingRuleTypes = this.facade.isLoadingRuleTypes;
615
2095
  isAddingRule = this.facade.isAddingRule;
616
2096
  isUpdatingRule = this.facade.isUpdatingRule;
617
- // For optionsSource: 'auto-populated' (modules from API)
618
2097
  modules = this.facade.modules;
619
- // For optionsSource: 'requestSchema' and 'properties'
620
2098
  requestSchemas = this.facade.requestSchemas;
621
2099
  properties = this.facade.properties;
622
- isLoadingRequestSchemas = this.facade.isLoadingRequestSchemas;
623
- isLoadingProperties = this.facade.isLoadingProperties;
624
- // ============================================================================
625
- // Form Control
626
- // ============================================================================
627
- ruleFormControl = new FormControl();
628
- formValue = toSignal(this.ruleFormControl.valueChanges);
629
- // ============================================================================
630
- // Selected Rule Type (from form value)
631
- // ============================================================================
632
- selectedRuleType = computed(() => {
633
- const formValue = this.formValue();
634
- const ruleTypeKey = formValue?.ruleType;
635
- if (!ruleTypeKey)
636
- return null;
637
- return this.ruleTypes().find((rt) => rt.type === ruleTypeKey) ?? null;
638
- }, ...(ngDevMode ? [{ debugName: "selectedRuleType" }] : []));
639
- // ============================================================================
640
- // Target Scope and Module Key (from form value)
641
- // ============================================================================
642
- targetScope = computed(() => this.formValue()?.targetScope ?? 'Level', ...(ngDevMode ? [{ debugName: "targetScope" }] : []));
643
- targetModuleKey = computed(() => this.formValue()?.targetModuleKey ?? null, ...(ngDevMode ? [{ debugName: "targetModuleKey" }] : []));
644
- // ============================================================================
645
- // Options from state for optionsSource: 'requestSchema'
646
- // ============================================================================
2100
+ wizardFormControl = new FormControl(DEFAULT_WIZARD_FORM_STATE, { nonNullable: true });
2101
+ legacyFormControl = new FormControl(null);
2102
+ wizardStateRaw = toSignal(this.wizardFormControl.valueChanges.pipe(map((value) => value ?? DEFAULT_WIZARD_FORM_STATE)), { initialValue: this.wizardFormControl.value });
2103
+ legacyFormValue = toSignal(this.legacyFormControl.valueChanges.pipe(map(() => this.legacyFormControl.value)), { initialValue: this.legacyFormControl.value });
2104
+ activeLang = toSignal(this.translocoService.langChanges$, {
2105
+ initialValue: this.translocoService.getActiveLang(),
2106
+ });
2107
+ langCode = computed(() => this.activeLang() === 'ar' ? 'ar' : 'en', ...(ngDevMode ? [{ debugName: "langCode" }] : []));
2108
+ isEditMode = computed(() => !!this.ruleForEdit()?.id, ...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
2109
+ wizardState = computed(() => normalizeWizardFormState(this.wizardStateRaw()), ...(ngDevMode ? [{ debugName: "wizardState" }] : []));
2110
+ wizardValue = computed(() => mapWizardStateToWizardValue(this.wizardState()), ...(ngDevMode ? [{ debugName: "wizardValue" }] : []));
2111
+ moduleOptions = computed(() => {
2112
+ const lang = this.langCode();
2113
+ return this.modules().map((module) => ({
2114
+ label: module.name[lang] || module.name.en,
2115
+ value: module.key,
2116
+ }));
2117
+ }, ...(ngDevMode ? [{ debugName: "moduleOptions" }] : []));
647
2118
  requestSchemaOptions = computed(() => {
648
- const lang = this.translocoService.getActiveLang();
2119
+ const lang = this.langCode();
649
2120
  return this.requestSchemas().map((schema) => ({
650
2121
  label: schema.name[lang] || schema.name.en,
651
- value: schema.id.toString(),
2122
+ value: String(schema.id),
652
2123
  }));
653
2124
  }, ...(ngDevMode ? [{ debugName: "requestSchemaOptions" }] : []));
654
- // ============================================================================
655
- // Options from state for optionsSource: 'properties'
656
- // ============================================================================
657
2125
  propertyOptions = computed(() => {
658
- const lang = this.translocoService.getActiveLang();
659
- return this.properties().map((prop) => ({
660
- label: prop.name[lang] || prop.name.en,
661
- value: prop.key,
2126
+ const lang = this.langCode();
2127
+ return this.properties().map((property) => ({
2128
+ label: property.name[lang] || property.name.en,
2129
+ value: property.key,
662
2130
  }));
663
2131
  }, ...(ngDevMode ? [{ debugName: "propertyOptions" }] : []));
664
- // ============================================================================
665
- // Module Options for optionsSource: 'auto-populated' (moduleKey, requiredModuleKey)
666
- // ============================================================================
667
- moduleOptions = computed(() => {
668
- const lang = this.translocoService.getActiveLang();
669
- return this.modules().map((mod) => ({
670
- label: mod.name[lang] || mod.name.en,
671
- value: mod.key,
672
- }));
673
- }, ...(ngDevMode ? [{ debugName: "moduleOptions" }] : []));
674
- // ============================================================================
675
- // Scope Options based on selected rule type
676
- // ============================================================================
2132
+ appliesToCardOptions = computed(() => GOVERNANCE_APPLIES_TO_OPTIONS.map((option) => ({
2133
+ id: option.value,
2134
+ name: this.translate(option.labelKey),
2135
+ })), ...(ngDevMode ? [{ debugName: "appliesToCardOptions" }] : []));
2136
+ availableActionOptions = computed(() => {
2137
+ return GOVERNANCE_ACTION_OPTIONS.filter((option) => hasCompatibleRecipe(this.ruleTypes(), this.wizardValue().appliesTo, option.value));
2138
+ }, ...(ngDevMode ? [{ debugName: "availableActionOptions" }] : []));
2139
+ actionCardOptions = computed(() => this.availableActionOptions().map((option) => ({
2140
+ id: option.value,
2141
+ name: buildActionPrompt(this.wizardValue().appliesTo, option.value, this.translate.bind(this)),
2142
+ })), ...(ngDevMode ? [{ debugName: "actionCardOptions" }] : []));
2143
+ availableRecipeDefinitions = computed(() => {
2144
+ const actionKey = this.wizardValue().actionKey;
2145
+ if (!actionKey) {
2146
+ return [];
2147
+ }
2148
+ return this.ruleTypes()
2149
+ .map((ruleType) => {
2150
+ const recipe = resolveRecipeDefinitionForRuleType(ruleType);
2151
+ if (!recipe) {
2152
+ return null;
2153
+ }
2154
+ return isRuleTypeCompatible(ruleType, this.wizardValue().appliesTo, actionKey)
2155
+ ? recipe
2156
+ : null;
2157
+ })
2158
+ .filter((recipe, index, items) => !!recipe &&
2159
+ items.findIndex((item) => item?.key === recipe.key) === index);
2160
+ }, ...(ngDevMode ? [{ debugName: "availableRecipeDefinitions" }] : []));
2161
+ recipeCardOptions = computed(() => this.availableRecipeDefinitions().map((recipe) => ({
2162
+ id: recipe.key,
2163
+ name: this.translate(recipe.labelKey),
2164
+ })), ...(ngDevMode ? [{ debugName: "recipeCardOptions" }] : []));
2165
+ selectedRuleType = computed(() => findRuleTypeByRecipe(this.ruleTypes(), this.wizardValue().recipeKey), ...(ngDevMode ? [{ debugName: "selectedRuleType" }] : []));
2166
+ useLegacyEditor = computed(() => {
2167
+ if (!this.ruleForEdit()?.id) {
2168
+ return false;
2169
+ }
2170
+ const rule = this.selectedRule();
2171
+ return !!rule && !mapRuleToWizardFormValue(rule);
2172
+ }, ...(ngDevMode ? [{ debugName: "useLegacyEditor" }] : []));
2173
+ isInitializing = computed(() => {
2174
+ if (this.ruleForEdit()?.id && this.isLoadingRule()) {
2175
+ return true;
2176
+ }
2177
+ return this.ruleTypes().length === 0 && this.isLoadingRuleTypes();
2178
+ }, ...(ngDevMode ? [{ debugName: "isInitializing" }] : []));
2179
+ noRecipesAvailable = computed(() => !this.useLegacyEditor() &&
2180
+ !!this.wizardValue().actionKey &&
2181
+ this.recipeCardOptions().length === 0, ...(ngDevMode ? [{ debugName: "noRecipesAvailable" }] : []));
2182
+ hasReviewSummary = computed(() => {
2183
+ if (this.useLegacyEditor()) {
2184
+ return false;
2185
+ }
2186
+ const value = this.wizardValue();
2187
+ return !!value.actionKey && !!value.recipeKey;
2188
+ }, ...(ngDevMode ? [{ debugName: "hasReviewSummary" }] : []));
2189
+ canSubmit = computed(() => {
2190
+ if (this.useLegacyEditor()) {
2191
+ return this.legacyFormControl.valid;
2192
+ }
2193
+ return (this.wizardFormControl.valid &&
2194
+ !!this.selectedRuleType() &&
2195
+ !!this.wizardValue().actionKey &&
2196
+ !!this.wizardValue().recipeKey);
2197
+ }, ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
2198
+ reviewSummary = computed(() => buildSummary({
2199
+ value: this.wizardValue(),
2200
+ moduleOptions: this.moduleOptions(),
2201
+ requestSchemaOptions: this.requestSchemaOptions(),
2202
+ propertyOptions: this.propertyOptions(),
2203
+ phaseGateOptions: this.getPhaseGateOptions(),
2204
+ translate: this.translate.bind(this),
2205
+ }), ...(ngDevMode ? [{ debugName: "reviewSummary" }] : []));
2206
+ wizardFormConfig = computed(() => buildWizardFormConfig({
2207
+ translate: this.translate.bind(this),
2208
+ isEdit: this.isEditMode(),
2209
+ recipeKey: this.wizardValue().recipeKey,
2210
+ appliesToOptions: this.appliesToCardOptions(),
2211
+ actionOptions: this.actionCardOptions(),
2212
+ recipeOptions: this.recipeCardOptions(),
2213
+ moduleOptions: this.moduleOptions(),
2214
+ requestSchemaOptions: this.requestSchemaOptions(),
2215
+ propertyOptions: this.propertyOptions(),
2216
+ phaseGateOptions: this.getPhaseGateOptions(),
2217
+ blockingStatuses: this.getBlockingStatusOptions(),
2218
+ }), ...(ngDevMode ? [{ debugName: "wizardFormConfig" }] : []));
2219
+ selectedLegacyRuleType = computed(() => {
2220
+ const ruleTypeKey = String(this.legacyFormValue()?.ruleType ?? '').trim();
2221
+ if (!ruleTypeKey) {
2222
+ return null;
2223
+ }
2224
+ return (this.ruleTypes().find((ruleType) => {
2225
+ const catalogKey = readGovernanceRuleTypeKey(ruleType);
2226
+ return (catalogKey === ruleTypeKey || String(ruleType.type) === ruleTypeKey);
2227
+ }) ?? null);
2228
+ }, ...(ngDevMode ? [{ debugName: "selectedLegacyRuleType" }] : []));
677
2229
  scopeOptions = computed(() => {
678
- const ruleType = this.selectedRuleType();
2230
+ const ruleType = this.selectedLegacyRuleType();
679
2231
  if (!ruleType) {
680
2232
  return [
681
- { label: 'Level', value: 'Level' },
682
- { label: 'Module', value: 'Module' },
683
- { label: 'PhaseGate', value: 'PhaseGate' },
2233
+ { label: this.translateScopeLabel('Level'), value: 'Level' },
2234
+ { label: this.translateScopeLabel('Module'), value: 'Module' },
2235
+ { label: this.translateScopeLabel('PhaseGate'), value: 'PhaseGate' },
684
2236
  ];
685
2237
  }
686
2238
  return ruleType.supportedScopes.map((scope) => ({
687
- label: scope,
2239
+ label: this.translateScopeLabel(scope),
688
2240
  value: scope,
689
2241
  }));
690
2242
  }, ...(ngDevMode ? [{ debugName: "scopeOptions" }] : []));
691
- // ============================================================================
692
- // Operation Options based on selected rule type
693
- // ============================================================================
694
2243
  operationOptions = computed(() => {
695
- const ruleType = this.selectedRuleType();
2244
+ const ruleType = this.selectedLegacyRuleType();
696
2245
  if (!ruleType) {
697
2246
  return [
698
- { label: 'All', value: '' },
699
- { label: 'Create', value: 'Create' },
700
- { label: 'Update', value: 'Update' },
701
- { label: 'Delete', value: 'Delete' },
2247
+ { label: this.translate('all'), value: '' },
2248
+ { label: this.translateOperationLabel('Create'), value: 'Create' },
2249
+ { label: this.translateOperationLabel('Update'), value: 'Update' },
2250
+ { label: this.translateOperationLabel('Delete'), value: 'Delete' },
702
2251
  ];
703
2252
  }
704
2253
  return [
705
- { label: 'All', value: '' },
706
- ...ruleType.supportedOperations.map((op) => ({
707
- label: op,
708
- value: op,
2254
+ { label: this.translate('all'), value: '' },
2255
+ ...ruleType.supportedOperations.map((operation) => ({
2256
+ label: this.translateOperationLabel(operation),
2257
+ value: operation,
709
2258
  })),
710
2259
  ];
711
2260
  }, ...(ngDevMode ? [{ debugName: "operationOptions" }] : []));
712
- // ============================================================================
713
- // Rule Type Options
714
- // ============================================================================
715
- ruleTypeOptions = computed(() => {
716
- const lang = this.translocoService.getActiveLang();
717
- return this.ruleTypes().map((rt) => ({
718
- label: rt.name[lang] || rt.name.en,
719
- value: rt.type,
720
- }));
721
- }, ...(ngDevMode ? [{ debugName: "ruleTypeOptions" }] : []));
722
- // ============================================================================
723
- // Dynamic Form Configuration
724
- // ============================================================================
725
- formConfig = computed(() => {
726
- const t = (key) => this.translocoService.translate(`governance.${key}`);
727
- const ruleType = this.selectedRuleType();
728
- const isEdit = !!this.ruleForEdit()?.id;
729
- return {
730
- sections: [
731
- // Section 1: Basic Information
732
- {
733
- key: 'basicInfo',
734
- type: 'header',
735
- label: t('basic-information'),
736
- order: 1,
737
- fields: [
738
- new TextFieldConfig({
739
- key: 'key',
740
- label: t('rule-key'),
741
- placeholder: 'e.g., charter-required',
742
- validators: [
743
- ValidatorConfig.required(),
744
- ValidatorConfig.pattern('^[a-z0-9-]+$', 'Only lowercase letters, numbers and hyphens allowed'),
745
- ],
746
- order: 1,
747
- colSpan: 6,
748
- disabled: isEdit,
749
- }),
750
- new NumberFieldConfig({
751
- key: 'priority',
752
- label: t('priority'),
753
- placeholder: '10',
754
- validators: [ValidatorConfig.required()],
755
- min: 0,
756
- max: 1000,
757
- order: 2,
758
- colSpan: 6,
759
- }),
760
- new TextFieldConfig({
761
- key: 'name_en',
762
- label: t('name-en'),
763
- placeholder: t('name-en-placeholder'),
764
- validators: [ValidatorConfig.required()],
765
- order: 3,
766
- colSpan: 6,
767
- }),
768
- new TextFieldConfig({
769
- key: 'name_ar',
770
- label: t('name-ar'),
771
- placeholder: t('name-ar-placeholder'),
772
- validators: [ValidatorConfig.required()],
773
- order: 4,
774
- colSpan: 6,
775
- }),
776
- new TextareaFieldConfig({
777
- key: 'description_en',
778
- label: t('description-en'),
779
- placeholder: t('description-en-placeholder'),
780
- order: 5,
781
- colSpan: 6,
782
- }),
783
- new TextareaFieldConfig({
784
- key: 'description_ar',
785
- label: t('description-ar'),
786
- placeholder: t('description-ar-placeholder'),
787
- order: 6,
788
- colSpan: 6,
789
- }),
790
- new TextareaFieldConfig({
791
- key: 'errorMessage_en',
792
- label: t('error-message-en'),
793
- placeholder: t('error-message-en-placeholder'),
794
- validators: [ValidatorConfig.required()],
795
- order: 7,
796
- colSpan: 6,
797
- }),
798
- new TextareaFieldConfig({
799
- key: 'errorMessage_ar',
800
- label: t('error-message-ar'),
801
- placeholder: t('error-message-ar-placeholder'),
802
- validators: [ValidatorConfig.required()],
803
- order: 8,
804
- colSpan: 6,
805
- }),
806
- ],
807
- },
808
- // Section 2: Rule Target
809
- {
810
- key: 'ruleTarget',
811
- type: 'header',
812
- label: t('rule-target'),
813
- order: 2,
814
- fields: [
815
- new SelectFieldConfig({
816
- key: 'ruleType',
817
- label: t('rule-type'),
818
- options: this.ruleTypeOptions(),
819
- optionLabel: 'label',
820
- optionValue: 'value',
821
- validators: [ValidatorConfig.required()],
822
- order: 1,
823
- colSpan: 6,
824
- disabled: isEdit,
825
- }),
826
- new SelectFieldConfig({
827
- key: 'targetScope',
828
- label: t('target-scope'),
829
- options: this.scopeOptions(),
830
- optionLabel: 'label',
831
- optionValue: 'value',
832
- validators: [ValidatorConfig.required()],
833
- order: 2,
834
- colSpan: 6,
835
- }),
836
- new SelectFieldConfig({
837
- key: 'targetModuleKey',
838
- label: t('target-module'),
839
- options: this.moduleOptions(),
840
- optionLabel: 'label',
841
- optionValue: 'value',
842
- order: 3,
843
- colSpan: 6,
844
- relations: [
845
- {
846
- key: 'targetScope',
847
- value: 'Module',
848
- action: 'show',
849
- },
850
- ],
851
- }),
852
- new SelectFieldConfig({
853
- key: 'targetOperationKey',
854
- label: t('target-operation'),
855
- options: this.operationOptions(),
856
- optionLabel: 'label',
857
- optionValue: 'value',
858
- order: 4,
859
- colSpan: 6,
860
- }),
861
- ],
862
- },
863
- // Section 3: Rule-Specific Configuration (dynamic)
864
- ...(ruleType ? [this.buildConfigurationSection(ruleType)] : []),
865
- // Section 4: Execution Settings
866
- {
867
- key: 'executionSettings',
868
- type: 'header',
869
- label: t('execution-settings'),
870
- order: 4,
871
- fields: [
872
- new ToggleFieldConfig({
873
- key: 'stopOnFailure',
874
- label: t('stop-on-failure'),
875
- order: 1,
876
- colSpan: 6,
877
- }),
878
- new ToggleFieldConfig({
879
- key: 'isActive',
880
- label: t('active-rule'),
881
- order: 2,
882
- colSpan: 6,
883
- }),
884
- ],
885
- },
886
- ],
887
- };
888
- }, ...(ngDevMode ? [{ debugName: "formConfig" }] : []));
889
- // ============================================================================
890
- // Constructor with Effect for Form Population
891
- // ============================================================================
2261
+ legacyFormConfig = computed(() => buildLegacyFormConfig({
2262
+ translate: this.translate.bind(this),
2263
+ isEdit: this.isEditMode(),
2264
+ langCode: this.langCode(),
2265
+ selectedRuleType: this.selectedLegacyRuleType(),
2266
+ ruleTypeOptions: this.ruleTypeOptions(),
2267
+ scopeOptions: this.scopeOptions(),
2268
+ operationOptions: this.operationOptions(),
2269
+ moduleOptions: this.moduleOptions(),
2270
+ requestSchemaOptions: this.requestSchemaOptions(),
2271
+ propertyOptions: this.propertyOptions(),
2272
+ }), ...(ngDevMode ? [{ debugName: "legacyFormConfig" }] : []));
892
2273
  constructor() {
893
- // Effect to populate form when editing existing rule
894
2274
  effect(() => {
895
- const ruleId = this.ruleForEdit()?.id;
896
- if (this.selectedRule() && ruleId) {
897
- const rule = this.selectedRule();
898
- if (!rule)
899
- return;
900
- // Unpack rule data to form format
901
- const formData = this.unpackRuleToForm(rule);
902
- this.ruleFormControl.patchValue(formData);
903
- }
904
- else {
905
- // Default values for new rule
906
- this.ruleFormControl.reset({
907
- priority: 10,
908
- stopOnFailure: true,
909
- isActive: true,
910
- targetScope: 'Module',
911
- });
2275
+ if (this.wizardValue().appliesTo !== 'specific-module' &&
2276
+ this.wizardValue().selectedModuleKey) {
2277
+ this.patchWizardState({ selectedModuleKey: null });
912
2278
  }
913
2279
  });
914
- // Effect to apply default values when rule type changes (for new rules only)
915
2280
  effect(() => {
916
- const ruleType = this.selectedRuleType();
917
- const ruleId = this.ruleForEdit()?.id;
918
- // Only apply defaults for new rules (not editing)
919
- if (ruleType && !ruleId) {
920
- const defaults = this.getDefaultsFromSchema(ruleType);
921
- if (Object.keys(defaults).length > 0) {
922
- const currentValue = this.ruleFormControl.value ?? {};
923
- this.ruleFormControl.patchValue({ ...currentValue, ...defaults });
924
- }
2281
+ const currentAction = this.wizardValue().actionKey;
2282
+ if (!currentAction ||
2283
+ this.availableActionOptions().some((option) => option.value === currentAction)) {
2284
+ return;
925
2285
  }
2286
+ this.patchWizardState({ actionKey: null, recipeKey: null });
926
2287
  });
927
- // Effect to fetch requestSchemas and properties when scope/module changes
928
- // This runs when configurationSchema has fields with optionsSource = 'requestSchema' or 'properties'
929
2288
  effect(() => {
930
- const ruleType = this.selectedRuleType();
931
- const scope = this.targetScope();
932
- const moduleKey = this.targetModuleKey();
933
- if (!ruleType)
2289
+ const currentRecipe = this.wizardValue().recipeKey;
2290
+ if (!currentRecipe ||
2291
+ this.availableRecipeDefinitions().some((definition) => definition.key === currentRecipe)) {
934
2292
  return;
935
- // Check if any field needs requestSchema options
936
- const needsRequestSchemas = ruleType.configurationSchema.some((f) => f.optionsSource === 'requestSchema');
937
- // Check if any field needs properties options
938
- const needsProperties = ruleType.configurationSchema.some((f) => f.optionsSource === 'properties');
939
- // For Module scope, we need a target module key
940
- if (scope === 'Module' && !moduleKey) {
941
- // Clear options when module is not selected
942
- if (needsRequestSchemas) {
943
- this.facade.clearRequestSchemas();
2293
+ }
2294
+ this.patchWizardState({ recipeKey: null });
2295
+ });
2296
+ effect(() => {
2297
+ const editing = !!this.ruleForEdit()?.id;
2298
+ if (!editing) {
2299
+ this.resetWizardForm();
2300
+ if (this.legacyFormControl.value !== null) {
2301
+ this.legacyFormControl.reset(null);
944
2302
  }
945
- if (needsProperties) {
946
- this.facade.clearProperties();
2303
+ return;
2304
+ }
2305
+ const rule = this.selectedRule();
2306
+ if (!rule) {
2307
+ return;
2308
+ }
2309
+ const wizardValue = mapRuleToWizardFormValue(rule);
2310
+ if (wizardValue) {
2311
+ this.setWizardState(mapWizardFormValueToState(wizardValue));
2312
+ if (this.legacyFormControl.value !== null) {
2313
+ this.legacyFormControl.reset(null);
947
2314
  }
948
2315
  return;
949
2316
  }
950
- // Fetch options based on scope
2317
+ this.resetWizardForm();
2318
+ const nextLegacyValue = mapRuleToLegacyFormValue(rule);
2319
+ if (!areLegacyFormValuesEqual(this.legacyFormControl.value, nextLegacyValue)) {
2320
+ this.legacyFormControl.setValue(nextLegacyValue);
2321
+ }
2322
+ });
2323
+ effect(() => {
2324
+ const recipeKey = this.wizardValue().recipeKey;
2325
+ const appliesTo = this.wizardValue().appliesTo;
2326
+ const targetModuleKey = appliesTo === 'specific-module'
2327
+ ? (this.wizardValue().selectedModuleKey ?? undefined)
2328
+ : undefined;
2329
+ const targetScope = appliesTo === 'level'
2330
+ ? 'Level'
2331
+ : appliesTo === 'phase-gate'
2332
+ ? 'PhaseGate'
2333
+ : 'Module';
2334
+ const needsRequestSchemas = recipeKey === 'no-active-request';
2335
+ const needsProperties = recipeKey === 'property-value';
2336
+ if (!needsRequestSchemas && !needsProperties) {
2337
+ this.clearReferenceData();
2338
+ return;
2339
+ }
2340
+ if (targetScope === 'Module' &&
2341
+ appliesTo === 'specific-module' &&
2342
+ !targetModuleKey) {
2343
+ this.clearReferenceData();
2344
+ return;
2345
+ }
951
2346
  if (needsRequestSchemas) {
952
- this.facade.getRequestSchemas(scope, moduleKey ?? undefined);
2347
+ this.facade.getRequestSchemas(targetScope, targetModuleKey);
953
2348
  }
954
2349
  if (needsProperties) {
955
- this.facade.getProperties(scope, moduleKey ?? undefined);
2350
+ this.facade.getProperties(targetScope, targetModuleKey);
956
2351
  }
957
2352
  });
958
2353
  }
959
- /**
960
- * Extract default values from rule type's configuration schema
961
- */
962
- getDefaultsFromSchema(ruleType) {
963
- const defaults = {};
964
- for (const field of ruleType.configurationSchema) {
965
- if (field.defaultValue !== undefined) {
966
- defaults[`config_${field.key}`] = field.defaultValue;
967
- }
968
- }
969
- return defaults;
970
- }
971
2354
  ngOnInit() {
2355
+ if (!this.ruleTypes().length) {
2356
+ this.facade.getRuleTypes();
2357
+ }
2358
+ if (!this.modules().length) {
2359
+ this.facade.getModules();
2360
+ }
972
2361
  const ruleId = this.ruleForEdit()?.id;
973
2362
  if (ruleId) {
974
2363
  this.facade.getRule(ruleId);
975
2364
  }
976
2365
  }
977
- // ============================================================================
978
- // Submit Handler
979
- // ============================================================================
980
2366
  onSubmit() {
981
- if (!this.ruleFormControl.valid) {
2367
+ this.dynamicFormRef()?.form.markAllAsTouched();
2368
+ if (this.useLegacyEditor()) {
2369
+ if (!this.legacyFormControl.valid) {
2370
+ this.toast.warn(this.translate('wizard.complete-required-fields'), this.translate('warning'));
2371
+ return;
2372
+ }
2373
+ this.submitLegacyForm();
2374
+ return;
2375
+ }
2376
+ if (!this.wizardValue().actionKey || !this.wizardValue().recipeKey) {
2377
+ this.toast.warn(this.translate('wizard.summary-empty'), this.translate('warning'));
2378
+ return;
2379
+ }
2380
+ if (!this.canSubmit()) {
2381
+ this.toast.warn(this.translate('wizard.complete-required-fields'), this.translate('warning'));
2382
+ return;
2383
+ }
2384
+ const ruleType = this.selectedRuleType();
2385
+ if (!ruleType) {
982
2386
  return;
983
2387
  }
984
- const formValue = this.ruleFormControl.value;
985
2388
  const ruleId = this.ruleForEdit()?.id;
2389
+ const payloadValue = this.wizardValue();
986
2390
  if (ruleId) {
987
- // Update
988
- const payload = this.packFormToUpdateDto(formValue);
989
- this.facade.updateRule(ruleId, payload).subscribe({
990
- next: () => {
991
- this.ref.close(true);
992
- },
993
- error: () => { },
994
- });
995
- }
996
- else {
997
- // Create
998
- const payload = this.packFormToCreateDto(formValue);
999
- this.facade.addRule(payload).subscribe({
1000
- next: () => {
1001
- this.ref.close(true);
1002
- },
1003
- error: () => { },
1004
- });
2391
+ this.saveUpdate(ruleId, buildUpdatePayload(payloadValue, ruleType));
2392
+ return;
1005
2393
  }
2394
+ this.saveCreate(buildCreatePayload(payloadValue, ruleType));
1006
2395
  }
1007
- // ============================================================================
1008
- // Build Configuration Section Based on Rule Type
1009
- // ============================================================================
1010
- buildConfigurationSection(ruleType) {
1011
- const t = (key) => this.translocoService.translate(`governance.${key}`);
1012
- return {
1013
- key: 'configuration',
1014
- type: 'header',
1015
- label: t('rule-configuration'),
1016
- order: 3,
1017
- fields: this.buildConfigFieldsForType(ruleType),
1018
- };
2396
+ getPhaseGateOptions() {
2397
+ return (findConfigurationSchemaField(this.selectedRuleType(), 'phaseGateSchemaId')
2398
+ ?.options ?? []);
1019
2399
  }
1020
- buildConfigFieldsForType(ruleType) {
1021
- const lang = this.translocoService.getActiveLang();
1022
- // Build fields dynamically from configurationSchema array
1023
- return ruleType.configurationSchema.map((field, index) => this.schemaFieldToFormField(field, index + 1, lang));
1024
- }
1025
- /**
1026
- * Convert a ConfigurationFieldSchema to DynamicForm BaseFieldConfig
1027
- */
1028
- schemaFieldToFormField(field, order, lang) {
1029
- const label = field.label[lang] || field.label.en;
1030
- const colSpan = field.colSpan ?? 6;
1031
- const validators = field.required ? [ValidatorConfig.required()] : [];
1032
- switch (field.type) {
1033
- case 'text':
1034
- return new TextFieldConfig({
1035
- key: `config_${field.key}`,
1036
- label,
1037
- validators,
1038
- order,
1039
- colSpan,
1040
- });
1041
- case 'textarea':
1042
- return new TextareaFieldConfig({
1043
- key: `config_${field.key}`,
1044
- label,
1045
- validators,
1046
- order,
1047
- colSpan,
1048
- });
1049
- case 'number':
1050
- return new NumberFieldConfig({
1051
- key: `config_${field.key}`,
1052
- label,
1053
- validators,
1054
- min: field.min,
1055
- max: field.max,
1056
- order,
1057
- colSpan,
1058
- });
1059
- case 'select':
1060
- return new SelectFieldConfig({
1061
- key: `config_${field.key}`,
1062
- label,
1063
- options: this.getOptionsForField(field),
1064
- optionLabel: 'label',
1065
- optionValue: 'value',
1066
- validators,
1067
- order,
1068
- colSpan,
1069
- });
1070
- case 'multi-select':
1071
- return new MultiSelectFieldConfig({
1072
- key: `config_${field.key}`,
1073
- label,
1074
- options: this.getOptionsForField(field),
1075
- optionLabel: 'label',
1076
- optionValue: 'value',
1077
- validators,
1078
- order,
1079
- colSpan,
1080
- });
1081
- case 'checkbox':
1082
- return new ToggleFieldConfig({
1083
- key: `config_${field.key}`,
1084
- label,
1085
- order,
1086
- colSpan,
1087
- });
1088
- case 'date':
1089
- return new DateFieldConfig({
1090
- key: `config_${field.key}`,
1091
- label,
1092
- validators,
1093
- order,
1094
- colSpan,
1095
- });
2400
+ getBlockingStatusOptions() {
2401
+ const field = findConfigurationSchemaField(this.selectedRuleType(), 'blockingStatuses');
2402
+ if (field?.options?.length) {
2403
+ return field.options.map((option) => option.value);
2404
+ }
2405
+ return [...DEFAULT_BLOCKING_STATUSES];
2406
+ }
2407
+ translate(key, params) {
2408
+ const lang = this.activeLang();
2409
+ return this.translocoService.translate(`governance.${key}`, params, lang);
2410
+ }
2411
+ translateScopeLabel(scope) {
2412
+ switch (scope) {
2413
+ case 'Level':
2414
+ return this.translate('scope-level');
2415
+ case 'Module':
2416
+ return this.translate('scope-module');
2417
+ case 'PhaseGate':
2418
+ return this.translate('scope-phase-gate');
1096
2419
  default:
1097
- // Fallback to text field for unknown types
1098
- return new TextFieldConfig({
1099
- key: `config_${field.key}`,
1100
- label,
1101
- validators,
1102
- order,
1103
- colSpan,
1104
- });
2420
+ return scope;
1105
2421
  }
1106
2422
  }
1107
- /**
1108
- * Get options for a field based on its optionsSource
1109
- * - 'static' or 'auto-populated': Use options from schema (already in API response)
1110
- * - 'requestSchema': Use fetched request schemas from state
1111
- * - 'properties': Use fetched properties from state
1112
- */
1113
- getOptionsForField(field) {
1114
- switch (field.optionsSource) {
1115
- case 'requestSchema':
1116
- return this.requestSchemaOptions();
1117
- case 'properties':
1118
- return this.propertyOptions();
1119
- case 'auto-populated':
1120
- case 'static':
2423
+ translateOperationLabel(operation) {
2424
+ switch (operation) {
2425
+ case 'Create':
2426
+ return this.translate('create');
2427
+ case 'Update':
2428
+ return this.translate('update');
2429
+ case 'Delete':
2430
+ return this.translate('delete');
1121
2431
  default:
1122
- // Use options directly from schema
1123
- return field.options ?? [];
2432
+ return operation;
1124
2433
  }
1125
2434
  }
1126
- // ============================================================================
1127
- // Data Transformation Helpers
1128
- // ============================================================================
1129
- unpackRuleToForm(rule) {
1130
- const formData = {
1131
- key: rule.key,
1132
- priority: rule.priority,
1133
- name_en: rule.name?.en ?? '',
1134
- name_ar: rule.name?.ar ?? '',
1135
- description_en: rule.description?.en ?? '',
1136
- description_ar: rule.description?.ar ?? '',
1137
- errorMessage_en: rule.errorMessage?.en ?? '',
1138
- errorMessage_ar: rule.errorMessage?.ar ?? '',
1139
- ruleType: rule.ruleTypeKey,
1140
- targetScope: rule.targetScope,
1141
- targetModuleKey: rule.targetModuleKey ?? '',
1142
- targetOperationKey: rule.targetOperationKey ?? '',
1143
- stopOnFailure: rule.stopOnFailure,
1144
- isActive: rule.isActive,
1145
- };
1146
- // Unpack configuration fields with config_ prefix
1147
- if (rule.configuration) {
1148
- Object.entries(rule.configuration).forEach(([key, value]) => {
1149
- formData[`config_${key}`] = value;
1150
- });
2435
+ patchWizardState(patch) {
2436
+ const currentState = normalizeWizardFormState(this.wizardFormControl.getRawValue());
2437
+ const nextState = normalizeWizardFormState({
2438
+ ...currentState,
2439
+ ...patch,
2440
+ });
2441
+ if (areWizardStatesEqual(currentState, nextState)) {
2442
+ return;
1151
2443
  }
1152
- return formData;
1153
- }
1154
- packFormToCreateDto(formValue) {
1155
- return {
1156
- key: formValue['key'],
1157
- name: {
1158
- en: formValue['name_en'],
1159
- ar: formValue['name_ar'],
1160
- },
1161
- description: formValue['description_en'] || formValue['description_ar']
1162
- ? {
1163
- en: formValue['description_en'] ?? '',
1164
- ar: formValue['description_ar'] ?? '',
1165
- }
1166
- : undefined,
1167
- errorMessage: {
1168
- en: formValue['errorMessage_en'],
1169
- ar: formValue['errorMessage_ar'],
1170
- },
1171
- ruleType: formValue['ruleType'],
1172
- targetScope: formValue['targetScope'],
1173
- targetModuleKey: formValue['targetModuleKey'] || undefined,
1174
- targetOperationKey: formValue['targetOperationKey'] || undefined,
1175
- configuration: this.extractConfiguration(formValue),
1176
- priority: formValue['priority'],
1177
- stopOnFailure: formValue['stopOnFailure'] ?? true,
1178
- };
2444
+ this.wizardFormControl.setValue(nextState);
1179
2445
  }
1180
- packFormToUpdateDto(formValue) {
1181
- return {
1182
- ...this.packFormToCreateDto(formValue),
1183
- isActive: formValue['isActive'] ?? true,
1184
- };
2446
+ setWizardState(state) {
2447
+ const currentState = normalizeWizardFormState(this.wizardFormControl.getRawValue());
2448
+ const nextState = normalizeWizardFormState(state);
2449
+ if (areWizardStatesEqual(currentState, nextState)) {
2450
+ return;
2451
+ }
2452
+ this.wizardFormControl.setValue(nextState);
1185
2453
  }
1186
- extractConfiguration(formValue) {
1187
- const config = {};
1188
- Object.entries(formValue).forEach(([key, value]) => {
1189
- if (key.startsWith('config_') &&
1190
- value !== null &&
1191
- value !== undefined &&
1192
- value !== '') {
1193
- const configKey = key.replace('config_', '');
1194
- config[configKey] = value;
1195
- }
2454
+ resetWizardForm() {
2455
+ this.setWizardState(DEFAULT_WIZARD_FORM_STATE);
2456
+ }
2457
+ clearReferenceData() {
2458
+ const hasRequestSchemas = untracked(() => this.requestSchemas().length > 0);
2459
+ const hasProperties = untracked(() => this.properties().length > 0);
2460
+ if (hasRequestSchemas) {
2461
+ this.facade.clearRequestSchemas();
2462
+ }
2463
+ if (hasProperties) {
2464
+ this.facade.clearProperties();
2465
+ }
2466
+ }
2467
+ saveCreate(payload) {
2468
+ this.facade.addRule(payload).subscribe({
2469
+ next: () => this.ref.close(true),
2470
+ error: () => { },
2471
+ });
2472
+ }
2473
+ saveUpdate(id, payload) {
2474
+ this.facade.updateRule(id, payload).subscribe({
2475
+ next: () => this.ref.close(true),
2476
+ error: () => { },
1196
2477
  });
1197
- return Object.keys(config).length > 0 ? config : undefined;
2478
+ }
2479
+ submitLegacyForm() {
2480
+ if (!this.legacyFormControl.valid) {
2481
+ return;
2482
+ }
2483
+ const formValue = this.legacyFormControl.value;
2484
+ if (!formValue) {
2485
+ return;
2486
+ }
2487
+ const ruleId = this.ruleForEdit()?.id;
2488
+ if (ruleId) {
2489
+ this.saveUpdate(ruleId, packLegacyFormToUpdateDto(formValue));
2490
+ return;
2491
+ }
2492
+ this.saveCreate(packLegacyFormToCreateDto(formValue));
2493
+ }
2494
+ ruleTypeOptions() {
2495
+ const lang = this.langCode();
2496
+ return this.ruleTypes().map((ruleType) => ({
2497
+ label: ruleType.name[lang] || ruleType.name.en,
2498
+ value: readGovernanceRuleTypeKey(ruleType),
2499
+ }));
1198
2500
  }
1199
2501
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: GovernanceRuleForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
1200
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: GovernanceRuleForm, isStandalone: true, selector: "mt-governance-rule-form", inputs: { ruleForEdit: { classPropertyName: "ruleForEdit", publicName: "ruleForEdit", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DialogService], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'governance'\">\r\n <div\r\n [class]=\"'flex flex-col gap-3 p-4 overflow-y-auto ' + modal.contentClass\"\r\n >\r\n @if (isLoadingRule()) {\r\n <p-skeleton class=\"my-4 mt-7\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"6rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n } @else {\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"ruleFormControl\"\r\n />\r\n }\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"'cancel' | transloco\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n />\r\n <mt-button\r\n [label]=\"ruleForEdit() ? t('update') : t('create')\"\r\n [loading]=\"isAddingRule() || isUpdatingRule()\"\r\n [disabled]=\"!ruleFormControl.valid\"\r\n (click)=\"onSubmit()\"\r\n />\r\n </div>\r\n</ng-container>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
2502
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: GovernanceRuleForm, isStandalone: true, selector: "mt-governance-rule-form", inputs: { ruleForEdit: { classPropertyName: "ruleForEdit", publicName: "ruleForEdit", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full" }, viewQueries: [{ propertyName: "dynamicFormRef", first: true, predicate: DynamicForm, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'governance'\">\n <div\n [class]=\"\n 'governance-rule-form-content flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 sm:p-5 ' +\n modal.contentClass\n \"\n >\n @if (isInitializing()) {\n <div class=\"space-y-4\">\n <p-skeleton height=\"4rem\" />\n <p-skeleton height=\"12rem\" />\n <p-skeleton height=\"12rem\" />\n </div>\n } @else if (useLegacyEditor()) {\n <section class=\"rounded-2xl border border-amber-200 bg-amber-50/70 p-4\">\n <p class=\"m-0 text-sm font-semibold text-slate-900\">\n {{ t(\"wizard.legacy-title\") }}\n </p>\n <p class=\"mt-1 mb-0 text-sm leading-6 text-slate-600\">\n {{ t(\"wizard.legacy-description\") }}\n </p>\n </section>\n\n <mt-dynamic-form\n [formConfig]=\"legacyFormConfig()\"\n [formControl]=\"legacyFormControl\"\n />\n } @else {\n @if (noRecipesAvailable()) {\n <section\n class=\"rounded-2xl border border-orange-200 bg-orange-50/80 p-4\"\n >\n <p class=\"m-0 text-sm font-semibold text-slate-900\">\n {{ t(\"wizard.no-recipes-title\") }}\n </p>\n <p class=\"mt-1 mb-0 text-sm leading-6 text-slate-600\">\n {{ t(\"wizard.no-recipes-description\") }}\n </p>\n </section>\n }\n\n <mt-dynamic-form\n [formConfig]=\"wizardFormConfig()\"\n [formControl]=\"wizardFormControl\"\n />\n\n @if (hasReviewSummary()) {\n <section class=\"rounded-2xl border border-slate-900 bg-slate-900 p-4\">\n <p\n class=\"m-0 text-xs font-semibold uppercase tracking-[0.18em] text-white/70\"\n >\n {{ t(\"wizard.summary-title\") }}\n </p>\n <p class=\"mt-2 mb-0 text-sm leading-6 text-white\">\n {{ reviewSummary() }}\n </p>\n </section>\n }\n }\n </div>\n\n <div\n [class]=\"\n 'governance-rule-form-footer ' +\n modal.footerClass +\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\n \"\n >\n <mt-button\n variant=\"outlined\"\n [label]=\"'cancel' | transloco\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n\n <mt-button\n [label]=\"ruleForEdit() ? t('update') : t('create')\"\n [loading]=\"isAddingRule() || isUpdatingRule()\"\n [disabled]=\"isAddingRule() || isUpdatingRule()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n </div>\n</ng-container>\n", styles: [":host{display:block;min-height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1201
2503
  }
1202
2504
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: GovernanceRuleForm, decorators: [{
1203
2505
  type: Component,
1204
- args: [{ selector: 'mt-governance-rule-form', standalone: true, imports: [
2506
+ args: [{ selector: 'mt-governance-rule-form', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1205
2507
  CommonModule,
1206
2508
  Button,
1207
- TranslocoDirective,
1208
- SkeletonModule,
1209
2509
  DynamicForm,
1210
2510
  ReactiveFormsModule,
2511
+ SkeletonModule,
2512
+ TranslocoDirective,
1211
2513
  TranslocoPipe,
1212
- ], providers: [DialogService], template: "<ng-container *transloco=\"let t; prefix: 'governance'\">\r\n <div\r\n [class]=\"'flex flex-col gap-3 p-4 overflow-y-auto ' + modal.contentClass\"\r\n >\r\n @if (isLoadingRule()) {\r\n <p-skeleton class=\"my-4 mt-7\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"6rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n } @else {\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"ruleFormControl\"\r\n />\r\n }\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"'cancel' | transloco\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n />\r\n <mt-button\r\n [label]=\"ruleForEdit() ? t('update') : t('create')\"\r\n [loading]=\"isAddingRule() || isUpdatingRule()\"\r\n [disabled]=\"!ruleFormControl.valid\"\r\n (click)=\"onSubmit()\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
1213
- }], ctorParameters: () => [], propDecorators: { ruleForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "ruleForEdit", required: false }] }] } });
2514
+ ], host: {
2515
+ class: 'block h-full',
2516
+ }, template: "<ng-container *transloco=\"let t; prefix: 'governance'\">\n <div\n [class]=\"\n 'governance-rule-form-content flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 sm:p-5 ' +\n modal.contentClass\n \"\n >\n @if (isInitializing()) {\n <div class=\"space-y-4\">\n <p-skeleton height=\"4rem\" />\n <p-skeleton height=\"12rem\" />\n <p-skeleton height=\"12rem\" />\n </div>\n } @else if (useLegacyEditor()) {\n <section class=\"rounded-2xl border border-amber-200 bg-amber-50/70 p-4\">\n <p class=\"m-0 text-sm font-semibold text-slate-900\">\n {{ t(\"wizard.legacy-title\") }}\n </p>\n <p class=\"mt-1 mb-0 text-sm leading-6 text-slate-600\">\n {{ t(\"wizard.legacy-description\") }}\n </p>\n </section>\n\n <mt-dynamic-form\n [formConfig]=\"legacyFormConfig()\"\n [formControl]=\"legacyFormControl\"\n />\n } @else {\n @if (noRecipesAvailable()) {\n <section\n class=\"rounded-2xl border border-orange-200 bg-orange-50/80 p-4\"\n >\n <p class=\"m-0 text-sm font-semibold text-slate-900\">\n {{ t(\"wizard.no-recipes-title\") }}\n </p>\n <p class=\"mt-1 mb-0 text-sm leading-6 text-slate-600\">\n {{ t(\"wizard.no-recipes-description\") }}\n </p>\n </section>\n }\n\n <mt-dynamic-form\n [formConfig]=\"wizardFormConfig()\"\n [formControl]=\"wizardFormControl\"\n />\n\n @if (hasReviewSummary()) {\n <section class=\"rounded-2xl border border-slate-900 bg-slate-900 p-4\">\n <p\n class=\"m-0 text-xs font-semibold uppercase tracking-[0.18em] text-white/70\"\n >\n {{ t(\"wizard.summary-title\") }}\n </p>\n <p class=\"mt-2 mb-0 text-sm leading-6 text-white\">\n {{ reviewSummary() }}\n </p>\n </section>\n }\n }\n </div>\n\n <div\n [class]=\"\n 'governance-rule-form-footer ' +\n modal.footerClass +\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\n \"\n >\n <mt-button\n variant=\"outlined\"\n [label]=\"'cancel' | transloco\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n\n <mt-button\n [label]=\"ruleForEdit() ? t('update') : t('create')\"\n [loading]=\"isAddingRule() || isUpdatingRule()\"\n [disabled]=\"isAddingRule() || isUpdatingRule()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n </div>\n</ng-container>\n", styles: [":host{display:block;min-height:100%}\n"] }]
2517
+ }], ctorParameters: () => [], propDecorators: { ruleForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "ruleForEdit", required: false }] }], dynamicFormRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DynamicForm), { isSignal: true }] }] } });
1214
2518
 
1215
2519
  class GovernanceRulesList {
1216
2520
  // ============================================================================
@@ -1360,9 +2664,18 @@ class GovernanceRulesList {
1360
2664
  type: 'select',
1361
2665
  label: this.translocoService.translate('governance.scope'),
1362
2666
  options: [
1363
- { label: 'Level', value: 'Level' },
1364
- { label: 'Module', value: 'Module' },
1365
- { label: 'PhaseGate', value: 'PhaseGate' },
2667
+ {
2668
+ label: this.translocoService.translate('governance.scope-level'),
2669
+ value: 'Level',
2670
+ },
2671
+ {
2672
+ label: this.translocoService.translate('governance.scope-module'),
2673
+ value: 'Module',
2674
+ },
2675
+ {
2676
+ label: this.translocoService.translate('governance.scope-phase-gate'),
2677
+ value: 'PhaseGate',
2678
+ },
1366
2679
  ],
1367
2680
  },
1368
2681
  },
@@ -1424,14 +2737,38 @@ class GovernanceRulesList {
1424
2737
  }
1425
2738
  getRuleTypeOptions() {
1426
2739
  return [
1427
- { label: 'Module Exists', value: 'ModuleExists' },
1428
- { label: 'Module Does Not Exist', value: 'ModuleDoesNotExist' },
1429
- { label: 'Module Max Count', value: 'ModuleMaxCount' },
1430
- { label: 'Module Min Count', value: 'ModuleMinCount' },
1431
- { label: 'Field Required', value: 'FieldRequired' },
1432
- { label: 'Field Value', value: 'FieldValue' },
1433
- { label: 'Phase Gate Module Required', value: 'PhaseGateModuleRequired' },
1434
- { label: 'No Active Request', value: 'NoActiveRequest' },
2740
+ {
2741
+ label: this.translocoService.translate('governance.rule-types.ModuleExists'),
2742
+ value: 'ModuleExists',
2743
+ },
2744
+ {
2745
+ label: this.translocoService.translate('governance.rule-types.ModuleDoesNotExist'),
2746
+ value: 'ModuleDoesNotExist',
2747
+ },
2748
+ {
2749
+ label: this.translocoService.translate('governance.rule-types.ModuleMaxCount'),
2750
+ value: 'ModuleMaxCount',
2751
+ },
2752
+ {
2753
+ label: this.translocoService.translate('governance.rule-types.ModuleMinCount'),
2754
+ value: 'ModuleMinCount',
2755
+ },
2756
+ {
2757
+ label: this.translocoService.translate('governance.rule-types.FieldRequired'),
2758
+ value: 'FieldRequired',
2759
+ },
2760
+ {
2761
+ label: this.translocoService.translate('governance.rule-types.FieldValue'),
2762
+ value: 'FieldValue',
2763
+ },
2764
+ {
2765
+ label: this.translocoService.translate('governance.rule-types.PhaseGateModuleRequired'),
2766
+ value: 'PhaseGateModuleRequired',
2767
+ },
2768
+ {
2769
+ label: this.translocoService.translate('governance.rule-types.NoActiveRequest'),
2770
+ value: 'NoActiveRequest',
2771
+ },
1435
2772
  ];
1436
2773
  }
1437
2774
  getRuleTypeChipClass(ruleTypeKey) {
@@ -1456,7 +2793,7 @@ class GovernanceRulesList {
1456
2793
  return icons[scope] ?? 'general.file-02';
1457
2794
  }
1458
2795
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: GovernanceRulesList, deps: [], target: i0.ɵɵFactoryTarget.Component });
1459
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: GovernanceRulesList, isStandalone: true, selector: "mt-governance-rules-list", viewQueries: [{ propertyName: "typeCol", first: true, predicate: ["typeCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "priorityCol", first: true, predicate: ["priorityCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'governance'\">\r\n <div class=\"space-y-4\">\r\n <!-- Rule Type Column Template -->\r\n <ng-template #typeCol let-row>\r\n <mt-chip\r\n [label]=\"row.ruleTypeKey\"\r\n [styleClass]=\"getRuleTypeChipClass(row.ruleTypeKey)\"\r\n />\r\n </ng-template>\r\n\r\n <!-- Scope Column Template -->\r\n <ng-template #scopeCol let-row>\r\n <div class=\"flex items-center gap-2\">\r\n <mt-icon\r\n class=\"text-lg\"\r\n [icon]=\"getScopeIcon(row.targetScope)\"\r\n ></mt-icon>\r\n <span>{{ row.targetScope }}</span>\r\n @if (row.targetModuleKey) {\r\n <span class=\"text-slate-500\">\u2192 {{ row.targetModuleKey }}</span>\r\n }\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Priority Column Template -->\r\n <ng-template #priorityCol let-row>\r\n <div\r\n class=\"flex items-center justify-center w-8 h-8 rounded-full bg-slate-100 text-slate-700 font-semibold text-sm\"\r\n >\r\n {{ row.priority }}\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Table -->\r\n <mt-table\r\n [tabs]=\"tabs()\"\r\n [(activeTab)]=\"activeTab\"\r\n [data]=\"rules()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n [loading]=\"loading()\"\r\n (cellChange)=\"onCellChange($event)\"\r\n >\r\n </mt-table>\r\n </div>\r\n</ng-container>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "showFilters", "loading", "updating", "lazy", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "exportable", "exportFilename", "actionShape", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "pageSize", "currentPage", "first", "filterTerm"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass"], outputs: ["onRemove", "onImageError"] }] });
2796
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: GovernanceRulesList, isStandalone: true, selector: "mt-governance-rules-list", viewQueries: [{ propertyName: "typeCol", first: true, predicate: ["typeCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "priorityCol", first: true, predicate: ["priorityCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'governance'\">\r\n <div class=\"space-y-4\">\r\n <!-- Rule Type Column Template -->\r\n <ng-template #typeCol let-row>\r\n <mt-chip\r\n [label]=\"row.ruleTypeKey\"\r\n [styleClass]=\"getRuleTypeChipClass(row.ruleTypeKey)\"\r\n />\r\n </ng-template>\r\n\r\n <!-- Scope Column Template -->\r\n <ng-template #scopeCol let-row>\r\n <div class=\"flex items-center gap-2\">\r\n <mt-icon\r\n class=\"text-lg\"\r\n [icon]=\"getScopeIcon(row.targetScope)\"\r\n ></mt-icon>\r\n <span>{{ row.targetScope }}</span>\r\n @if (row.targetModuleKey) {\r\n <span class=\"text-slate-500\">\u2192 {{ row.targetModuleKey }}</span>\r\n }\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Priority Column Template -->\r\n <ng-template #priorityCol let-row>\r\n <div\r\n class=\"flex items-center justify-center w-8 h-8 rounded-full bg-slate-100 text-slate-700 font-semibold text-sm\"\r\n >\r\n {{ row.priority }}\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Table -->\r\n <mt-table\r\n [tabs]=\"tabs()\"\r\n [(activeTab)]=\"activeTab\"\r\n [data]=\"rules()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n [loading]=\"loading()\"\r\n (cellChange)=\"onCellChange($event)\"\r\n >\r\n </mt-table>\r\n </div>\r\n</ng-container>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "exportable", "exportFilename", "actionShape", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "pageSize", "currentPage", "first", "filterTerm"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass"], outputs: ["onRemove", "onImageError"] }] });
1460
2797
  }
1461
2798
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: GovernanceRulesList, decorators: [{
1462
2799
  type: Component,
@@ -1465,7 +2802,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
1465
2802
  SkeletonModule,
1466
2803
  Table,
1467
2804
  TranslocoDirective,
1468
- Breadcrumb,
1469
2805
  Icon,
1470
2806
  Chip,
1471
2807
  ], template: "<ng-container *transloco=\"let t; prefix: 'governance'\">\r\n <div class=\"space-y-4\">\r\n <!-- Rule Type Column Template -->\r\n <ng-template #typeCol let-row>\r\n <mt-chip\r\n [label]=\"row.ruleTypeKey\"\r\n [styleClass]=\"getRuleTypeChipClass(row.ruleTypeKey)\"\r\n />\r\n </ng-template>\r\n\r\n <!-- Scope Column Template -->\r\n <ng-template #scopeCol let-row>\r\n <div class=\"flex items-center gap-2\">\r\n <mt-icon\r\n class=\"text-lg\"\r\n [icon]=\"getScopeIcon(row.targetScope)\"\r\n ></mt-icon>\r\n <span>{{ row.targetScope }}</span>\r\n @if (row.targetModuleKey) {\r\n <span class=\"text-slate-500\">\u2192 {{ row.targetModuleKey }}</span>\r\n }\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Priority Column Template -->\r\n <ng-template #priorityCol let-row>\r\n <div\r\n class=\"flex items-center justify-center w-8 h-8 rounded-full bg-slate-100 text-slate-700 font-semibold text-sm\"\r\n >\r\n {{ row.priority }}\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Table -->\r\n <mt-table\r\n [tabs]=\"tabs()\"\r\n [(activeTab)]=\"activeTab\"\r\n [data]=\"rules()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n [loading]=\"loading()\"\r\n (cellChange)=\"onCellChange($event)\"\r\n >\r\n </mt-table>\r\n </div>\r\n</ng-container>\r\n" }]