@iapps/ng-dhis2-ui 1.1.70 → 1.1.72

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.
@@ -30,10 +30,10 @@ import { isObject, isString, debounce, sortBy, isArray, find, camelCase, flatten
30
30
  import cx from 'classnames';
31
31
  import { getNowInCalendar, generateFixedPeriods } from '@dhis2/multi-calendar-dates';
32
32
  import { switchMap, map as map$1 } from 'rxjs/operators';
33
+ import { parse as parse$1, format } from 'date-fns';
33
34
  import * as i2 from '@angular/forms';
34
35
  import { Validators, UntypedFormControl, UntypedFormGroup, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
35
36
  import { toObservable } from '@angular/core/rxjs-interop';
36
- import { format } from 'date-fns';
37
37
  import { DataQueryFilter, DataFilterCondition, ProgramRuleEngine } from '@iapps/d2-web-sdk';
38
38
  import moment from 'moment';
39
39
 
@@ -3441,6 +3441,11 @@ function buildFilters(filters) {
3441
3441
  .filter(Boolean) // Remove any empty strings
3442
3442
  .join('&');
3443
3443
  }
3444
+ function buildFiltersFromEvents(filters) {
3445
+ return filters
3446
+ .map((filter) => `filter=${filter.programStage}.${filter.dataElement}:${filter.operator}:${filter.value}`)
3447
+ .join('&');
3448
+ }
3444
3449
  const matchesCondition = (eventValue, operator, filterValue) => {
3445
3450
  if (typeof eventValue === 'number' && typeof filterValue === 'number') {
3446
3451
  switch (operator) {
@@ -3526,7 +3531,7 @@ class LineListService {
3526
3531
  getProgramMetadata(programId) {
3527
3532
  return this.httpClient.get(`programs/${programId}.json?fields=programType,programStages[id,name,programStageDataElements[dataElement[id,name,shortName,formName,code,optionSetValue,valueType,
3528
3533
  optionSet[id,name,options[id,name,code]]]]],programTrackedEntityAttributes[*,trackedEntityAttribute[id,name,unique,generated,formName,code,optionSetValue
3529
- ,valueType,optionSet[id,name,options[id,name,code]]]]`);
3534
+ ,valueType,optionSet[id,name,options[id,name,code]]]],orgUnitLabel`);
3530
3535
  }
3531
3536
  // private getTrackedEntityInstances(
3532
3537
  // programId: string,
@@ -3559,8 +3564,9 @@ class LineListService {
3559
3564
  // })
3560
3565
  // );
3561
3566
  // }
3562
- getTrackedEntityInstances(programId, orgUnit, page, pageSize, filters = [], startDate, endDate, ouMode, filterRootOrgUnit, useOuModeWithOlderDHIS2Instance) {
3567
+ getTrackedEntityInstances(programId, orgUnit, page, pageSize, filters = [], startDate, endDate, ouMode, filterRootOrgUnit, useOuModeWithOlderDHIS2Instance, filtersFromEvents = []) {
3563
3568
  const filterParams = buildFilters(filters);
3569
+ const filterParamsFromEvents = buildFiltersFromEvents(filtersFromEvents);
3564
3570
  const dateFilter = [
3565
3571
  startDate ? `enrollmentEnrolledAfter=${startDate}` : '',
3566
3572
  endDate ? `enrollmentEnrolledBefore=${endDate}` : '',
@@ -3571,7 +3577,7 @@ class LineListService {
3571
3577
  ? `&ouMode=${ouMode}`
3572
3578
  : `&orgUnitMode=${ouMode}`;
3573
3579
  return this.httpClient
3574
- .get(`tracker/trackedEntities.json?program=${programId}&orgUnit=${orgUnit}${ouModeIdentifier}&page=${page}&pageSize=${pageSize}&fields=trackedEntity,orgUnit,attributes[*],enrollments[*]&totalPages=true&${filterParams}${dateFilter}&order=createdAt:desc`)
3580
+ .get(`tracker/trackedEntities.json?program=${programId}&orgUnit=${orgUnit}${ouModeIdentifier}&page=${page}&pageSize=${pageSize}&fields=trackedEntity,orgUnit,attributes[*],enrollments[*]&totalPages=true&${filterParams}${filterParamsFromEvents}${dateFilter}&order=createdAt:desc`)
3575
3581
  .pipe(switchMap((response) => {
3576
3582
  if (filterRootOrgUnit) {
3577
3583
  response.trackedEntities = response.trackedEntities.filter((tei) => tei.orgUnit !== orgUnit);
@@ -3640,7 +3646,7 @@ class LineListService {
3640
3646
  const ouModeIdentifier = ouMode ? `&ouMode=${ouMode}` : ``;
3641
3647
  return this.httpClient.get(`events.json?programStage=${programStageId}&orgUnit=${orgUnit}${ouModeIdentifier}&page=${page}&pageSize=${pageSize}&fields=*&totalPages=true&${filterParams}${dateFilter}`);
3642
3648
  }
3643
- getLineListData(programId, orgUnit = '', programStageId, page = 1, pageSize = 10, filters = [], startDate, endDate, ouMode, filterRootOrgUnit, useOuModeWithOlderDHIS2Instance) {
3649
+ getLineListData(programId, orgUnit = '', programStageId, page = 1, pageSize = 10, filters = [], startDate, endDate, ouMode, filterRootOrgUnit, useOuModeWithOlderDHIS2Instance, filtersFromEvents = []) {
3644
3650
  return this.getProgramMetadata(programId).pipe(switchMap((programMetadata) => {
3645
3651
  if (programStageId) {
3646
3652
  return this.getEventsByProgramStage(programStageId, orgUnit, page, pageSize, filters, startDate, endDate, ouMode).pipe(map$1((events) => ({
@@ -3649,7 +3655,7 @@ class LineListService {
3649
3655
  })));
3650
3656
  }
3651
3657
  if (programMetadata.programType === 'WITH_REGISTRATION') {
3652
- return this.getTrackedEntityInstances(programId, orgUnit, page, pageSize, filters, startDate, endDate, ouMode, filterRootOrgUnit, useOuModeWithOlderDHIS2Instance).pipe(map$1((teis) => ({
3658
+ return this.getTrackedEntityInstances(programId, orgUnit, page, pageSize, filters, startDate, endDate, ouMode, filterRootOrgUnit, useOuModeWithOlderDHIS2Instance, filtersFromEvents).pipe(map$1((teis) => ({
3653
3659
  metadata: programMetadata,
3654
3660
  data: teis,
3655
3661
  orgUnits: teis.orgUnitsMap,
@@ -3888,21 +3894,18 @@ const getTrackedEntityData = (response, programId, pager, isOptionSetNameVisible
3888
3894
  response.data?.instances ??
3889
3895
  [];
3890
3896
  const programMetaData = response.metadata.programTrackedEntityAttributes;
3897
+ const orgUnitLabel = response.metadata.orgUnitLabel;
3891
3898
  const orgUnitMap = response.data.orgUnitsMap;
3892
3899
  const mappedProgramMetadataAttributes = programMetaData.map((attr) => ({
3893
3900
  displayInList: attr.displayInList,
3894
3901
  searchable: attr.searchable,
3895
3902
  id: attr.trackedEntityAttribute.id,
3903
+ name: attr.trackedEntityAttribute.name,
3896
3904
  optionSet: attr.trackedEntityAttribute.optionSetValue
3897
3905
  ? attr.trackedEntityAttribute.optionSet
3898
3906
  : null,
3907
+ sortOrder: attr.sortOrder,
3899
3908
  }));
3900
- const programAttributesMap = programMetaData.reduce((acc, attribute) => {
3901
- if (attribute.displayInList) {
3902
- acc[attribute.id] = attribute.displayName;
3903
- }
3904
- return acc;
3905
- }, {});
3906
3909
  if (filters) {
3907
3910
  teis = getFilteredTrackedEntites(teis, filters);
3908
3911
  }
@@ -3928,12 +3931,22 @@ const getTrackedEntityData = (response, programId, pager, isOptionSetNameVisible
3928
3931
  key: attrId,
3929
3932
  };
3930
3933
  });
3931
- entityColumns = entityColumns.filter((column) => {
3932
- return Object.values(programAttributesMap).some((value) => value.includes(column.label));
3934
+ entityColumns = entityColumns
3935
+ .filter((column) => {
3936
+ return mappedProgramMetadataAttributes
3937
+ .filter((attr) => attr.displayInList)
3938
+ .some((attr) => attr.id === column.key); // Ensure the column's key matches an id in the filtered list
3939
+ })
3940
+ .sort((a, b) => {
3941
+ // Ensures sortOrder values are treated as numbers
3942
+ const sortOrderA = Number(mappedProgramMetadataAttributes.find((attr) => attr.id === a.key)
3943
+ ?.sortOrder ?? 0); // Default to 0 if undefined, explicitly cast to number
3944
+ const sortOrderB = Number(mappedProgramMetadataAttributes.find((attr) => attr.id === b.key)
3945
+ ?.sortOrder ?? 0); // Default to 0 if undefined, explicitly cast to number
3946
+ return sortOrderA - sortOrderB; // Ascending order based on sortOrder
3933
3947
  });
3934
- // Add orgUnit as the first column
3935
3948
  entityColumns.unshift({
3936
- label: 'Location',
3949
+ label: orgUnitLabel || 'registering unit',
3937
3950
  key: 'orgUnit',
3938
3951
  });
3939
3952
  const searchableAttributes = mappedProgramMetadataAttributes
@@ -3943,16 +3956,44 @@ const getTrackedEntityData = (response, programId, pager, isOptionSetNameVisible
3943
3956
  }));
3944
3957
  const searchableKeys = new Set(searchableAttributes.map((attr) => attr.key));
3945
3958
  const filteredEntityColumns = entityColumns.filter((column) => searchableKeys.has(column.key));
3959
+ console.log('teis', teis);
3946
3960
  const attributesData = teis.map((tei, idx) => {
3947
3961
  const row = {
3948
3962
  trackedEntityInstance: tei.trackedEntity,
3949
3963
  index: (pager.page - 1) * pager.pageSize + idx + 1,
3950
3964
  };
3951
3965
  allAttributes.forEach((attrId) => (row[attrId] = ''));
3966
+ console.log('all attributes', allAttributes);
3952
3967
  const matchingEnrollment = tei.enrollments.find((enrollment) => enrollment.program === programId);
3953
- const attributesToUse = matchingEnrollment
3968
+ let attributesToUse = matchingEnrollment
3954
3969
  ? matchingEnrollment.attributes
3955
3970
  : tei.attributes;
3971
+ //TODO: find a better way to do optimize this date parsing
3972
+ //map any date that occur in the attributes to dd-mm-yyyy
3973
+ attributesToUse = attributesToUse.map((attr) => {
3974
+ // Check if the attribute is of type DATE
3975
+ if (attr?.valueType === 'DATE' || attr?.valueType === 'date') {
3976
+ try {
3977
+ // Parse the date and format it as dd-MM-yyyy
3978
+ const parsedDate = parse$1(attr.value, 'yyyy-MM-dd', new Date());
3979
+ const formattedDate = format(parsedDate, 'dd-MM-yyyy');
3980
+ // Update the value to the formatted date
3981
+ return {
3982
+ ...attr,
3983
+ value: formattedDate,
3984
+ };
3985
+ }
3986
+ catch {
3987
+ // If parsing fails, keep the original value
3988
+ return {
3989
+ ...attr,
3990
+ value: attr.value,
3991
+ };
3992
+ }
3993
+ }
3994
+ // If not a date, just return the attribute as is
3995
+ return attr;
3996
+ });
3956
3997
  // If option set names are visible, transform the attribute values
3957
3998
  if (isOptionSetNameVisible) {
3958
3999
  attributesToUse.forEach((attr) => {
@@ -3976,10 +4017,27 @@ const getTrackedEntityData = (response, programId, pager, isOptionSetNameVisible
3976
4017
  row['orgUnit'] = orgUnitMap?.get(matchingEnrollment.orgUnit) || 'N/A';
3977
4018
  return row;
3978
4019
  });
4020
+ console.log('entity columns', entityColumns);
4021
+ if (teis.length === 0) {
4022
+ entityColumns = [
4023
+ { label: orgUnitLabel || 'registering unit', key: 'orgUnit' },
4024
+ ...mappedProgramMetadataAttributes
4025
+ .filter((attr) => attr.displayInList === true)
4026
+ .sort((a, b) => {
4027
+ return Number(a.sortOrder) - Number(b.sortOrder);
4028
+ })
4029
+ .map((attr) => ({
4030
+ key: attr.id,
4031
+ label: attr.name,
4032
+ })),
4033
+ ];
4034
+ }
4035
+ console.log('filtered entity columns', filteredEntityColumns);
3979
4036
  return {
3980
4037
  columns: entityColumns,
3981
4038
  data: attributesData,
3982
4039
  filteredEntityColumns: filteredEntityColumns,
4040
+ orgUnitLabel: orgUnitLabel,
3983
4041
  };
3984
4042
  };
3985
4043
  // export const getEventData = (
@@ -4042,7 +4100,8 @@ const getEventData = (response, pager, isOptionSetNameVisible) => {
4042
4100
  if (isOptionSetNameVisible && dv.optionSetValue) {
4043
4101
  const dataElementMetadata = response.metadata.programStages
4044
4102
  .flatMap((programStage) => programStage.programStageDataElements)
4045
- .find((psde) => psde.dataElement.id === dv.dataElement)?.dataElement.optionSet;
4103
+ .find((psde) => psde.dataElement.id === dv.dataElement)
4104
+ ?.dataElement.optionSet;
4046
4105
  if (dataElementMetadata && dataElementMetadata.options) {
4047
4106
  const matchingOption = dataElementMetadata.options.find((option) => option.code === dv.value);
4048
4107
  if (matchingOption) {
@@ -4114,15 +4173,18 @@ class LineListTableComponent extends ReactWrapperModule {
4114
4173
  const [filteredColumns, setFilteredColumns] = useState();
4115
4174
  const [inputValues, setInputValues] = useState({});
4116
4175
  const [filters, setFilters] = useState(false);
4176
+ const [orgUnitLabel, setOrgUnitLabel] = useState('');
4117
4177
  //TODO: this will be migrated on the parent component
4118
4178
  const [checkValue, setCheckValue] = useState([]);
4119
4179
  const [valueMatch, setValuesMatch] = useState(false);
4120
- const [selectedOrgUnit, setSelectedOrgUnit] = useState(''); // for testing purposes to be removed
4180
+ const [selectedOrgUnit, setSelectedOrgUnit] = useState('');
4121
4181
  const [hide, setHide] = useState(true);
4122
4182
  const [showAllFilters, setShowAllFilters] = useState(false);
4123
4183
  const visibleFilters = showAllFilters
4124
4184
  ? filteredColumns ?? []
4125
4185
  : (filteredColumns ?? []).slice(0, 2);
4186
+ const defaultOrgUnit = this.orgUnit;
4187
+ const [prevValue, setPrevValue] = useState();
4126
4188
  // Store updaters in refs for Angular to access
4127
4189
  const updateRefs = useRef({
4128
4190
  setProgramIdState,
@@ -4140,7 +4202,7 @@ class LineListTableComponent extends ReactWrapperModule {
4140
4202
  useEffect(() => {
4141
4203
  setLoading(true);
4142
4204
  const subscription = this.lineListService
4143
- .getLineListData(this.programId, orgUnitState, this.programStageId, pager.page, pager.pageSize, attributeFiltersState, startDateState, endDateState, this.ouMode, this.filterRootOrgUnit, this.useOuModeWithOlderDHIS2Instance)
4205
+ .getLineListData(this.programId, orgUnitState, this.programStageId, pager.page, pager.pageSize, attributeFiltersState, startDateState, endDateState, this.ouMode, this.filterRootOrgUnit, this.useOuModeWithOlderDHIS2Instance, this.filters)
4144
4206
  .pipe(take(1)) // Automatically unsubscribe after the first emission
4145
4207
  .subscribe((response) => {
4146
4208
  let entityColumns = [];
@@ -4162,9 +4224,10 @@ class LineListTableComponent extends ReactWrapperModule {
4162
4224
  total: responseData.total,
4163
4225
  pageCount: responseData.pageCount,
4164
4226
  };
4165
- const { columns, data, filteredEntityColumns } = getTrackedEntityData(response, this.programId, pager, this.isOptionSetNameVisible, this.filters);
4227
+ const { columns, data, filteredEntityColumns, orgUnitLabel } = getTrackedEntityData(response, this.programId, pager, this.isOptionSetNameVisible, this.filters);
4166
4228
  entityColumns = columns;
4167
4229
  entityData = data;
4230
+ setOrgUnitLabel(orgUnitLabel);
4168
4231
  const checkValues = [
4169
4232
  ...new Set(responseData?.trackedEntities?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4170
4233
  .filter((dataValue) => /^[A-Za-z]{3}$/.test(dataValue.value))
@@ -4296,7 +4359,11 @@ class LineListTableComponent extends ReactWrapperModule {
4296
4359
  gap: '10px',
4297
4360
  flexWrap: 'wrap',
4298
4361
  } },
4299
- React__default.createElement(InputField$1, { key: "location", label: "Registraring unit:", value: selectedOrgUnit, clearable: true, onFocus: () => setHide(false), className: "custom-input" }),
4362
+ React__default.createElement(InputField$1, { key: "location", label: orgUnitLabel, value: selectedOrgUnit, clearable: true, onFocus: () => setHide(false), className: "custom-input", onChange: (event) => {
4363
+ console.log('onChange triggered with value:', event.value);
4364
+ setSelectedOrgUnit(event.value);
4365
+ setOrgUnitState(defaultOrgUnit);
4366
+ } }),
4300
4367
  React__default.createElement(CalendarInput, { label: "Start date:", calendar: "gregory", locale: "en-GB", timeZone: "Africa/Dar_es_Salaam", className: "custom-input", date: startDateState, onDateSelect: (selectedDate) => {
4301
4368
  setStartDateState(selectedDate.calendarDateString);
4302
4369
  } }),
@@ -4304,20 +4371,41 @@ class LineListTableComponent extends ReactWrapperModule {
4304
4371
  setEndDateState(selectedDate.calendarDateString);
4305
4372
  } }),
4306
4373
  (visibleFilters ?? []).map(({ label, key }) => (React__default.createElement(InputField$1, { key: key, label: `${label}:`, className: "custom-input", value: inputValues[key] || '', clearable: true, onChange: (e) => {
4374
+ let currentValue = '';
4307
4375
  if ('target' in e && e.target) {
4308
- setInputValues((prevValues) => ({
4309
- ...prevValues,
4310
- [key]: e
4311
- .target.value,
4312
- }));
4376
+ currentValue = e.target.value;
4313
4377
  }
4314
4378
  else if ('value' in e) {
4315
- setInputValues((prevValues) => ({
4316
- ...prevValues,
4317
- [key]: e.value ?? '',
4318
- }));
4379
+ currentValue = e.value ?? '';
4319
4380
  }
4320
- }, onBlur: (e) => {
4381
+ setInputValues((prevValues) => ({
4382
+ ...prevValues,
4383
+ [key]: currentValue,
4384
+ }));
4385
+ // Trigger change immediately if the input is cleared
4386
+ if (currentValue === '') {
4387
+ handleInputChange(key, '');
4388
+ }
4389
+ },
4390
+ // onChange={(
4391
+ // e:
4392
+ // | React.ChangeEvent<HTMLInputElement>
4393
+ // | { value?: string }
4394
+ // ) => {
4395
+ // if ('target' in e && e.target) {
4396
+ // setInputValues((prevValues) => ({
4397
+ // ...prevValues,
4398
+ // [key]: (e as React.ChangeEvent<HTMLInputElement>)
4399
+ // .target.value,
4400
+ // }));
4401
+ // } else if ('value' in e) {
4402
+ // setInputValues((prevValues) => ({
4403
+ // ...prevValues,
4404
+ // [key]: e.value ?? '',
4405
+ // }));
4406
+ // }
4407
+ // }}
4408
+ onBlur: (e) => {
4321
4409
  let currentValue = '';
4322
4410
  if ('target' in e && e.target) {
4323
4411
  currentValue = e.target.value;
@@ -4325,7 +4413,10 @@ class LineListTableComponent extends ReactWrapperModule {
4325
4413
  else if ('value' in e) {
4326
4414
  currentValue = e.value ?? '';
4327
4415
  }
4328
- handleInputChange(key, currentValue);
4416
+ if (currentValue !== '' && currentValue !== prevValue) {
4417
+ handleInputChange(key, currentValue);
4418
+ setPrevValue(currentValue);
4419
+ }
4329
4420
  } }))),
4330
4421
  React__default.createElement(Button$1, { onClick: () => setShowAllFilters(!showAllFilters) }, showAllFilters ? 'Less Filters' : 'More Filters')))),
4331
4422
  React__default.createElement(DataTable, null,