@iapps/ng-dhis2-ui 1.1.67 → 1.1.69

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.
@@ -8,7 +8,7 @@ import { NgxDhis2HttpClientService } from '@iapps/ngx-dhis2-http-client';
8
8
  import * as React from 'react';
9
9
  import React__default, { useState, useCallback, useRef, useEffect, createContext, useContext, useMemo } from 'react';
10
10
  import * as ReactDOM from 'react-dom/client';
11
- import { catchError, of, map, zip, firstValueFrom, filter, Subject, takeUntil } from 'rxjs';
11
+ import { catchError, of, map, zip, firstValueFrom, take, filter, Subject, takeUntil } from 'rxjs';
12
12
  import i18n from '@dhis2/d2-i18n';
13
13
  import { colors, theme, spacers } from '@dhis2/ui-constants';
14
14
  import PropTypes from 'prop-types';
@@ -3446,31 +3446,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.8", ngImpor
3446
3446
  type: Injectable
3447
3447
  }], ctorParameters: () => [{ type: i1.NgxDhis2HttpClientService }] });
3448
3448
 
3449
- const getProgramStageData = (response, programStageId, pager) => {
3450
- const events = response.data.events;
3451
- const allDataElements = new Set();
3452
- events.forEach((event) => {
3453
- event.dataValues.forEach((dv) => allDataElements.add(dv.dataElement));
3454
- });
3455
- const stageFromMetaData = response.metadata.programStages.find((stage) => stage.id === programStageId);
3456
- if (!stageFromMetaData) {
3457
- throw new Error(`Program stage with ID ${programStageId} not found`);
3458
- }
3459
- const entityColumns = Array.from(allDataElements).map((dataElementId) => ({
3460
- label: stageFromMetaData.programStageDataElements.find((psde) => psde.dataElement.id === dataElementId)?.dataElement.name || dataElementId,
3461
- key: dataElementId,
3462
- }));
3463
- const dataElementsData = events.map((event, idx) => {
3464
- const row = {
3465
- event: event.event,
3466
- index: (pager.page - 1) * pager.pageSize + idx + 1,
3467
- };
3468
- allDataElements.forEach((dataElementId) => (row[dataElementId] = ''));
3469
- event.dataValues.forEach((dv) => (row[dv.dataElement] = dv.value));
3470
- return row;
3471
- });
3472
- return { columns: entityColumns, data: dataElementsData };
3473
- };
3449
+ // export const getProgramStageData = (
3450
+ // response: LineListResponse,
3451
+ // programStageId: string,
3452
+ // pager: any,
3453
+ // isOptionSetNameVisible: boolean
3454
+ // ): { columns: ColumnDefinition[]; data: TableRow[] } => {
3455
+ // const events = (response.data as EventsResponse).events;
3456
+ // const allDataElements = new Set<string>();
3457
+ // events.forEach((event: any) => {
3458
+ // event.dataValues.forEach((dv: any) => allDataElements.add(dv.dataElement));
3459
+ // });
3460
+ // const stageFromMetaData = response.metadata.programStages.find(
3461
+ // (stage: any) => stage.id === programStageId
3462
+ // );
3463
+ // if (!stageFromMetaData) {
3464
+ // throw new Error(`Program stage with ID ${programStageId} not found`);
3465
+ // }
3466
+ // const entityColumns = Array.from(allDataElements).map(
3467
+ // (dataElementId: string) => ({
3468
+ // label:
3469
+ // stageFromMetaData.programStageDataElements.find(
3470
+ // (psde: any) => psde.dataElement.id === dataElementId
3471
+ // )?.dataElement.name || dataElementId,
3472
+ // key: dataElementId,
3473
+ // })
3474
+ // );
3475
+ // const dataElementsData: TableRow[] = events.map((event: any, idx: number) => {
3476
+ // const row: TableRow = {
3477
+ // event: event.event,
3478
+ // index: (pager.page - 1) * pager.pageSize + idx + 1,
3479
+ // };
3480
+ // allDataElements.forEach(
3481
+ // (dataElementId: string) => (row[dataElementId] = '')
3482
+ // );
3483
+ // if (isOptionSetNameVisible) {
3484
+ // event.dataValues.forEach((dv: any) => (row[dv.dataElement] = dv.value));
3485
+ // } else {
3486
+ // event.dataValues.forEach((dv: any) => (row[dv.dataElement] = dv.value));
3487
+ // }
3488
+ // return row;
3489
+ // });
3490
+ // return { columns: entityColumns, data: dataElementsData };
3491
+ // };
3474
3492
  // export const getTrackedEntityData = (
3475
3493
  // response: LineListResponse,
3476
3494
  // programId: string,
@@ -3603,6 +3621,44 @@ const getProgramStageData = (response, programStageId, pager) => {
3603
3621
  // filteredEntityColumns: filteredEntityColumns,
3604
3622
  // };
3605
3623
  // };
3624
+ const getProgramStageData = (response, programStageId, pager, isOptionSetNameVisible) => {
3625
+ const events = response.data.events;
3626
+ const allDataElements = new Set();
3627
+ events.forEach((event) => {
3628
+ event.dataValues.forEach((dv) => allDataElements.add(dv.dataElement));
3629
+ });
3630
+ const stageFromMetaData = response.metadata.programStages.find((stage) => stage.id === programStageId);
3631
+ if (!stageFromMetaData) {
3632
+ throw new Error(`Program stage with ID ${programStageId} not found`);
3633
+ }
3634
+ const entityColumns = Array.from(allDataElements).map((dataElementId) => {
3635
+ const dataElementMeta = stageFromMetaData.programStageDataElements.find((psde) => psde.dataElement.id === dataElementId);
3636
+ return {
3637
+ label: dataElementMeta?.dataElement.name || dataElementId,
3638
+ key: dataElementId,
3639
+ };
3640
+ });
3641
+ const dataElementsData = events.map((event, idx) => {
3642
+ const row = {
3643
+ event: event.event,
3644
+ index: (pager.page - 1) * pager.pageSize + idx + 1,
3645
+ };
3646
+ allDataElements.forEach((dataElementId) => (row[dataElementId] = ''));
3647
+ event.dataValues.forEach((dv) => {
3648
+ const dataElementMeta = stageFromMetaData.programStageDataElements.find((psde) => psde.dataElement.id === dv.dataElement);
3649
+ if (isOptionSetNameVisible && dataElementMeta?.dataElement.optionSet) {
3650
+ const optionSet = dataElementMeta.dataElement.optionSet;
3651
+ const matchingOption = optionSet.options.find((option) => option.code === dv.value);
3652
+ row[dv.dataElement] = matchingOption ? matchingOption.name : dv.value;
3653
+ }
3654
+ else {
3655
+ row[dv.dataElement] = dv.value;
3656
+ }
3657
+ });
3658
+ return row;
3659
+ });
3660
+ return { columns: entityColumns, data: dataElementsData };
3661
+ };
3606
3662
  const getTrackedEntityData = (response, programId, pager, isOptionSetNameVisible, filters) => {
3607
3663
  let teis = response.data?.trackedEntities ??
3608
3664
  response.data?.instances ??
@@ -4040,10 +4096,132 @@ class LineListTableComponent extends ReactWrapperModule {
4040
4096
  useEffect(() => {
4041
4097
  this.setReactStateUpdaters?.(updateRefs.current);
4042
4098
  }, []);
4099
+ // TODO: START: Improve the approach of handling observables
4100
+ // useEffect(() => {
4101
+ // setLoading(true);
4102
+ // this.lineListService
4103
+ // .getLineListData(
4104
+ // this.programId,
4105
+ // orgUnitState,
4106
+ // this.programStageId,
4107
+ // pager.page,
4108
+ // pager.pageSize,
4109
+ // attributeFiltersState,
4110
+ // startDateState,
4111
+ // endDateState,
4112
+ // this.ouMode,
4113
+ // this.filterRootOrgUnit,
4114
+ // this.useOuModeWithOlderDHIS2Instance
4115
+ // )
4116
+ // .subscribe((response: LineListResponse) => {
4117
+ // let entityColumns: ColumnDefinition[] = [];
4118
+ // let responsePager: Pager;
4119
+ // let entityData: TableRow[] = [];
4120
+ // let filteredDataColumns: ColumnDefinition[] = [];
4121
+ // if (this.programStageId) {
4122
+ // responsePager = (response.data as EventsResponse).pager;
4123
+ // const { columns, data } = getProgramStageData(
4124
+ // response,
4125
+ // this.programStageId,
4126
+ // pager,
4127
+ // this.isOptionSetNameVisible
4128
+ // );
4129
+ // entityColumns = columns;
4130
+ // entityData = data;
4131
+ // } else if (
4132
+ // 'trackedEntities' in response.data ||
4133
+ // 'instances' in response.data
4134
+ // ) {
4135
+ // const responseData: any = response.data as TrackedEntityResponse;
4136
+ // responsePager = (response.data as TrackedEntityResponse).pager || {
4137
+ // page: responseData.page,
4138
+ // pageSize: responseData.pageSize,
4139
+ // total: responseData.total,
4140
+ // pageCount: responseData.pageCount,
4141
+ // };
4142
+ // const { columns, data, filteredEntityColumns } =
4143
+ // getTrackedEntityData(
4144
+ // response,
4145
+ // this.programId,
4146
+ // pager,
4147
+ // this.isOptionSetNameVisible,
4148
+ // this.filters
4149
+ // );
4150
+ // entityColumns = columns;
4151
+ // entityData = data;
4152
+ // //TODO: Should be done on the parent
4153
+ // const checkValues = [
4154
+ // ...new Set(
4155
+ // (
4156
+ // response.data as TrackedEntityResponse
4157
+ // )?.trackedEntities?.[0]?.enrollments?.[0]?.events?.flatMap(
4158
+ // (event) =>
4159
+ // event.dataValues
4160
+ // .filter((dataValue) =>
4161
+ // /^[A-Za-z]{3}$/.test(dataValue.value)
4162
+ // )
4163
+ // .map((dataValue) => dataValue.value)
4164
+ // ) ??
4165
+ // (
4166
+ // response.data as TrackedEntityResponse
4167
+ // )?.instances?.[0]?.enrollments?.[0]?.events?.flatMap(
4168
+ // (event) =>
4169
+ // event.dataValues
4170
+ // .filter((dataValue) =>
4171
+ // /^[A-Za-z]{3}$/.test(dataValue.value)
4172
+ // )
4173
+ // .map((dataValue) => dataValue.value)
4174
+ // )
4175
+ // ),
4176
+ // ];
4177
+ // if (checkValues) {
4178
+ // // this.firstValue.emit(firstTei);
4179
+ // setCheckValue(checkValues);
4180
+ // setValuesMatch(!checkValues.includes(this.buttonFilter));
4181
+ // }
4182
+ // // setFilteredColumns(filteredEntityColumns);
4183
+ // // Ensure filter inputs do not disappear when no data is returned
4184
+ // // If filteredEntityColumns is empty, keep the previous columns instead of clearing them
4185
+ // setFilteredColumns((prev) =>
4186
+ // filteredEntityColumns.length > 0 ? filteredEntityColumns : prev
4187
+ // );
4188
+ // filteredDataColumns = filteredEntityColumns;
4189
+ // } else {
4190
+ // responsePager = (response.data as EventsResponse).pager;
4191
+ // const { columns, data } = getEventData(
4192
+ // response,
4193
+ // pager,
4194
+ // this.isOptionSetNameVisible
4195
+ // );
4196
+ // entityColumns = columns;
4197
+ // entityData = data;
4198
+ // }
4199
+ // const finalColumns: ColumnDefinition[] = addActionsColumn(
4200
+ // [{ label: '#', key: 'index' }, ...entityColumns],
4201
+ // this.actionOptions
4202
+ // );
4203
+ // setLoading(false);
4204
+ // setColumns(...[finalColumns]);
4205
+ // setData(...[entityData]);
4206
+ // setPager(...[responsePager]);
4207
+ // });
4208
+ // }, [
4209
+ // programIdState,
4210
+ // orgUnitState,
4211
+ // programStageIdState,
4212
+ // attributeFiltersState,
4213
+ // startDateState,
4214
+ // endDateState,
4215
+ // pager.page,
4216
+ // pager.pageSize,
4217
+ // isOptionSetNameVisibleState,
4218
+ // ]);
4219
+ // TODO: END: Improve the approach of handling observables
4043
4220
  useEffect(() => {
4044
4221
  setLoading(true);
4045
- this.lineListService
4222
+ const subscription = this.lineListService
4046
4223
  .getLineListData(this.programId, orgUnitState, this.programStageId, pager.page, pager.pageSize, attributeFiltersState, startDateState, endDateState, this.ouMode, this.filterRootOrgUnit, this.useOuModeWithOlderDHIS2Instance)
4224
+ .pipe(take(1)) // Automatically unsubscribe after the first emission
4047
4225
  .subscribe((response) => {
4048
4226
  let entityColumns = [];
4049
4227
  let responsePager;
@@ -4051,14 +4229,14 @@ class LineListTableComponent extends ReactWrapperModule {
4051
4229
  let filteredDataColumns = [];
4052
4230
  if (this.programStageId) {
4053
4231
  responsePager = response.data.pager;
4054
- const { columns, data } = getProgramStageData(response, this.programStageId, pager);
4232
+ const { columns, data } = getProgramStageData(response, this.programStageId, pager, this.isOptionSetNameVisible);
4055
4233
  entityColumns = columns;
4056
4234
  entityData = data;
4057
4235
  }
4058
- else if ('trackedEntities' in response.data ||
4059
- 'instances' in response.data) {
4236
+ else if ("trackedEntities" in response.data ||
4237
+ "instances" in response.data) {
4060
4238
  const responseData = response.data;
4061
- responsePager = response.data.pager || {
4239
+ responsePager = responseData.pager || {
4062
4240
  page: responseData.page,
4063
4241
  pageSize: responseData.pageSize,
4064
4242
  total: responseData.total,
@@ -4067,23 +4245,18 @@ class LineListTableComponent extends ReactWrapperModule {
4067
4245
  const { columns, data, filteredEntityColumns } = getTrackedEntityData(response, this.programId, pager, this.isOptionSetNameVisible, this.filters);
4068
4246
  entityColumns = columns;
4069
4247
  entityData = data;
4070
- //TODO: Should be done on the parent
4071
4248
  const checkValues = [
4072
- ...new Set(response.data?.trackedEntities?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4249
+ ...new Set(responseData?.trackedEntities?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4073
4250
  .filter((dataValue) => /^[A-Za-z]{3}$/.test(dataValue.value))
4074
4251
  .map((dataValue) => dataValue.value)) ??
4075
- response.data?.instances?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4252
+ responseData?.instances?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4076
4253
  .filter((dataValue) => /^[A-Za-z]{3}$/.test(dataValue.value))
4077
4254
  .map((dataValue) => dataValue.value))),
4078
4255
  ];
4079
- if (checkValues) {
4080
- // this.firstValue.emit(firstTei);
4256
+ if (checkValues.length > 0) {
4081
4257
  setCheckValue(checkValues);
4082
4258
  setValuesMatch(!checkValues.includes(this.buttonFilter));
4083
4259
  }
4084
- // setFilteredColumns(filteredEntityColumns);
4085
- // Ensure filter inputs do not disappear when no data is returned
4086
- // If filteredEntityColumns is empty, keep the previous columns instead of clearing them
4087
4260
  setFilteredColumns((prev) => filteredEntityColumns.length > 0 ? filteredEntityColumns : prev);
4088
4261
  filteredDataColumns = filteredEntityColumns;
4089
4262
  }
@@ -4093,12 +4266,15 @@ class LineListTableComponent extends ReactWrapperModule {
4093
4266
  entityColumns = columns;
4094
4267
  entityData = data;
4095
4268
  }
4096
- const finalColumns = addActionsColumn([{ label: '#', key: 'index' }, ...entityColumns], this.actionOptions);
4269
+ const finalColumns = addActionsColumn([{ label: "#", key: "index" }, ...entityColumns], this.actionOptions);
4097
4270
  setLoading(false);
4098
- setColumns(...[finalColumns]);
4099
- setData(...[entityData]);
4100
- setPager(...[responsePager]);
4271
+ setColumns(finalColumns);
4272
+ setData(entityData);
4273
+ setPager(responsePager);
4101
4274
  });
4275
+ return () => {
4276
+ subscription.unsubscribe(); // Ensure cleanup on unmount
4277
+ };
4102
4278
  }, [
4103
4279
  programIdState,
4104
4280
  orgUnitState,
@@ -4119,20 +4295,64 @@ class LineListTableComponent extends ReactWrapperModule {
4119
4295
  },
4120
4296
  }));
4121
4297
  };
4298
+ // TODO: START::: Improve the approach on handling observables
4299
+ // const handleApprovalClick = () => {
4300
+ // setIsButtonLoading(true);
4301
+ // this.lineListService
4302
+ // .getLineListData(
4303
+ // this.programId,
4304
+ // this.orgUnit,
4305
+ // this.programStageId,
4306
+ // pager.page,
4307
+ // 1000,
4308
+ // this.attributeFilters,
4309
+ // this.startDate,
4310
+ // this.endDate,
4311
+ // this.ouMode
4312
+ // )
4313
+ // .subscribe((response: LineListResponse) => {
4314
+ // if (
4315
+ // 'trackedEntities' in response.data ||
4316
+ // 'instances' in response.data
4317
+ // ) {
4318
+ // const trackedEntityInstances = (
4319
+ // response.data as TrackedEntityResponse
4320
+ // ).trackedEntities;
4321
+ // // Map TEIs to objects with teiId and enrollmentId
4322
+ // const teiEnrollmentList = trackedEntityInstances
4323
+ // .map((tei) => {
4324
+ // // Find the enrollment where program matches this.programId
4325
+ // const matchingEnrollment = tei.enrollments?.find(
4326
+ // (enrollment) => enrollment.program === this.programId
4327
+ // );
4328
+ // // Return object with teiId and enrollmentId (if found)
4329
+ // return matchingEnrollment
4330
+ // ? {
4331
+ // teiId: tei.trackedEntity,
4332
+ // enrollmentId: matchingEnrollment.enrollment,
4333
+ // }
4334
+ // : null;
4335
+ // })
4336
+ // .filter((item) => item !== null); // Remove null entries (no matching enrollment)
4337
+ // this.approvalSelected.emit(teiEnrollmentList);
4338
+ // } else {
4339
+ // this.approvalSelected.emit([]); // Emit empty array if no TEIs
4340
+ // }
4341
+ // });
4342
+ // };
4343
+ // TODO: END::: Improve the approach on handling observables
4122
4344
  const handleApprovalClick = () => {
4123
4345
  setIsButtonLoading(true);
4124
- this.lineListService
4346
+ const subscription = this.lineListService
4125
4347
  .getLineListData(this.programId, this.orgUnit, this.programStageId, pager.page, 1000, this.attributeFilters, this.startDate, this.endDate, this.ouMode)
4348
+ .pipe(take(1)) // Ensures the observable emits only once
4126
4349
  .subscribe((response) => {
4127
- if ('trackedEntities' in response.data ||
4128
- 'instances' in response.data) {
4350
+ if ("trackedEntities" in response.data || "instances" in response.data) {
4129
4351
  const trackedEntityInstances = response.data.trackedEntities;
4130
4352
  // Map TEIs to objects with teiId and enrollmentId
4131
4353
  const teiEnrollmentList = trackedEntityInstances
4132
4354
  .map((tei) => {
4133
- // Find the enrollment where program matches this.programId
4134
4355
  const matchingEnrollment = tei.enrollments?.find((enrollment) => enrollment.program === this.programId);
4135
- // Return object with teiId and enrollmentId (if found)
4136
4356
  return matchingEnrollment
4137
4357
  ? {
4138
4358
  teiId: tei.trackedEntity,
@@ -4140,13 +4360,17 @@ class LineListTableComponent extends ReactWrapperModule {
4140
4360
  }
4141
4361
  : null;
4142
4362
  })
4143
- .filter((item) => item !== null); // Remove null entries (no matching enrollment)
4363
+ .filter(Boolean); // Removes null entries
4144
4364
  this.approvalSelected.emit(teiEnrollmentList);
4145
4365
  }
4146
4366
  else {
4147
4367
  this.approvalSelected.emit([]); // Emit empty array if no TEIs
4148
4368
  }
4369
+ setIsButtonLoading(false); // Stop loading after processing
4149
4370
  });
4371
+ return () => {
4372
+ subscription.unsubscribe(); // Cleanup to avoid potential memory leaks
4373
+ };
4150
4374
  };
4151
4375
  const handleInputChange = (key, value) => {
4152
4376
  setInputValues((prevValues) => ({
@@ -5919,9 +6143,24 @@ class BaseFormFieldComponent extends ReactWrapperModule {
5919
6143
  checkValueUniqueness();
5920
6144
  }
5921
6145
  }, [this.isValueAssigned()]);
6146
+ // TODO: START: Improve the approach of handling observables
6147
+ // useEffect(() => {
6148
+ // const fieldErrorSubscription = this.fieldError$
6149
+ // .pipe(filter((error) => error !== initialError))
6150
+ // .subscribe({
6151
+ // next: (error: string | undefined) => {
6152
+ // setInitialError(error);
6153
+ // },
6154
+ // });
6155
+ // return () => {
6156
+ // fieldErrorSubscription.unsubscribe();
6157
+ // };
6158
+ // });
6159
+ // TODO: END: Improve the approach of handling observables
5922
6160
  useEffect(() => {
5923
6161
  const fieldErrorSubscription = this.fieldError$
5924
- .pipe(filter((error) => error !== initialError))
6162
+ .pipe(filter((error) => error !== initialError), take(1) // Take only the first emitted value, then unsubscribe
6163
+ )
5925
6164
  .subscribe({
5926
6165
  next: (error) => {
5927
6166
  setInitialError(error);
@@ -5930,7 +6169,7 @@ class BaseFormFieldComponent extends ReactWrapperModule {
5930
6169
  return () => {
5931
6170
  fieldErrorSubscription.unsubscribe();
5932
6171
  };
5933
- });
6172
+ }, [initialError]); // Add initialError as a dependency to avoid stale closures
5934
6173
  const onChange = (payload) => setSelected(payload.selected);
5935
6174
  const arrayValue = useMemo(() => {
5936
6175
  if (value && value.length > 0) {
@@ -6817,14 +7056,30 @@ class SelectionFiltersComponent extends ReactWrapperModule {
6817
7056
  const [selected, setSelected] = useState([]);
6818
7057
  const [rootOrgUnits, setRootOrgUnits] = useState();
6819
7058
  const [config, setConfig] = useState();
7059
+ // TODO: START::: Improve approach on handling observables
7060
+ // useEffect(() => {
7061
+ // zip(this.getAppConfig(), this.getRootOrgUnits()).subscribe({
7062
+ // next: ([appConfig, rootOrgUnits]) => {
7063
+ // setConfig(appConfig);
7064
+ // setRootOrgUnits(rootOrgUnits);
7065
+ // },
7066
+ // error: (error) => console.error(error),
7067
+ // });
7068
+ // }, []);
7069
+ // TODO: END::: Improve approach on handling observables
6820
7070
  useEffect(() => {
6821
- zip(this.getAppConfig(), this.getRootOrgUnits()).subscribe({
7071
+ const subscription = zip(this.getAppConfig(), this.getRootOrgUnits())
7072
+ .pipe(take(1)) // Ensures only one emission
7073
+ .subscribe({
6822
7074
  next: ([appConfig, rootOrgUnits]) => {
6823
7075
  setConfig(appConfig);
6824
7076
  setRootOrgUnits(rootOrgUnits);
6825
7077
  },
6826
7078
  error: (error) => console.error(error),
6827
7079
  });
7080
+ return () => {
7081
+ subscription.unsubscribe(); // Cleanup to avoid memory leaks
7082
+ };
6828
7083
  }, []);
6829
7084
  return config ? (React__default.createElement(Provider, { config: config, plugin: false, parentAlertsAdd: undefined, showAlertsInPlugin: false }, React__default.createElement(Modal$1, { position: "middle", large: true },
6830
7085
  React__default.createElement(ModalTitle$1, null, "Organisation unit"),