@iapps/ng-dhis2-ui 1.4.5 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  import { CommonModule } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { NgModule, ContentChild, Input, Directive, inject, ElementRef, ChangeDetectionStrategy, Component, EventEmitter, Output, NgZone, input, Injectable, model, computed, signal, effect, ViewChildren } from '@angular/core';
3
+ import { NgModule, ContentChild, Input, Directive, inject, ElementRef, ChangeDetectionStrategy, Component, EventEmitter, Output, NgZone, input, Injectable, model, computed, signal, effect, DestroyRef, ViewChildren } from '@angular/core';
4
4
  import { useDataQuery, useConfig, useAlert, clearSensitiveCaches, DataProvider, useDataEngine, Provider } from '@dhis2/app-runtime';
5
5
  import * as React from 'react';
6
6
  import React__default, { useState, useCallback, useRef, useEffect, createContext, useContext, useMemo, memo } from 'react';
7
7
  import * as ReactDOM from 'react-dom/client';
8
- import { from, map, catchError, of, zip, firstValueFrom, filter, take, Subject, takeUntil } from 'rxjs';
8
+ import { from, map, catchError, of, zip, firstValueFrom, filter, take, Subject, takeUntil, startWith, distinctUntilChanged } from 'rxjs';
9
9
  import parse from 'html-react-parser';
10
10
  import i18n from '@dhis2/d2-i18n';
11
11
  import { colors, theme, spacers } from '@dhis2/ui-constants';
@@ -32,7 +32,7 @@ import { isValid, format, addDays } from 'date-fns';
32
32
  import * as XLSX from 'xlsx';
33
33
  import * as i1 from '@angular/forms';
34
34
  import { FormControl, Validators, UntypedFormControl, UntypedFormGroup, FormGroup, ReactiveFormsModule } from '@angular/forms';
35
- import { toObservable } from '@angular/core/rxjs-interop';
35
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
36
36
  import { useMapEvents, useMap, MapContainer, TileLayer, GeoJSON, Marker } from 'react-leaflet';
37
37
  import moment from 'moment';
38
38
 
@@ -12998,6 +12998,31 @@ class FormMetaData {
12998
12998
  // Use of this source code is governed by a BSD-style
12999
12999
  // license that can be found in the LICENSE file.
13000
13000
 
13001
+ class FormValidationScopeUtil {
13002
+ static resolve(field, config, sections, actions) {
13003
+ const keys = [field.id, field.key, field.code].filter((key) => typeof key === 'string' && key.length > 0);
13004
+ const isMatch = (key) => typeof key === 'string' && keys.includes(key);
13005
+ const hiddenByMetadata = !!field.hidden;
13006
+ const hiddenByRule = (actions ?? []).some((action) => action.actionType === 'HIDEFIELD' && isMatch(action.field));
13007
+ const hiddenByAssignment = !!config?.hideCustomAssignedFields &&
13008
+ (config.autoAssignedValues ?? []).some((assignment) => isMatch(assignment.field));
13009
+ // Section membership is relevant only if the TRACKER form actually uses
13010
+ // configured sections. The fallback template renders all sanitized fields.
13011
+ const hasTrackerSections = config?.formType === 'TRACKER' &&
13012
+ Array.isArray(sections) &&
13013
+ sections.length > 0;
13014
+ const absentFromTrackerSections = !!hasTrackerSections &&
13015
+ !sections.some((section) => (section?.formFields ?? []).some((sectionField) => [sectionField?.id, sectionField?.key, sectionField?.code].some(isMatch)));
13016
+ return {
13017
+ excluded: hiddenByMetadata || hiddenByRule || hiddenByAssignment || absentFromTrackerSections,
13018
+ hiddenByMetadata,
13019
+ hiddenByRule,
13020
+ hiddenByAssignment,
13021
+ absentFromTrackerSections,
13022
+ };
13023
+ }
13024
+ }
13025
+
13001
13026
  class FormComponent {
13002
13027
  get layoutCssClass() {
13003
13028
  if (!this.isFormHorizontal()) {
@@ -13078,6 +13103,7 @@ class FormComponent {
13078
13103
  ...(ngDevMode ? [{ debugName: "configuration" }] : /* istanbul ignore next */ []));
13079
13104
  this.formUpdate = new EventEmitter();
13080
13105
  this.formValidityUpdate = new EventEmitter();
13106
+ this.destroyRef = inject(DestroyRef);
13081
13107
  // get sanitizedFields(): IFormField<string>[] {
13082
13108
  // return (this.fields() || []).filter((field: any) => {
13083
13109
  // if (field.hidden) {
@@ -13116,40 +13142,11 @@ class FormComponent {
13116
13142
  return map;
13117
13143
  }, /* @ts-ignore */
13118
13144
  ...(ngDevMode ? [{ debugName: "fieldControlKeyById" }] : /* istanbul ignore next */ []));
13119
- // get sanitizedFields(): IFormField<string>[] {
13120
- // return (this.fields() || []).filter(
13121
- // (field) => !this.isFieldHiddenByRules(field)
13122
- // );
13123
- // }
13124
- // getVisibleSectionFields(section: any): IFormField<string>[] {
13125
- // const rawFields: IFormField<string>[] = section?.formFields ?? [];
13126
- // return rawFields.filter((field) => !this.isFieldHiddenByRules(field));
13127
- // }
13128
- // private isFieldHiddenByRules(field: IFormField<string>): boolean {
13129
- // if (field.hidden) {
13130
- // return true;
13131
- // }
13132
- // const type = (field as any).isDataElement
13133
- // ? 'dataElement'
13134
- // : 'trackedEntityAttribute';
13135
- // const elementItem = (field as any)[type] || field;
13136
- // if (!elementItem) return false;
13137
- // const rule = (this.programRuleActions() || []).find(
13138
- // (ruleAction) =>
13139
- // ruleAction.field === elementItem.id &&
13140
- // ruleAction.actionType === 'HIDEFIELD'
13141
- // );
13142
- // return !!rule;
13143
- // }
13144
- // 1) Precompute the hidden field ids from programRuleActions()
13145
- this.hiddenFieldIds = computed(() => {
13146
- const actions = this.programRuleActions() || [];
13147
- return new Set(actions
13148
- .filter((ruleAction) => ruleAction.actionType === 'HIDEFIELD')
13149
- .map((ruleAction) => ruleAction.field)
13150
- .filter((id) => !!id));
13151
- }, /* @ts-ignore */
13152
- ...(ngDevMode ? [{ debugName: "hiddenFieldIds" }] : /* istanbul ignore next */ []));
13145
+ // Track only controls disabled by this component's visibility handling.
13146
+ // Do not re-enable controls disabled by metadata or other components.
13147
+ this.visibilityDisabledKeys = new Set();
13148
+ this.unrenderedViewDisabledKeys = new Set();
13149
+ this.previousShowErrorIds = new Set();
13153
13150
  this.ruleProcessor = effect(() => {
13154
13151
  this.#processRules(this.programRuleActions());
13155
13152
  }, /* @ts-ignore */
@@ -13250,6 +13247,9 @@ class FormComponent {
13250
13247
  return hasShowSectionAction || !hasHideSectionAction;
13251
13248
  }
13252
13249
  ngOnInit() {
13250
+ // A field can be hidden directly by metadata before program rules run.
13251
+ // Exclude such fields from validation without discarding their values.
13252
+ this.#processRules(this.programRuleActions(), true);
13253
13253
  this.values = this.form().getRawValue();
13254
13254
  // TODO: This is uneccesary emit at this stage of execution, since nothing has changes at initiation and potentially break some of implementation that depends formUpdate, which also is triggered when there is actual change in the form apart form initial values that came with it
13255
13255
  this.formUpdate.emit(new FormValue(this.form(), this.fields()));
@@ -13258,26 +13258,152 @@ class FormComponent {
13258
13258
  ngOnChanges(changes) {
13259
13259
  this.values = this.form().getRawValue();
13260
13260
  }
13261
- #processRules(ruleActions) {
13262
- ruleActions.forEach((ruleAction) => {
13263
- switch (ruleAction.actionType) {
13264
- case 'HIDEFIELD': {
13265
- const fieldToRemove = this.fields().find((field) => field.id === ruleAction.field);
13266
- if (fieldToRemove) {
13267
- const form = this.form();
13268
- const currentValue = form.get(fieldToRemove.key)?.value;
13269
- const shouldPreserveValue = fieldToRemove.disabled || fieldToRemove.generated;
13270
- if (currentValue != null && !shouldPreserveValue) {
13271
- form.get(fieldToRemove.key)?.setValue(null);
13272
- this.onFieldUpdate(form, fieldToRemove);
13273
- }
13261
+ ngAfterViewInit() {
13262
+ // For expanded, non-collapsible TRACKER forms, the instantiated field
13263
+ // components are the final authority on what the user can actually edit.
13264
+ // Avoid this policy for collapsible sections: collapsing must never waive
13265
+ // mandatory fields that the user can reveal by expanding a section.
13266
+ if (this.programEntryFormConfig()?.formType !== 'TRACKER' ||
13267
+ this.collapsibleSections())
13268
+ return;
13269
+ queueMicrotask(() => this.syncActuallyRenderedFields());
13270
+ this.fieldComponents.changes
13271
+ .pipe(takeUntilDestroyed(this.destroyRef))
13272
+ .subscribe(() => queueMicrotask(() => this.syncActuallyRenderedFields()));
13273
+ }
13274
+ syncActuallyRenderedFields() {
13275
+ if (this.destroyRef.destroyed)
13276
+ return;
13277
+ if (this.programEntryFormConfig()?.formType !== 'TRACKER' ||
13278
+ this.collapsibleSections())
13279
+ return;
13280
+ const renderedIds = new Set(this.fieldComponents.toArray()
13281
+ .map((component) => component.field()?.id)
13282
+ .filter((id) => typeof id === 'string'));
13283
+ // An empty view may be a loading/teardown transition, not an intentionally
13284
+ // empty form. Never disable the entire form based on this transient state.
13285
+ if (renderedIds.size === 0)
13286
+ return;
13287
+ const form = this.form();
13288
+ let changed = false;
13289
+ for (const field of this.fields() ?? []) {
13290
+ if (!field?.id)
13291
+ continue;
13292
+ const key = field.key || field.id;
13293
+ const controlKeys = [key, field.referenceKey].filter((item) => typeof item === 'string' && item.length > 0);
13294
+ const scope = FormValidationScopeUtil.resolve(field, this.programEntryFormConfig(), this.programEntryFormMetaData()?.sections, this.programRuleActions());
13295
+ const actuallyUnrendered = !renderedIds.has(field.id);
13296
+ for (const controlKey of controlKeys) {
13297
+ const control = form.get(controlKey);
13298
+ if (!control)
13299
+ continue;
13300
+ if (actuallyUnrendered) {
13301
+ if (control.enabled) {
13302
+ control.disable({ emitEvent: false });
13303
+ this.unrenderedViewDisabledKeys.add(controlKey);
13304
+ changed = true;
13274
13305
  }
13275
- break;
13276
13306
  }
13277
- default:
13278
- break;
13307
+ else if (this.unrenderedViewDisabledKeys.delete(controlKey) &&
13308
+ !scope.excluded && !field.disabled && !field.generated &&
13309
+ control.disabled) {
13310
+ control.enable({ emitEvent: false });
13311
+ changed = true;
13312
+ }
13279
13313
  }
13280
- });
13314
+ }
13315
+ if (changed)
13316
+ form.updateValueAndValidity({ emitEvent: true });
13317
+ }
13318
+ #processRules(ruleActions, force = false) {
13319
+ // Share the same visibility/validation policy as FlatFormComponent.
13320
+ const config = this.programEntryFormConfig();
13321
+ const sections = this.programEntryFormMetaData()?.sections;
13322
+ const hasTrackerSections = config?.formType === 'TRACKER' && Array.isArray(sections) && sections.length > 0;
13323
+ // This effect can run before required inputs are set. An empty action set
13324
+ // needs no work until ngOnInit synchronizes metadata-hidden controls.
13325
+ if (!ruleActions.length &&
13326
+ !this.visibilityDisabledKeys.size &&
13327
+ !this.previousShowErrorIds.size &&
13328
+ !hasTrackerSections &&
13329
+ !config?.hideCustomAssignedFields &&
13330
+ !force) {
13331
+ return;
13332
+ }
13333
+ const form = this.form();
13334
+ const showErrorIds = new Set(ruleActions
13335
+ .filter((action) => action.actionType === 'SHOWERROR')
13336
+ .map((action) => action.field)
13337
+ .filter((id) => !!id));
13338
+ let statusChanged = false;
13339
+ let clearedField;
13340
+ for (const field of this.fields() ?? []) {
13341
+ if (!field)
13342
+ continue;
13343
+ const controlKey = field.key || field.id;
13344
+ const control = form.get(controlKey);
13345
+ if (!control)
13346
+ continue;
13347
+ const scope = FormValidationScopeUtil.resolve(field, config, sections, ruleActions);
13348
+ const hiddenByRule = scope.hiddenByRule;
13349
+ const omittedFromSections = scope.absentFromTrackerSections;
13350
+ const hidden = scope.excluded;
13351
+ const originallyDisabled = !!field.disabled || !!field.generated;
13352
+ if (hidden) {
13353
+ // Retain prior HIDEFIELD clearing semantics for editable fields.
13354
+ // Metadata-hidden values (e.g. auto-assigned attributes) are preserved.
13355
+ if (hiddenByRule &&
13356
+ !omittedFromSections &&
13357
+ !originallyDisabled &&
13358
+ control.value != null) {
13359
+ control.setValue(null, { emitEvent: false });
13360
+ clearedField = field;
13361
+ statusChanged = true;
13362
+ }
13363
+ }
13364
+ // Keep both the field's control and any generated reference control in
13365
+ // the same validation scope; preserve their raw values when hidden.
13366
+ const keys = [controlKey, field.referenceKey].filter((key) => typeof key === 'string' && key.length > 0);
13367
+ for (const key of keys) {
13368
+ const target = form.get(key);
13369
+ if (!target)
13370
+ continue;
13371
+ if (hidden) {
13372
+ if (target.enabled) {
13373
+ target.disable({ emitEvent: false });
13374
+ statusChanged = true;
13375
+ }
13376
+ // Also track initial exclusions already disabled by FlatFormComponent,
13377
+ // so visibility changes can restore them without touching metadata locks.
13378
+ this.visibilityDisabledKeys.add(key);
13379
+ }
13380
+ else if (this.visibilityDisabledKeys.delete(key)) {
13381
+ if (!originallyDisabled && target.disabled) {
13382
+ target.enable({ emitEvent: false });
13383
+ statusChanged = true;
13384
+ }
13385
+ }
13386
+ }
13387
+ // A SHOWERROR action may cease to apply without any control value change.
13388
+ // Remove only its custom error; keep required/pattern/min/max errors.
13389
+ if (this.previousShowErrorIds.has(field.id) &&
13390
+ !showErrorIds.has(field.id) &&
13391
+ control.hasError('customError')) {
13392
+ const remainingErrors = { ...(control.errors ?? {}) };
13393
+ delete remainingErrors['customError'];
13394
+ control.setErrors(Object.keys(remainingErrors).length ? remainingErrors : null, { emitEvent: false });
13395
+ statusChanged = true;
13396
+ }
13397
+ }
13398
+ this.previousShowErrorIds.clear();
13399
+ showErrorIds.forEach((id) => this.previousShowErrorIds.add(id));
13400
+ if (statusChanged) {
13401
+ // Emit a single aggregate validity change for the parent flat form.
13402
+ form.updateValueAndValidity({ emitEvent: true });
13403
+ }
13404
+ if (clearedField) {
13405
+ this.onFieldUpdate(form, clearedField);
13406
+ }
13281
13407
  }
13282
13408
  onSubmit() {
13283
13409
  this.formUpdate.emit(this.form().getRawValue());
@@ -13302,7 +13428,7 @@ class FormComponent {
13302
13428
  }
13303
13429
  onRunTimeOptionUpdate(result) {
13304
13430
  if (this.fieldComponents) {
13305
- const fieldToUpdate = this.fieldComponents.find((fieldComponent) => fieldComponent?.field()?.dependentField?.id === result?.field?.id);
13431
+ const fieldToUpdate = this.fieldComponents.toArray().find((fieldComponent) => fieldComponent?.field()?.dependentField?.id === result?.field?.id);
13306
13432
  const selectedValue = String(this.form()?.value[result?.field?.id] ??
13307
13433
  this.form()?.value[result?.field?.key] ??
13308
13434
  '');
@@ -13313,6 +13439,31 @@ class FormComponent {
13313
13439
  ngOnDestroy() {
13314
13440
  this.ruleProcessor.destroy();
13315
13441
  }
13442
+ // get sanitizedFields(): IFormField<string>[] {
13443
+ // return (this.fields() || []).filter(
13444
+ // (field) => !this.isFieldHiddenByRules(field)
13445
+ // );
13446
+ // }
13447
+ // getVisibleSectionFields(section: any): IFormField<string>[] {
13448
+ // const rawFields: IFormField<string>[] = section?.formFields ?? [];
13449
+ // return rawFields.filter((field) => !this.isFieldHiddenByRules(field));
13450
+ // }
13451
+ // private isFieldHiddenByRules(field: IFormField<string>): boolean {
13452
+ // if (field.hidden) {
13453
+ // return true;
13454
+ // }
13455
+ // const type = (field as any).isDataElement
13456
+ // ? 'dataElement'
13457
+ // : 'trackedEntityAttribute';
13458
+ // const elementItem = (field as any)[type] || field;
13459
+ // if (!elementItem) return false;
13460
+ // const rule = (this.programRuleActions() || []).find(
13461
+ // (ruleAction) =>
13462
+ // ruleAction.field === elementItem.id &&
13463
+ // ruleAction.actionType === 'HIDEFIELD'
13464
+ // );
13465
+ // return !!rule;
13466
+ // }
13316
13467
  get sanitizedFields() {
13317
13468
  return (this.fields() || []).filter((field) => !this.isFieldHiddenByRules(field));
13318
13469
  }
@@ -13321,17 +13472,7 @@ class FormComponent {
13321
13472
  return rawFields.filter((field) => !this.isFieldHiddenByRules(field));
13322
13473
  }
13323
13474
  isFieldHiddenByRules(field) {
13324
- if (field.hidden) {
13325
- return true;
13326
- }
13327
- const type = field.isDataElement
13328
- ? 'dataElement'
13329
- : 'trackedEntityAttribute';
13330
- const elementItem = field || field;
13331
- if (!elementItem || !elementItem.id) {
13332
- return false;
13333
- }
13334
- return this.hiddenFieldIds().has(elementItem.id);
13475
+ return FormValidationScopeUtil.resolve(field, this.programEntryFormConfig(), this.programEntryFormMetaData()?.sections, this.programRuleActions()).excluded;
13335
13476
  }
13336
13477
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
13337
13478
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FormComponent, isStandalone: false, selector: "ng-dhis2-ui-form", inputs: { programEntryFormConfig: { classPropertyName: "programEntryFormConfig", publicName: "programEntryFormConfig", isSignal: true, isRequired: false, transformFunction: null }, formConfig: { classPropertyName: "formConfig", publicName: "formConfig", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, isFormHorizontal: { classPropertyName: "isFormHorizontal", publicName: "isFormHorizontal", isSignal: true, isRequired: false, transformFunction: null }, showSaveButton: { classPropertyName: "showSaveButton", publicName: "showSaveButton", isSignal: true, isRequired: false, transformFunction: null }, fieldsData: { classPropertyName: "fieldsData", publicName: "fieldsData", isSignal: true, isRequired: false, transformFunction: null }, formMetaData: { classPropertyName: "formMetaData", publicName: "formMetaData", isSignal: true, isRequired: false, transformFunction: null }, programEntryFormMetaData: { classPropertyName: "programEntryFormMetaData", publicName: "programEntryFormMetaData", isSignal: true, isRequired: false, transformFunction: null }, fieldClass: { classPropertyName: "fieldClass", publicName: "fieldClass", isSignal: true, isRequired: false, transformFunction: null }, shouldRenderAsCheckBoxesButton: { classPropertyName: "shouldRenderAsCheckBoxesButton", publicName: "shouldRenderAsCheckBoxesButton", isSignal: true, isRequired: false, transformFunction: null }, programRuleActions: { classPropertyName: "programRuleActions", publicName: "programRuleActions", isSignal: true, isRequired: false, transformFunction: null }, dataEntities: { classPropertyName: "dataEntities", publicName: "dataEntities", isSignal: true, isRequired: false, transformFunction: null }, dataId: { classPropertyName: "dataId", publicName: "dataId", isSignal: true, isRequired: false, transformFunction: null }, customOrgUnitRoots: { classPropertyName: "customOrgUnitRoots", publicName: "customOrgUnitRoots", isSignal: true, isRequired: false, transformFunction: null }, optionCascadeConfigs: { classPropertyName: "optionCascadeConfigs", publicName: "optionCascadeConfigs", isSignal: true, isRequired: false, transformFunction: null }, coordinatePickerGeoConfig: { classPropertyName: "coordinatePickerGeoConfig", publicName: "coordinatePickerGeoConfig", isSignal: true, isRequired: false, transformFunction: null }, program: { classPropertyName: "program", publicName: "program", isSignal: true, isRequired: false, transformFunction: null }, collapsibleSections: { classPropertyName: "collapsibleSections", publicName: "collapsibleSections", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formUpdate: "formUpdate", formValidityUpdate: "formValidityUpdate" }, viewQueries: [{ propertyName: "fieldComponents", predicate: FormFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- <form [formGroup]=\"form()\" class=\"{{ configuration().directionClassName }}\">\n <div class=\"form-name\">\n <h2>{{ configuration().formName }}</h2>\n </div>\n\n <div>\n {{ formMetaData().sections }}\n </div>\n <div\n *ngFor=\"let field of sanitizedFields\"\n class=\"form-field-{{ configuration().direction }}\"\n >\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [program]=\"program()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n</form> -->\n\n<!-- <form [formGroup]=\"form()\" class=\"form {{ configuration().directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) {\n\n @if (meta.programStageSections?.length) {\n @for (section of meta.programStageSections; track section?.id ?? $index) {\n <section class=\"form__section\">\n @if (section?.name) {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n }\n\n <div class=\"form__section-content\">\n @if (section.formFields?.length) {\n @for (field of section.formFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n </section>\n } @empty {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n</form> -->\n\n<!-- @if (programEntryFormConfig() && programEntryFormConfig()!.formType === 'EVENT') {\n <form [formGroup]=\"form()\" class=\"form {{ configuration().directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) {\n @if (meta.programStageSections?.length) {\n @for (section of meta.programStageSections; track section?.id ?? $index) {\n <section class=\"form__section\">\n @if (section?.name) {\n @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n }\n }\n\n @if (!collapsibleSections() || isSectionOpen(section, $index)) {\n <div class=\"form__section-content\">\n @if (section.formFields?.length) {\n @for (field of section.formFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n} @else if (programEntryFormConfig() && programEntryFormConfig()!.formType === 'TRACKER') {\n <form [formGroup]=\"form()\" class=\"form {{ configuration().directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) {\n @if (meta.sections?.length) {\n @for (section of meta.sections; track section?.id ?? $index) {\n <section class=\"form__section\">\n @if (section?.name) {\n @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n }\n }\n \n @if (!collapsibleSections() || isSectionOpen(section, $index)) {\n <div class=\"form__section-content\">\n \n @if (section.formFields?.length) {\n \n @for (field of section.formFields; track field?.id ?? $index) {\n <div>\n </div>\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n} @else {\n <form [formGroup]=\"form()\" class=\"{{ configuration().directionClassName }}\">\n <div class=\"form-name\">\n <h2>{{ configuration().formName }}</h2>\n </div>\n\n <div>\n {{ formMetaData().sections }}\n </div>\n <div\n *ngFor=\"let field of sanitizedFields\"\n class=\"form-field-{{ configuration().direction }}\"\n >\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n </form>\n} -->\n<!-- \n@if (programEntryFormConfig() && programEntryFormConfig()!.formType === 'EVENT')\n{\n<form\n [formGroup]=\"form()\"\n class=\"form {{ configuration().directionClassName }}\"\n>\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) { @if\n (meta?.programStageSections?.length) { @for (section of\n meta?.programStageSections; track section?.id ?? $index) { @let sectionId =\n 'section-' + (section?.id ?? $index); @let descId = sectionId + '-desc'; @let\n desc = section?.description; @let hasDesc = !!desc;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionId\">\n @if (section?.name) { @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n [attr.aria-controls]=\"sectionId\"\n [attr.aria-describedby]=\"\n hasDesc && isSectionOpen(section, $index) ? descId : null\n \"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">{{ section.name }} - {{ descId }}</div>\n @if (hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n } } } @if (!collapsibleSections() || isSectionOpen(section, $index)) { @if\n (collapsibleSections() && hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n }\n\n <div class=\"form__section-content\" [id]=\"sectionId\">\n @if (section.formFields?.length) { @for (field of\n getVisibleSectionFields(section); track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n \n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [optionCascadeConfigs]=\"optionCascadeConfigs()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n } } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } }\n</form>\n} @else if (programEntryFormConfig() && programEntryFormConfig()!.formType ===\n'TRACKER') {\n<form\n [formGroup]=\"form()\"\n class=\"form {{ configuration().directionClassName }}\"\n>\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ configuration().formName }}</h2>\n </div>\n\n @if (programEntryFormMetaData(); as meta) { @if (meta?.sections?.length) {\n @for (section of meta?.sections; track section?.id ?? $index) { @let sectionId\n = 'section-' + (section?.id ?? $index); @let descId = sectionId + '-desc';\n @let desc = section?.description; @let hasDesc = !!desc;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionId\">\n @if (section?.name) { @if (collapsibleSections()) {\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(section, $index)\"\n [attr.aria-expanded]=\"isSectionOpen(section, $index)\"\n [attr.aria-controls]=\"sectionId\"\n [attr.aria-describedby]=\"\n hasDesc && isSectionOpen(section, $index) ? descId : null\n \"\n >\n <span class=\"form__section-title\">{{ section.name }}</span>\n <span\n class=\"form__chevron\"\n [class.form__chevron--open]=\"isSectionOpen(section, $index)\"\n ></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">\n {{ section.name }}\n </div>\n @if (hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n } } } @if (!collapsibleSections() || isSectionOpen(section, $index)) { @if\n (collapsibleSections() && hasDesc) {\n <p class=\"form__section-description\" [id]=\"descId\">\n {{ desc }}\n </p>\n }\n <div class=\"form__section-content\" [id]=\"sectionId\">\n @if (section.formFields?.length) { @for (field of\n getVisibleSectionFields(section); track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n } } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n } @empty { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else { @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } } } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (field of sanitizedFields; track field?.id ?? $index) {\n <div class=\"form__field form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n } }\n</form>\n} @else {\n<form [formGroup]=\"form()\" [class]=\"configuration().directionClassName\">\n @if (configuration()?.formName) {\n <div class=\"form-name\">\n <h2>{{ configuration()?.formName }}</h2>\n </div>\n } @if (sanitizedFields?.length) { @for (field of sanitizedFields; track\n field?.id ?? $index) {\n <div class=\"form-field-{{ configuration().direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"field\"\n [fieldConfig]=\"configuration().fieldConfig\"\n [form]=\"form()\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [optionCascadeConfigMap]=\"optionCascadeConfigMap()\"\n [programRuleActions]=\"programRuleActions()\"\n [isCheckBoxButton]=\"shouldRenderAsCheckBoxesButton()\"\n (fieldUpdate)=\"onFieldUpdate($event, field)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } } @else {\n <div class=\"text-sm text-gray-500\">No fields to display.</div>\n }\n</form>\n} -->\n\n@let entryFormConfig = programEntryFormConfig();\n@let entryFormMeta = programEntryFormMetaData();\n@let uiConfig = configuration();\n@let formGroupValue = form();\n\n@let isSectionsCollapsible = collapsibleSections();\n\n@let dataEntitiesValue = dataEntities();\n@let dataEntityId = dataId();\n@let orgUnitRootIds = customOrgUnitRoots();\n\n@let cascadeConfigMap = optionCascadeConfigMap();\n@let cascadeConfigs = optionCascadeConfigs();\n\n@let ruleActionList = programRuleActions();\n@let renderAsCheckboxButtons = shouldRenderAsCheckBoxesButton();\n\n@let entryFormCoordinatePickerGeoConfig = coordinatePickerGeoConfig();\n@let entryFormFieldControlKeyById = fieldControlKeyById();\n\n@if (entryFormConfig?.formType === 'EVENT') {\n <form [formGroup]=\"formGroupValue\" class=\"form {{ uiConfig.directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ uiConfig.formName }}</h2>\n </div>\n\n @if (entryFormMeta?.programStageSections?.length) {\n @for (stageSection of entryFormMeta.programStageSections; track stageSection?.id ?? $index) {\n @if (isSectionVisible(stageSection, 'PROGRAM_STAGE_SECTION')) {\n @let sectionKey = stageSection?.id ?? $index;\n @let sectionDomId = 'section-' + sectionKey;\n @let descriptionDomId = sectionDomId + '-desc';\n <!-- @let sectionDescription = $safeNavigationMigration(stageSection?.description); -->\n @let sectionDescription = stageSection?.description;\n @let hasDescription = !!sectionDescription;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionDomId\">\n @if (stageSection?.name) {\n @if (isSectionsCollapsible) {\n @let isOpen = isSectionOpen(stageSection, $index);\n\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(stageSection, $index)\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"sectionDomId\"\n [attr.aria-describedby]=\"hasDescription && isOpen ? descriptionDomId : null\"\n >\n <span class=\"form__section-title\">{{ stageSection.name }}</span>\n <span class=\"form__chevron\" [class.form__chevron--open]=\"isOpen\"></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">{{ stageSection.name }}</div>\n @if (hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n }\n }\n\n @let isOpen = !isSectionsCollapsible || isSectionOpen(stageSection, $index);\n @if (isOpen) {\n @if (isSectionsCollapsible && hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n\n <div class=\"form__section-content\" [id]=\"sectionDomId\">\n @if (stageSection?.formFields?.length) {\n @for (visibleField of getVisibleSectionFields(stageSection); track visibleField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"visibleField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [optionCascadeConfigs]=\"cascadeConfigs\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, visibleField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n }\n } @empty {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n\n} @else if (entryFormConfig?.formType === 'TRACKER') {\n\n <form [formGroup]=\"formGroupValue\" class=\"form {{ uiConfig.directionClassName }}\">\n <div class=\"form__header\">\n <h2 class=\"form__title\">{{ uiConfig.formName }}</h2>\n </div>\n\n @if (entryFormMeta?.sections?.length) {\n @for (teiSection of entryFormMeta.sections; track teiSection?.id ?? $index) {\n @if (isSectionVisible(teiSection, 'PROGRAM_SECTION')) {\n @let sectionKey = teiSection?.id ?? $index;\n @let sectionDomId = 'section-' + sectionKey;\n @let descriptionDomId = sectionDomId + '-desc';\n <!-- @let sectionDescription = $safeNavigationMigration(teiSection?.description); -->\n @let sectionDescription = teiSection?.description;\n @let hasDescription = !!sectionDescription;\n\n <section class=\"form__section\" [attr.data-section-id]=\"sectionDomId\">\n @if (teiSection?.name) {\n @if (isSectionsCollapsible) {\n @let isOpen = isSectionOpen(teiSection, $index);\n\n <button\n type=\"button\"\n class=\"form__section-toggle\"\n (click)=\"openSection(teiSection, $index)\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"sectionDomId\"\n [attr.aria-describedby]=\"hasDescription && isOpen ? descriptionDomId : null\"\n >\n <span class=\"form__section-title\">{{ teiSection.name }}</span>\n <span class=\"form__chevron\" [class.form__chevron--open]=\"isOpen\"></span>\n </button>\n } @else {\n <div class=\"form__field-group-name\">{{ teiSection.name }}</div>\n @if (hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n }\n }\n\n @let isOpen = !isSectionsCollapsible || isSectionOpen(teiSection, $index);\n @if (isOpen) {\n @if (isSectionsCollapsible && hasDescription) {\n <p class=\"form__section-description\" [id]=\"descriptionDomId\">\n {{ sectionDescription }}\n </p>\n }\n\n <div class=\"form__section-content\" [id]=\"sectionDomId\">\n @if (teiSection?.formFields?.length) {\n @for (visibleField of getVisibleSectionFields(teiSection); track visibleField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"visibleField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, visibleField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n } @else {\n <div class=\"form__empty\">No fields available for this section.</div>\n }\n </div>\n }\n </section>\n }\n } @empty {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n } @else {\n <div class=\"form__empty\">No metadata available</div>\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form__field form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n } @empty {\n <div class=\"form__empty\">No fields to display.</div>\n }\n }\n </form>\n\n} @else {\n\n <form [formGroup]=\"formGroupValue\" [class]=\"uiConfig.directionClassName\">\n @if (uiConfig?.formName) {\n <div class=\"form-name\">\n <h2>{{ uiConfig.formName }}</h2>\n </div>\n }\n\n @if (sanitizedFields?.length) {\n @for (fallbackField of sanitizedFields; track fallbackField?.id ?? $index) {\n <div class=\"form-field-{{ uiConfig.direction }}\">\n <ng-dhis2-ui-form-field\n [field]=\"fallbackField\"\n [fieldConfig]=\"uiConfig.fieldConfig\"\n [form]=\"formGroupValue\"\n [fieldClass]=\"fieldClass()\"\n [dataEntities]=\"dataEntitiesValue\"\n [dataId]=\"dataEntityId\"\n [customOrgUnitRoots]=\"orgUnitRootIds\"\n [optionCascadeConfigMap]=\"cascadeConfigMap\"\n [coordinatePickerGeoConfig]=\"entryFormCoordinatePickerGeoConfig\"\n [fieldControlKeyById]=\"entryFormFieldControlKeyById\"\n [programRuleActions]=\"ruleActionList\"\n [isCheckBoxButton]=\"renderAsCheckboxButtons\"\n (fieldUpdate)=\"onFieldUpdate($event, fallbackField)\"\n (runtimeOptionChange)=\"onRunTimeOptionUpdate($event)\"\n />\n </div>\n }\n } @else {\n <div class=\"text-sm text-gray-500\">No fields to display.</div>\n }\n </form>\n\n}\n", styles: ["@charset \"UTF-8\";.form__section-toggle{width:100%;display:flex;align-items:center;justify-content:space-between;font-size:15px;font-weight:600;padding:14px 16px 16px;background-color:#e8edf2;color:#374151;border:0;cursor:pointer;text-align:left}.form__section-toggle:focus{outline:2px solid #93c5fd;outline-offset:2px}.form__section-title{pointer-events:none}.form__chevron{width:10px;height:10px;display:inline-block;margin-left:12px;-webkit-mask-image:linear-gradient(#000,#000);mask-image:linear-gradient(#000,#000);border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg);transition:transform .12s ease-in-out}.form__chevron--open{transform:rotate(45deg)}.form__section{border:1px solid #eee;margin-bottom:16px;background-color:#fff}.form__field-group-name{display:inline-flex;font-size:14px;font-weight:600;padding:8px 16px;background-color:#e8edf2;color:#374151}.form__section-content{padding:16px}.form__section-description{margin:5px 5px 15px;font-size:15px;line-height:1.5;color:#4b5563;white-space:pre-line}.form__section-description:before{content:\"\";display:inline-block;width:4px;height:.9em;margin-right:8px;vertical-align:-2px;background-color:#cbd5e1;border-radius:2px}\n"], dependencies: [{ kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormFieldComponent, selector: "ng-dhis2-ui-form-field", inputs: ["field", "fieldConfig", "form", "isCheckBoxButton", "fieldClass", "programRuleActions", "dataEntities", "minDate", "maxDate", "dataId", "customOrgUnitRoots", "program", "optionCascadeConfigs", "optionCascadeConfigMap", "coordinatePickerGeoConfig", "fieldControlKeyById"], outputs: ["fieldUpdate", "immediateFieldUpdate", "runtimeOptionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -13634,6 +13775,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
13634
13775
  class FlatFormComponent extends BaseFormWrapperComponent {
13635
13776
  constructor() {
13636
13777
  super(...arguments);
13778
+ this.destroyRef = inject(DestroyRef);
13637
13779
  this.collapsibleSections = input(false, /* @ts-ignore */
13638
13780
  ...(ngDevMode ? [{ debugName: "collapsibleSections" }] : /* istanbul ignore next */ []));
13639
13781
  this.programEntryFormConfig = input.required(/* @ts-ignore */
@@ -13643,8 +13785,50 @@ class FlatFormComponent extends BaseFormWrapperComponent {
13643
13785
  }
13644
13786
  ngOnInit() {
13645
13787
  this.formGroup = FormUtil.getFormGroup(this.fields(), this.dataValues());
13788
+ // Build the interactive validation scope before the first validity event.
13789
+ // A TRACKER with sections renders only fields belonging to those sections,
13790
+ // while FormUtil creates controls for the complete metadata field list.
13791
+ this.excludeUnrenderedTrackerControls();
13792
+ // Form controls can change validity without going through a fieldUpdate
13793
+ // callback (e.g. conditional validators, program rules, or async changes).
13794
+ // Keep the form validity output in sync with the actual FormGroup status.
13795
+ this.formGroup.statusChanges
13796
+ .pipe(startWith(this.formGroup.status), map((status) => status === 'VALID'), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
13797
+ .subscribe((isValid) => this.updateFormValidity(isValid));
13646
13798
  this.onFormUpdate(this.dataValues());
13647
13799
  }
13800
+ /**
13801
+ * Exclude only controls that cannot be edited in this form: metadata-hidden,
13802
+ * HIDEFIELD, hidden auto-assigned, or absent from TRACKER sections.
13803
+ * Controls keep their raw values for the existing SDK save flow.
13804
+ */
13805
+ excludeUnrenderedTrackerControls() {
13806
+ const config = this.programEntryFormConfig();
13807
+ const sections = this.programEntryFormMetaData()?.sections;
13808
+ const actions = this.ruleActions() ?? [];
13809
+ let changed = false;
13810
+ for (const field of this.fields() ?? []) {
13811
+ if (!field)
13812
+ continue;
13813
+ const scope = FormValidationScopeUtil.resolve(field, config, sections, actions);
13814
+ const control = this.formGroup.get(field.key || field.id);
13815
+ if (scope.excluded && control?.enabled) {
13816
+ control.disable({ emitEvent: false });
13817
+ changed = true;
13818
+ }
13819
+ // A referenceKey is a separate generated control and must share its
13820
+ // source field's visibility; it must not remain an invisible requirement.
13821
+ if (scope.excluded && field.referenceKey) {
13822
+ const reference = this.formGroup.get(field.referenceKey);
13823
+ if (reference?.enabled) {
13824
+ reference.disable({ emitEvent: false });
13825
+ changed = true;
13826
+ }
13827
+ }
13828
+ }
13829
+ if (changed)
13830
+ this.formGroup.updateValueAndValidity({ emitEvent: false });
13831
+ }
13648
13832
  onFormUpdate(dataValues) {
13649
13833
  // Controls can be omitted from a FLAT form when their fields are hidden.
13650
13834
  // Keep their initial values while letting rendered controls override them,
@@ -15355,6 +15539,201 @@ class ProgramEntryFormMetaData {
15355
15539
  // Use of this source code is governed by a BSD-style
15356
15540
  // license that can be found in the LICENSE file.
15357
15541
 
15542
+ function idOf(value) {
15543
+ if (typeof value === 'string')
15544
+ return value;
15545
+ if (value && typeof value === 'object' && 'id' in value) {
15546
+ const id = value.id;
15547
+ if (typeof id === 'string')
15548
+ return id;
15549
+ }
15550
+ return undefined;
15551
+ }
15552
+ function isEventContextField(field, config) {
15553
+ const name = field.id || field.key;
15554
+ const names = [name, field.key];
15555
+ if (names.some((n) => n === 'occurredAt' || n === 'eventDate')) {
15556
+ return config.hideEventDate !== true;
15557
+ }
15558
+ if (field.isOrgUnit || names.includes('orgUnit')) {
15559
+ return config.hideRegistrationUnit !== true;
15560
+ }
15561
+ return false;
15562
+ }
15563
+ function fieldIds(field) {
15564
+ return [field.id, idOf(field.dataElement), field.dataElementId]
15565
+ .filter((id) => typeof id === 'string' && id.length > 0);
15566
+ }
15567
+ function isTrackedEntityAttribute(field) {
15568
+ // Form metadata may use typed flags, or SDK-style attribute references.
15569
+ return field.isAttribute === true ||
15570
+ !!idOf(field.trackedEntityAttribute) ||
15571
+ !!idOf(field.attribute);
15572
+ }
15573
+ function belongsTo(field, stage) {
15574
+ return (idOf(field.programStage) ?? field.stageId ?? field.programStageId) === stage;
15575
+ }
15576
+ function matchingSectionFields(sections, stage) {
15577
+ return sections.filter((section) => {
15578
+ const owner = idOf(section.programStage) ?? section.stageId ?? section.programStageId;
15579
+ return !owner || owner === stage;
15580
+ });
15581
+ }
15582
+ class ProgramEntryFormScopeUtil {
15583
+ static resolve(metadata, config) {
15584
+ // Preserve the existing TRACKER-with-stage implementation. It can have
15585
+ // separate registration/stage workflows and must not be silently changed.
15586
+ if (config.formType !== 'EVENT' && config.formType !== 'TRACKER') {
15587
+ return { metadata, error: null };
15588
+ }
15589
+ if (config.formType === 'TRACKER') {
15590
+ if (config.programStage) {
15591
+ return { metadata, error: null };
15592
+ }
15593
+ // Registration without a stage has attributes and its existing special
15594
+ // registration fields; data elements are never required in this mode.
15595
+ const fields = metadata.formFields.filter((field) => field.isDataElement !== true &&
15596
+ !['occurredAt', 'eventDate'].includes(field.id) &&
15597
+ !['occurredAt', 'eventDate'].includes(field.key ?? ''));
15598
+ const allowed = new Set(fields.map((field) => field.id));
15599
+ const sections = metadata.sections?.map((section) => ({
15600
+ ...section,
15601
+ formFields: (section.formFields ?? []).filter((field) => allowed.has(field.id)),
15602
+ })).filter((section) => section.formFields.length > 0);
15603
+ return {
15604
+ metadata: { ...metadata, formFields: fields, sections },
15605
+ error: null,
15606
+ };
15607
+ }
15608
+ const stageId = config.programStage?.trim();
15609
+ if (!stageId) {
15610
+ return { metadata: null, error: 'EVENT form requires config.programStage.' };
15611
+ }
15612
+ const stageCandidates = [
15613
+ ...(Array.isArray(metadata.program?.programStages) ? metadata.program.programStages : []),
15614
+ ...(Array.isArray(metadata.programStages) ? metadata.programStages : []),
15615
+ ];
15616
+ if (metadata.programStage && typeof metadata.programStage !== 'string') {
15617
+ stageCandidates.push(metadata.programStage);
15618
+ }
15619
+ const stage = stageCandidates.find((item) => item.id === stageId);
15620
+ if (stageCandidates.length > 0 && !stage) {
15621
+ return {
15622
+ metadata: null,
15623
+ error: `Program stage ${stageId} is not part of the supplied program metadata.`,
15624
+ };
15625
+ }
15626
+ const stageElementIds = new Set((stage?.programStageDataElements ?? [])
15627
+ .map((item) => idOf(item.dataElement) ?? item.dataElementId)
15628
+ .filter((id) => !!id));
15629
+ // Sections supplied by the selected ProgramStage take precedence. The
15630
+ // top-level programStageSections is used when metadata is already scoped
15631
+ // to a stage, as is customary in entry-form metadata builders.
15632
+ const stageSectionsWithFields = stage?.programStageSections?.some((section) => (section.formFields ?? []).length > 0);
15633
+ const sectionSource = stageSectionsWithFields
15634
+ ? stage.programStageSections
15635
+ : metadata.programStageSections?.length
15636
+ ? metadata.programStageSections
15637
+ : stage?.programStageSections ?? [];
15638
+ const candidateSections = matchingSectionFields(sectionSource, stageId);
15639
+ const sectionFieldIds = new Set(candidateSections.flatMap((section) => (section.formFields ?? []).flatMap((field) => fieldIds(field))));
15640
+ // Stage sections sometimes contain complete field definitions even when
15641
+ // ProgramEntryFormMetaData.formFields contains registration attributes only.
15642
+ // Prefer the richer top-level definition where both are available.
15643
+ const candidates = [...metadata.formFields];
15644
+ const knownFieldIds = new Set(candidates.flatMap((field) => fieldIds(field)));
15645
+ for (const section of candidateSections) {
15646
+ for (const sectionField of section.formFields ?? []) {
15647
+ if (fieldIds(sectionField).some((id) => knownFieldIds.has(id)))
15648
+ continue;
15649
+ candidates.push(sectionField);
15650
+ fieldIds(sectionField).forEach((id) => knownFieldIds.add(id));
15651
+ }
15652
+ }
15653
+ // The metadata builder may not attach isDataElement=true. A stage's
15654
+ // programStageDataElements, its stage sections, or an explicit stage ID
15655
+ // is authoritative evidence of EVENT field membership. Do not allow
15656
+ // tracked-entity attributes through even when IDs overlap unexpectedly.
15657
+ const hasStageMembership = stageElementIds.size > 0 ||
15658
+ candidateSections.length > 0 ||
15659
+ candidates.some((field) => belongsTo(field, stageId));
15660
+ if (!hasStageMembership) {
15661
+ return {
15662
+ metadata: null,
15663
+ error: `No form metadata could be resolved for program stage ${stageId}.`,
15664
+ };
15665
+ }
15666
+ const fields = candidates.filter((field) => {
15667
+ if (isEventContextField(field, config))
15668
+ return true;
15669
+ if (isTrackedEntityAttribute(field))
15670
+ return false;
15671
+ const fieldStage = idOf(field.programStage) ?? field.stageId ?? field.programStageId;
15672
+ if (fieldStage && fieldStage !== stageId)
15673
+ return false;
15674
+ const refs = fieldIds(field);
15675
+ // An explicit stage DE list is the strongest source of truth.
15676
+ if (stageElementIds.size > 0) {
15677
+ return refs.some((id) => stageElementIds.has(id));
15678
+ }
15679
+ // Use selected-stage sections or explicit field ownership. Do not
15680
+ // require the optional isDataElement boolean from the metadata builder.
15681
+ return fieldStage === stageId || refs.some((id) => sectionFieldIds.has(id));
15682
+ });
15683
+ const allowed = new Set(fields.flatMap((field) => fieldIds(field)));
15684
+ const resolvedFieldById = new Map();
15685
+ for (const field of fields) {
15686
+ fieldIds(field).forEach((id) => resolvedFieldById.set(id, field));
15687
+ }
15688
+ const sections = candidateSections.map((section) => ({
15689
+ ...section,
15690
+ formFields: (section.formFields ?? [])
15691
+ .filter((field) => fieldIds(field).some((id) => allowed.has(id)))
15692
+ .map((field) => fieldIds(field)
15693
+ .map((id) => resolvedFieldById.get(id))
15694
+ .find((resolved) => !!resolved) ?? field),
15695
+ })).filter((section) => section.formFields.length > 0);
15696
+ // Fail closed only when there really are no stage fields. Do not reject
15697
+ // verified stage fields just because isDataElement was not supplied.
15698
+ const hasStageFields = fields.some((field) => !isEventContextField(field, config));
15699
+ const stageExplicitlyEmpty = !!stage &&
15700
+ Array.isArray(stage.programStageDataElements) &&
15701
+ stage.programStageDataElements.length === 0 &&
15702
+ sectionFieldIds.size === 0;
15703
+ if (!stageExplicitlyEmpty && !hasStageFields) {
15704
+ return {
15705
+ metadata: null,
15706
+ error: `Stage ${stageId} has no resolvable data-element field definitions. Check ProgramEntryFormMetaData stage sections and field IDs.`,
15707
+ };
15708
+ }
15709
+ // In section-based EVENT forms, ensure selected-stage fields not assigned
15710
+ // to a named section still render and are not invisible required controls.
15711
+ // This also keeps occurredAt editable when hideEventDate is false.
15712
+ if (sections.length > 0) {
15713
+ const sectioned = new Set(sections.flatMap((section) => (section.formFields ?? []).map((field) => field.id)));
15714
+ const extraFields = fields.filter((field) => !sectioned.has(field.id));
15715
+ if (extraFields.length > 0) {
15716
+ sections.push({
15717
+ id: `__event_additional_${stageId}`,
15718
+ name: 'Additional event fields',
15719
+ formFields: extraFields,
15720
+ });
15721
+ }
15722
+ }
15723
+ // Keep stage sections only; never pass registration sections to the EVENT
15724
+ // renderer. The renderer's fallback displays 'fields' when none exist.
15725
+ return {
15726
+ metadata: {
15727
+ ...metadata,
15728
+ formFields: fields,
15729
+ sections: [],
15730
+ programStageSections: sections,
15731
+ },
15732
+ error: null,
15733
+ };
15734
+ }
15735
+ }
15736
+
15358
15737
  class ProgramEntryFormModule {
15359
15738
  constructor() {
15360
15739
  this.config = input.required(/* @ts-ignore */
@@ -15381,8 +15760,21 @@ class ProgramEntryFormModule {
15381
15760
  ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
15382
15761
  this.metaData = signal(null, /* @ts-ignore */
15383
15762
  ...(ngDevMode ? [{ debugName: "metaData" }] : /* istanbul ignore next */ []));
15384
- this.isFormValid = signal(true, /* @ts-ignore */
15763
+ // Select exactly the configured EVENT program stage (or TRACKER registration)
15764
+ // before the FLAT form builds its FormGroup. Keep raw metadata for the SDK.
15765
+ this.activeFormScope = computed(() => {
15766
+ const metadata = this.metaData();
15767
+ if (!metadata)
15768
+ return null;
15769
+ return ProgramEntryFormScopeUtil.resolve(metadata, this.config());
15770
+ }, /* @ts-ignore */
15771
+ ...(ngDevMode ? [{ debugName: "activeFormScope" }] : /* istanbul ignore next */ []));
15772
+ // Do not enable submit before the child form has reported its validity.
15773
+ this.isFormValid = signal(false, /* @ts-ignore */
15385
15774
  ...(ngDevMode ? [{ debugName: "isFormValid" }] : /* istanbul ignore next */ []));
15775
+ // Keep submission state in Angular so React component remounts cannot reset it.
15776
+ this.isSubmitting = signal(false, /* @ts-ignore */
15777
+ ...(ngDevMode ? [{ debugName: "isSubmitting" }] : /* istanbul ignore next */ []));
15386
15778
  this.isFormValid$ = toObservable(this.isFormValid);
15387
15779
  this.instance = signal(null, /* @ts-ignore */
15388
15780
  ...(ngDevMode ? [{ debugName: "instance" }] : /* istanbul ignore next */ []));
@@ -15438,35 +15830,51 @@ class ProgramEntryFormModule {
15438
15830
  }, /* @ts-ignore */
15439
15831
  ...(ngDevMode ? [{ debugName: "dataEntities" }] : /* istanbul ignore next */ []));
15440
15832
  this.FormActionButtons = computed(() => {
15441
- if (this.config().hideActionButtons) {
15833
+ const config = this.config();
15834
+ if (config.hideActionButtons) {
15442
15835
  return React__default.createElement(React__default.Fragment, null);
15443
15836
  }
15444
15837
  return () => {
15445
- const [isValid, setIsValid] = useState();
15446
- const [submitting, setSubmitting] = useState(false);
15447
- useEffect(() => {
15448
- const isValidSubscription = this.isFormValid$.subscribe((isValid) => {
15449
- setIsValid(isValid);
15838
+ // ReactWrapper may mount the supplied function only once. Angular's
15839
+ // computed() changing its return value does not guarantee that the
15840
+ // already-mounted React component will be rendered again.
15841
+ const [isValid, setIsValid] = React__default.useState(() => this.isFormValid());
15842
+ const [submitting, setSubmitting] = React__default.useState(() => this.isSubmitting());
15843
+ React__default.useEffect(() => {
15844
+ // Subscribe to the same event that Angular consumers receive.
15845
+ // It is emitted synchronously by onValidityUpdate(), so the mounted
15846
+ // React button can respond without a ReactWrapper input refresh.
15847
+ const subscription = this.formValidityUpdate.subscribe((valid) => {
15848
+ setIsValid(valid);
15450
15849
  });
15451
- return () => {
15452
- isValidSubscription.unsubscribe();
15453
- };
15850
+ // Catch an update that arrived before React mounted its effect.
15851
+ setIsValid(this.isFormValid());
15852
+ return () => subscription.unsubscribe();
15454
15853
  }, []);
15455
15854
  const onSave = () => {
15855
+ // Do not trust a possibly stale React callback for validation.
15856
+ if (!this.isFormValid() || this.isSubmitting())
15857
+ return;
15456
15858
  setSubmitting(true);
15457
15859
  this.ngZone.run(async () => {
15458
- const saveResponse = await this.#saveInstance();
15459
- this.instanceSaveCompleted.emit(saveResponse);
15460
- setSubmitting(false);
15860
+ this.isSubmitting.set(true);
15861
+ try {
15862
+ const saveResponse = await this.#saveInstance();
15863
+ this.instanceSaveCompleted.emit(saveResponse);
15864
+ }
15865
+ finally {
15866
+ this.isSubmitting.set(false);
15867
+ setSubmitting(false);
15868
+ }
15461
15869
  });
15462
15870
  };
15463
15871
  return (React__default.createElement(ButtonStrip$1, null,
15464
- !(this.config().hideSubmitButton || this.config().disableForm) && (React__default.createElement(Button$1, { primary: true, disabled: !isValid, loading: submitting, onClick: onSave }, this.config().submitButtonLabel)),
15465
- !this.config().hideCancelButton && (React__default.createElement(Button$1, { onClick: () => {
15872
+ !(config.hideSubmitButton || config.disableForm) && (React__default.createElement(Button$1, { primary: true, disabled: !isValid || submitting, loading: submitting, onClick: onSave }, config.submitButtonLabel)),
15873
+ !config.hideCancelButton && (React__default.createElement(Button$1, { onClick: () => {
15466
15874
  this.ngZone.run(() => {
15467
15875
  this.onCancel();
15468
15876
  });
15469
- } }, this.config().cancelButtonLabel))));
15877
+ } }, config.cancelButtonLabel))));
15470
15878
  };
15471
15879
  }, /* @ts-ignore */
15472
15880
  ...(ngDevMode ? [{ debugName: "FormActionButtons" }] : /* istanbul ignore next */ []));
@@ -15765,11 +16173,11 @@ class ProgramEntryFormModule {
15765
16173
  }
15766
16174
  }
15767
16175
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ProgramEntryFormModule, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
15768
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ProgramEntryFormModule, isStandalone: true, selector: "ng-dhis2-ui-program-entry-form", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, trackedEntity: { classPropertyName: "trackedEntity", publicName: "trackedEntity", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, enrollment: { classPropertyName: "enrollment", publicName: "enrollment", isSignal: true, isRequired: false, transformFunction: null }, orgUnit: { classPropertyName: "orgUnit", publicName: "orgUnit", isSignal: true, isRequired: false, transformFunction: null }, codeGeneratorOrgUnit: { classPropertyName: "codeGeneratorOrgUnit", publicName: "codeGeneratorOrgUnit", isSignal: true, isRequired: false, transformFunction: null }, customOrgUnitRoots: { classPropertyName: "customOrgUnitRoots", publicName: "customOrgUnitRoots", isSignal: true, isRequired: false, transformFunction: null }, formMetaData: { classPropertyName: "formMetaData", publicName: "formMetaData", isSignal: true, isRequired: false, transformFunction: null }, collapsibleSections: { classPropertyName: "collapsibleSections", publicName: "collapsibleSections", isSignal: true, isRequired: false, transformFunction: null }, preprocess: { classPropertyName: "preprocess", publicName: "preprocess", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { instanceSave: "instanceSave", instanceSaveCompleted: "instanceSaveCompleted", instanceUpdate: "instanceUpdate", formValidityUpdate: "formValidityUpdate", formCancel: "formCancel" }, ngImport: i0, template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if(metaData()) {\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"metaData()!\"\n [name]=\"metaData()!.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"], dependencies: [{ kind: "ngmodule", type: D2FormModule }, { kind: "component", type: FlatFormComponent, selector: "ng-dhis2-ui-flat-form", inputs: ["collapsibleSections", "programEntryFormConfig", "optionCascadeConfigs"] }, { kind: "directive", type: ReactWrapperModule, selector: "ng-dhis2-ui-wrapper", inputs: ["props", "component"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16176
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ProgramEntryFormModule, isStandalone: true, selector: "ng-dhis2-ui-program-entry-form", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, trackedEntity: { classPropertyName: "trackedEntity", publicName: "trackedEntity", isSignal: true, isRequired: false, transformFunction: null }, event: { classPropertyName: "event", publicName: "event", isSignal: true, isRequired: false, transformFunction: null }, enrollment: { classPropertyName: "enrollment", publicName: "enrollment", isSignal: true, isRequired: false, transformFunction: null }, orgUnit: { classPropertyName: "orgUnit", publicName: "orgUnit", isSignal: true, isRequired: false, transformFunction: null }, codeGeneratorOrgUnit: { classPropertyName: "codeGeneratorOrgUnit", publicName: "codeGeneratorOrgUnit", isSignal: true, isRequired: false, transformFunction: null }, customOrgUnitRoots: { classPropertyName: "customOrgUnitRoots", publicName: "customOrgUnitRoots", isSignal: true, isRequired: false, transformFunction: null }, formMetaData: { classPropertyName: "formMetaData", publicName: "formMetaData", isSignal: true, isRequired: false, transformFunction: null }, collapsibleSections: { classPropertyName: "collapsibleSections", publicName: "collapsibleSections", isSignal: true, isRequired: false, transformFunction: null }, preprocess: { classPropertyName: "preprocess", publicName: "preprocess", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { instanceSave: "instanceSave", instanceSaveCompleted: "instanceSaveCompleted", instanceUpdate: "instanceUpdate", formValidityUpdate: "formValidityUpdate", formCancel: "formCancel" }, ngImport: i0, template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if (activeFormScope()?.error) {\n<div role=\"alert\" class=\"program-entry__form-error\">\n {{ activeFormScope()?.error }}\n</div>\n} @else if (activeFormScope()?.metadata) {\n@let activeMetaData = activeFormScope()!.metadata!;\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"activeMetaData\"\n [name]=\"activeMetaData.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"activeMetaData.rules\"\n [fields]=\"activeMetaData.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"], dependencies: [{ kind: "ngmodule", type: D2FormModule }, { kind: "component", type: FlatFormComponent, selector: "ng-dhis2-ui-flat-form", inputs: ["collapsibleSections", "programEntryFormConfig", "optionCascadeConfigs"] }, { kind: "directive", type: ReactWrapperModule, selector: "ng-dhis2-ui-wrapper", inputs: ["props", "component"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
15769
16177
  }
15770
16178
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ProgramEntryFormModule, decorators: [{
15771
16179
  type: Component,
15772
- args: [{ selector: 'ng-dhis2-ui-program-entry-form', changeDetection: ChangeDetectionStrategy.OnPush, imports: [D2FormModule, ReactWrapperModule], template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if(metaData()) {\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"metaData()!\"\n [name]=\"metaData()!.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"] }]
16180
+ args: [{ selector: 'ng-dhis2-ui-program-entry-form', changeDetection: ChangeDetectionStrategy.OnPush, imports: [D2FormModule, ReactWrapperModule], template: "@if (loading()) {\n<div className=\"program-entry__form-loader\">\n <ng-dhis2-ui-wrapper [component]=\"FormLoader\" />\n</div>\n} @else if (activeFormScope()?.error) {\n<div role=\"alert\" class=\"program-entry__form-error\">\n {{ activeFormScope()?.error }}\n</div>\n} @else if (activeFormScope()?.metadata) {\n@let activeMetaData = activeFormScope()!.metadata!;\n\n<div>\n <!-- Form content -->\n @switch (config().displayType) {\n\n <!-- Flat display -->\n @case ('FLAT') {\n <ng-dhis2-ui-flat-form\n [programEntryFormConfig]=\"config()\"\n [optionCascadeConfigs]=\"config().optionCascadeConfigs || []\"\n [collapsibleSections]=\"collapsibleSections()\"\n [programEntryFormMetaData]=\"activeMetaData\"\n [name]=\"activeMetaData.name\"\n [dataValues]=\"dataEntities()\"\n [dataId]=\"dataId()\"\n [rules]=\"activeMetaData.rules\"\n [fields]=\"activeMetaData.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [coordinatePickerGeoConfig]=\"config().coordinatePickerGeoConfig\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n />\n\n <!-- <ng-dhis2-ui-flat-form\n [name]=\"metaData()!.name\"\n [dataValues]=\"instance() || {}\"\n [dataId]=\"dataId()\"\n [rules]=\"metaData()!.rules\"\n [fields]=\"metaData()!.formFields\"\n [customOrgUnitRoots]=\"customOrgUnitRoots()\"\n [program]=\"config().program\"\n (formUpdate)=\"onFormUpdate($event)\"\n (formValidityUpdate)=\"onValidityUpdate($event)\"\n /> -->\n }\n\n <!-- SECTION display -->\n @case ('SECTION') { Section form }\n\n <!-- CUSTOM FORM -->\n @case ('CUSTOM') { } }\n\n <!-- Action buttons -->\n\n @if(!config().hideActionButtons) {\n <ng-dhis2-ui-wrapper [component]=\"FormActionButtons()\" />\n }\n</div>\n}\n", styles: ["::ng-deep .program-entry__form-loader{padding:8px}\n"] }]
15773
16181
  }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], trackedEntity: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackedEntity", required: false }] }], event: [{ type: i0.Input, args: [{ isSignal: true, alias: "event", required: false }] }], enrollment: [{ type: i0.Input, args: [{ isSignal: true, alias: "enrollment", required: false }] }], orgUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "orgUnit", required: false }] }], codeGeneratorOrgUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "codeGeneratorOrgUnit", required: false }] }], customOrgUnitRoots: [{ type: i0.Input, args: [{ isSignal: true, alias: "customOrgUnitRoots", required: false }] }], formMetaData: [{ type: i0.Input, args: [{ isSignal: true, alias: "formMetaData", required: false }] }], collapsibleSections: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsibleSections", required: false }] }], preprocess: [{ type: i0.Input, args: [{ isSignal: true, alias: "preprocess", required: false }] }], instanceSave: [{
15774
16182
  type: Output
15775
16183
  }], instanceSaveCompleted: [{