@iapps/ng-dhis2-ui 1.1.68 → 1.1.70

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';
@@ -3195,13 +3195,237 @@ const DropdownMenu$1 = (props) => {
3195
3195
  const ref = useRef();
3196
3196
  const [visible, setVisible] = useState(false);
3197
3197
  const { dropdownOptions, onClick } = props;
3198
- return (React__default.createElement("div", { ref: ref, style: { display: "inline-block" } },
3198
+ return (React__default.createElement("div", { ref: ref, style: { display: 'inline-block' } },
3199
3199
  React__default.createElement(Button$1, { small: true, secondary: true, icon: React__default.createElement(IconMore24, { color: colors$1.grey600 }), onClick: () => setVisible(true) }),
3200
3200
  visible && (React__default.createElement(ContextMenu$1, { dropDownOptions: dropdownOptions, anchorRef: ref, onClose: () => setVisible(false), onMenuClick: (dropDownOption) => {
3201
3201
  onClick(dropDownOption);
3202
3202
  } }))));
3203
3203
  };
3204
3204
 
3205
+ // Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
3206
+ // Use of this source code is governed by a BSD-style
3207
+ // license that can be found in the LICENSE file.
3208
+ const DataTableActions = (props) => {
3209
+ const { actionOptions, actionOptionOrientation, onClick } = props;
3210
+ switch (actionOptionOrientation) {
3211
+ case 'BUTTON':
3212
+ return (React__default.createElement(ButtonStrip$1, null, actionOptions.map((option) => (React__default.createElement(Button$1, { key: option.label, onClick: () => onClick(option), icon: option.icon ? React__default.createElement(option.icon, null) : React__default.createElement(React__default.Fragment, null), small: true, destructive: option.destructive }, !option.iconOnly ? option.label : '')))));
3213
+ case 'DROPDOWN':
3214
+ return (React__default.createElement(DropdownMenu$1, { dropdownOptions: actionOptions, onClick: (option) => {
3215
+ onClick(option);
3216
+ } }));
3217
+ case 'LINK':
3218
+ return (React__default.createElement("div", { style: {
3219
+ display: 'flex',
3220
+ gap: 16,
3221
+ alignItems: 'center',
3222
+ } }, actionOptions.map((option) => (React__default.createElement("a", { style: {
3223
+ cursor: 'pointer',
3224
+ textDecoration: 'underline',
3225
+ fontSize: 14,
3226
+ display: 'flex',
3227
+ alignItems: 'center',
3228
+ gap: 4,
3229
+ color: option.destructive ? colors$1.red700 : colors$1.grey900,
3230
+ }, key: option.label, onClick: (event) => {
3231
+ event.stopPropagation();
3232
+ onClick(option);
3233
+ } },
3234
+ option.icon ? React__default.createElement(option.icon, null) : React__default.createElement(React__default.Fragment, null),
3235
+ !option.iconOnly ? React__default.createElement("span", null, option.label) : React__default.createElement(React__default.Fragment, null))))));
3236
+ default:
3237
+ return React__default.createElement(React__default.Fragment, null);
3238
+ }
3239
+ };
3240
+
3241
+ // Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
3242
+ // Use of this source code is governed by a BSD-style
3243
+ // license that can be found in the LICENSE file.
3244
+
3245
+ // Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
3246
+ // Use of this source code is governed by a BSD-style
3247
+ // license that can be found in the LICENSE file.
3248
+ // @flow
3249
+ const orgUnitFieldStyles$1 = {
3250
+ container: {
3251
+ border: '1px solid',
3252
+ borderColor: colors$1.grey400,
3253
+ borderRadius: '3px',
3254
+ height: '100%',
3255
+ width: '100%',
3256
+ zIndex: 0,
3257
+ backgroundColor: 'white',
3258
+ },
3259
+ searchField: {
3260
+ height: '40px',
3261
+ overflow: 'hidden',
3262
+ borderBottomLeftRadius: 0,
3263
+ borderBottomRightRadius: 0,
3264
+ boxShadow: 'none',
3265
+ border: 'none',
3266
+ },
3267
+ debounceFieldContainer: {
3268
+ padding: '8px',
3269
+ background: colors$1.grey100,
3270
+ borderTopLeftRadius: '3px',
3271
+ borderTopRightRadius: '3px',
3272
+ },
3273
+ orgUnitTreeContainer: {
3274
+ borderBottomLeftRadius: '3px',
3275
+ borderBottomRightRadius: '3px',
3276
+ overflow: 'auto',
3277
+ },
3278
+ orgUnitTreeLoader: {
3279
+ padding: '8px',
3280
+ },
3281
+ orgUnitFieldLabel: {
3282
+ marginBottom: '8px',
3283
+ display: 'block',
3284
+ boxSizing: 'border-box',
3285
+ fontSize: '14px',
3286
+ lineHeight: '19px',
3287
+ color: 'rgb(33, 41, 52)',
3288
+ padding: '0',
3289
+ },
3290
+ orgUnitFieldRequiredLabel: {
3291
+ paddingInlineStart: '4px',
3292
+ },
3293
+ };
3294
+ const OrgUnitFormField$1 = (props) => {
3295
+ const { onSelectOrgUnit, label, required, selected, disabled, key } = props;
3296
+ const d2 = window?.d2Web;
3297
+ const classes = useDynamicStyles$2(orgUnitFieldStyles$1);
3298
+ const config = useMemo(() => {
3299
+ return d2.systemInfo?.toInitObject();
3300
+ }, []);
3301
+ const rootOrgUnits = useMemo(() => {
3302
+ return d2.currentUser?.organisationUnits || [];
3303
+ }, []);
3304
+ const getExpandedItems = () => {
3305
+ if (rootOrgUnits && rootOrgUnits.length === 1) {
3306
+ return [`/${rootOrgUnits[0].id}`];
3307
+ }
3308
+ else if (rootOrgUnits?.length > 1) {
3309
+ return rootOrgUnits.map((root) => root.path);
3310
+ }
3311
+ return undefined;
3312
+ };
3313
+ const initiallyExpanded = getExpandedItems();
3314
+ const [searchText, setSearchText] = useState(undefined);
3315
+ const [loading, setLoading] = useState(true);
3316
+ const [searchLoading, setSearchLoading] = useState();
3317
+ const [searchData, setSearchData] = useState();
3318
+ const [selectedOrgUnit, setSelectedOrgUnit] = useState();
3319
+ const [expanded, setExpanded] = useState(initiallyExpanded);
3320
+ const [showOrgUnitTree, setShowOrgUnitTree] = useState(!selected);
3321
+ useEffect(() => {
3322
+ setLoading(true);
3323
+ if (selected) {
3324
+ d2.httpInstance
3325
+ .get(`organisationUnits/${selected}.json?fields=id,displayName,path,code,ancestors[id,displayName]`)
3326
+ .then((response) => {
3327
+ if (response.data) {
3328
+ setSelectedOrgUnit(response.data);
3329
+ handleExpand({ path: response.data['path'] });
3330
+ setLoading(false);
3331
+ }
3332
+ });
3333
+ }
3334
+ else {
3335
+ setShowOrgUnitTree(true);
3336
+ setLoading(false);
3337
+ }
3338
+ }, []);
3339
+ const highlighted = useMemo(() => {
3340
+ if (selectedOrgUnit) {
3341
+ return [selectedOrgUnit['path']];
3342
+ }
3343
+ return undefined;
3344
+ }, [selectedOrgUnit]);
3345
+ const selectedOrgUnitLabel = useMemo(() => {
3346
+ if (selectedOrgUnit) {
3347
+ return [
3348
+ ...(selectedOrgUnit['ancestors'] || []),
3349
+ selectedOrgUnit,
3350
+ ]
3351
+ .map((orgUnit) => orgUnit['displayName'])
3352
+ .join(' / ');
3353
+ }
3354
+ return '';
3355
+ }, [selectedOrgUnit]);
3356
+ const handleExpand = ({ path }) => {
3357
+ if (expanded && !expanded.includes(path)) {
3358
+ setExpanded([...expanded, path]);
3359
+ }
3360
+ };
3361
+ const handleCollapse = ({ path }) => {
3362
+ const pathIndex = expanded?.indexOf(path);
3363
+ if (pathIndex && pathIndex !== -1 && expanded) {
3364
+ const updatedExpanded = pathIndex === 0
3365
+ ? expanded.slice(1)
3366
+ : [...expanded.slice(0, pathIndex), ...expanded.slice(pathIndex + 1)];
3367
+ setExpanded(updatedExpanded);
3368
+ }
3369
+ };
3370
+ const handleOrgUnitSearch = async (query) => {
3371
+ setSearchLoading(true);
3372
+ try {
3373
+ const searchResponse = await d2.httpInstance.get(`organisationUnits.json?fields=id,displayName,path,publicAccess,access,lastUpdated,children[id,displayName,publicAccess,access,path,children::isNotEmpty]&paging=true&query=${query}&withinUserSearchHierarchy=true&pageSize=15`);
3374
+ setSearchData((searchResponse.data || {})['organisationUnits']);
3375
+ setSearchLoading(false);
3376
+ }
3377
+ catch (e) {
3378
+ setSearchLoading(false);
3379
+ }
3380
+ };
3381
+ const debouncedSearch = useCallback(debounce((value) => {
3382
+ handleOrgUnitSearch(value);
3383
+ }, 500), []);
3384
+ const handleFilterChange = (event) => {
3385
+ setSearchText(event.value);
3386
+ debouncedSearch(event.value);
3387
+ };
3388
+ const ready = useMemo(() => {
3389
+ return searchText?.length ? !searchLoading : !loading;
3390
+ }, [searchText, searchLoading, loading]);
3391
+ const renderOrgUnitTree = () => {
3392
+ if (searchText && searchText?.length > 0) {
3393
+ if (searchLoading) {
3394
+ return (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
3395
+ React__default.createElement(CircularLoader$1, { small: true })));
3396
+ }
3397
+ return (ready && (React__default.createElement(OrganisationUnitTree, { key: `${key}-search`, roots: searchData?.map((orgUnit) => orgUnit.id) || [], singleSelection: true, expanded: expanded, handleExpand: handleExpand, handleCollapse: handleCollapse, selected: highlighted, onChange: (event) => {
3398
+ setSelectedOrgUnit(event);
3399
+ setShowOrgUnitTree(false);
3400
+ onSelectOrgUnit(event);
3401
+ } })));
3402
+ }
3403
+ return (React__default.createElement(OrganisationUnitTree, { key: key, roots: rootOrgUnits?.map((orgUnit) => orgUnit.id) || [], singleSelection: true, expanded: expanded, handleExpand: handleExpand, handleCollapse: handleCollapse, selected: highlighted, onChange: (event) => {
3404
+ setSelectedOrgUnit(event);
3405
+ setShowOrgUnitTree(false);
3406
+ onSelectOrgUnit(event);
3407
+ } }));
3408
+ };
3409
+ const handleBlur = () => {
3410
+ // onBlur && onBlur(null);
3411
+ };
3412
+ return (config && (React__default.createElement(Provider, { config: config, plugin: false, parentAlertsAdd: undefined, showAlertsInPlugin: false },
3413
+ React__default.createElement("div", null,
3414
+ label && (React__default.createElement("div", { className: classes.orgUnitFieldLabel },
3415
+ React__default.createElement("span", null, label),
3416
+ required ? (React__default.createElement("span", { className: classes.orgUnitFieldRequiredLabel }, "*")) : (React__default.createElement(React__default.Fragment, null)))),
3417
+ !showOrgUnitTree ? (selectedOrgUnit ? (React__default.createElement(React__default.Fragment, null,
3418
+ React__default.createElement(Tooltip, { content: selectedOrgUnitLabel },
3419
+ React__default.createElement(Chip, { onRemove: () => {
3420
+ setShowOrgUnitTree(true);
3421
+ } }, selectedOrgUnit.displayName)))) : (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
3422
+ React__default.createElement(CircularLoader$1, { small: true })))) : (React__default.createElement("div", { className: classes.container },
3423
+ React__default.createElement("div", { className: classes.debounceFieldContainer },
3424
+ React__default.createElement(InputField$1, { placeholder: "Search", value: searchText, disabled: disabled, onChange: handleFilterChange })),
3425
+ loading ? (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
3426
+ React__default.createElement(CircularLoader$1, { small: true }))) : (React__default.createElement("div", { className: classes.orgUnitTreeContainer, style: { maxHeight: '200px', overflowY: 'auto' } }, renderOrgUnitTree()))))))));
3427
+ };
3428
+
3205
3429
  // export function buildFilters(filters: AttributeFilter[]): string {
3206
3430
  // return filters
3207
3431
  // .map(({ attribute, operator, value }) => `filter=${attribute}:${operator}:${value}`)
@@ -3849,194 +4073,11 @@ const addActionsColumn = (columns, actionOptions) => {
3849
4073
  ];
3850
4074
  };
3851
4075
 
3852
- // Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
3853
- // Use of this source code is governed by a BSD-style
3854
- // license that can be found in the LICENSE file.
3855
- // @flow
3856
- const orgUnitFieldStyles$1 = {
3857
- container: {
3858
- border: '1px solid',
3859
- borderColor: colors$1.grey400,
3860
- borderRadius: '3px',
3861
- height: '100%',
3862
- width: '100%',
3863
- zIndex: 0,
3864
- backgroundColor: 'white',
3865
- },
3866
- searchField: {
3867
- height: '40px',
3868
- overflow: 'hidden',
3869
- borderBottomLeftRadius: 0,
3870
- borderBottomRightRadius: 0,
3871
- boxShadow: 'none',
3872
- border: 'none',
3873
- },
3874
- debounceFieldContainer: {
3875
- padding: '8px',
3876
- background: colors$1.grey100,
3877
- borderTopLeftRadius: '3px',
3878
- borderTopRightRadius: '3px',
3879
- },
3880
- orgUnitTreeContainer: {
3881
- borderBottomLeftRadius: '3px',
3882
- borderBottomRightRadius: '3px',
3883
- overflow: 'auto',
3884
- },
3885
- orgUnitTreeLoader: {
3886
- padding: '8px',
3887
- },
3888
- orgUnitFieldLabel: {
3889
- marginBottom: '8px',
3890
- display: 'block',
3891
- boxSizing: 'border-box',
3892
- fontSize: '14px',
3893
- lineHeight: '19px',
3894
- color: 'rgb(33, 41, 52)',
3895
- padding: '0',
3896
- },
3897
- orgUnitFieldRequiredLabel: {
3898
- paddingInlineStart: '4px',
3899
- },
3900
- };
3901
- const OrgUnitFormField$1 = (props) => {
3902
- const { onSelectOrgUnit, label, required, selected, disabled, key } = props;
3903
- const d2 = window?.d2Web;
3904
- const classes = useDynamicStyles$2(orgUnitFieldStyles$1);
3905
- const config = useMemo(() => {
3906
- return d2.systemInfo?.toInitObject();
3907
- }, []);
3908
- const rootOrgUnits = useMemo(() => {
3909
- return d2.currentUser?.organisationUnits || [];
3910
- }, []);
3911
- const getExpandedItems = () => {
3912
- if (rootOrgUnits && rootOrgUnits.length === 1) {
3913
- return [`/${rootOrgUnits[0].id}`];
3914
- }
3915
- else if (rootOrgUnits?.length > 1) {
3916
- return rootOrgUnits.map((root) => root.path);
3917
- }
3918
- return undefined;
3919
- };
3920
- const initiallyExpanded = getExpandedItems();
3921
- const [searchText, setSearchText] = useState(undefined);
3922
- const [loading, setLoading] = useState(true);
3923
- const [searchLoading, setSearchLoading] = useState();
3924
- const [searchData, setSearchData] = useState();
3925
- const [selectedOrgUnit, setSelectedOrgUnit] = useState();
3926
- const [expanded, setExpanded] = useState(initiallyExpanded);
3927
- const [showOrgUnitTree, setShowOrgUnitTree] = useState(!selected);
3928
- useEffect(() => {
3929
- setLoading(true);
3930
- if (selected) {
3931
- d2.httpInstance
3932
- .get(`organisationUnits/${selected}.json?fields=id,displayName,path,code,ancestors[id,displayName]`)
3933
- .then((response) => {
3934
- if (response.data) {
3935
- setSelectedOrgUnit(response.data);
3936
- handleExpand({ path: response.data['path'] });
3937
- setLoading(false);
3938
- }
3939
- });
3940
- }
3941
- else {
3942
- setShowOrgUnitTree(true);
3943
- setLoading(false);
3944
- }
3945
- }, []);
3946
- const highlighted = useMemo(() => {
3947
- if (selectedOrgUnit) {
3948
- return [selectedOrgUnit['path']];
3949
- }
3950
- return undefined;
3951
- }, [selectedOrgUnit]);
3952
- const selectedOrgUnitLabel = useMemo(() => {
3953
- if (selectedOrgUnit) {
3954
- return [
3955
- ...(selectedOrgUnit['ancestors'] || []),
3956
- selectedOrgUnit,
3957
- ]
3958
- .map((orgUnit) => orgUnit['displayName'])
3959
- .join(' / ');
3960
- }
3961
- return '';
3962
- }, [selectedOrgUnit]);
3963
- const handleExpand = ({ path }) => {
3964
- if (expanded && !expanded.includes(path)) {
3965
- setExpanded([...expanded, path]);
3966
- }
3967
- };
3968
- const handleCollapse = ({ path }) => {
3969
- const pathIndex = expanded?.indexOf(path);
3970
- if (pathIndex && pathIndex !== -1 && expanded) {
3971
- const updatedExpanded = pathIndex === 0
3972
- ? expanded.slice(1)
3973
- : [...expanded.slice(0, pathIndex), ...expanded.slice(pathIndex + 1)];
3974
- setExpanded(updatedExpanded);
3975
- }
3976
- };
3977
- const handleOrgUnitSearch = async (query) => {
3978
- setSearchLoading(true);
3979
- try {
3980
- const searchResponse = await d2.httpInstance.get(`organisationUnits.json?fields=id,displayName,path,publicAccess,access,lastUpdated,children[id,displayName,publicAccess,access,path,children::isNotEmpty]&paging=true&query=${query}&withinUserSearchHierarchy=true&pageSize=15`);
3981
- setSearchData((searchResponse.data || {})['organisationUnits']);
3982
- setSearchLoading(false);
3983
- }
3984
- catch (e) {
3985
- setSearchLoading(false);
3986
- }
3987
- };
3988
- const debouncedSearch = useCallback(debounce((value) => {
3989
- handleOrgUnitSearch(value);
3990
- }, 500), []);
3991
- const handleFilterChange = (event) => {
3992
- setSearchText(event.value);
3993
- debouncedSearch(event.value);
3994
- };
3995
- const ready = useMemo(() => {
3996
- return searchText?.length ? !searchLoading : !loading;
3997
- }, [searchText, searchLoading, loading]);
3998
- const renderOrgUnitTree = () => {
3999
- if (searchText && searchText?.length > 0) {
4000
- if (searchLoading) {
4001
- return (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
4002
- React__default.createElement(CircularLoader$1, { small: true })));
4003
- }
4004
- return (ready && (React__default.createElement(OrganisationUnitTree, { key: `${key}-search`, roots: searchData?.map((orgUnit) => orgUnit.id) || [], singleSelection: true, expanded: expanded, handleExpand: handleExpand, handleCollapse: handleCollapse, selected: highlighted, onChange: (event) => {
4005
- setSelectedOrgUnit(event);
4006
- setShowOrgUnitTree(false);
4007
- onSelectOrgUnit(event);
4008
- } })));
4009
- }
4010
- return (React__default.createElement(OrganisationUnitTree, { key: key, roots: rootOrgUnits?.map((orgUnit) => orgUnit.id) || [], singleSelection: true, expanded: expanded, handleExpand: handleExpand, handleCollapse: handleCollapse, selected: highlighted, onChange: (event) => {
4011
- setSelectedOrgUnit(event);
4012
- setShowOrgUnitTree(false);
4013
- onSelectOrgUnit(event);
4014
- } }));
4015
- };
4016
- const handleBlur = () => {
4017
- // onBlur && onBlur(null);
4018
- };
4019
- return (config && (React__default.createElement(Provider, { config: config, plugin: false, parentAlertsAdd: undefined, showAlertsInPlugin: false },
4020
- React__default.createElement("div", null,
4021
- label && (React__default.createElement("div", { className: classes.orgUnitFieldLabel },
4022
- React__default.createElement("span", null, label),
4023
- required ? (React__default.createElement("span", { className: classes.orgUnitFieldRequiredLabel }, "*")) : (React__default.createElement(React__default.Fragment, null)))),
4024
- !showOrgUnitTree ? (selectedOrgUnit ? (React__default.createElement(React__default.Fragment, null,
4025
- React__default.createElement(Tooltip, { content: selectedOrgUnitLabel },
4026
- React__default.createElement(Chip, { onRemove: () => {
4027
- setShowOrgUnitTree(true);
4028
- } }, selectedOrgUnit.displayName)))) : (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
4029
- React__default.createElement(CircularLoader$1, { small: true })))) : (React__default.createElement("div", { className: classes.container },
4030
- React__default.createElement("div", { className: classes.debounceFieldContainer },
4031
- React__default.createElement(InputField$1, { placeholder: "Search", value: searchText, disabled: disabled, onChange: handleFilterChange })),
4032
- loading ? (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
4033
- React__default.createElement(CircularLoader$1, { small: true }))) : (React__default.createElement("div", { className: classes.orgUnitTreeContainer, style: { maxHeight: '200px', overflowY: 'auto' } }, renderOrgUnitTree()))))))));
4034
- };
4035
-
4036
4076
  // src/app/line-list-table.component.ts
4037
4077
  class LineListTableComponent extends ReactWrapperModule {
4038
4078
  constructor() {
4039
4079
  super(...arguments);
4080
+ this.actionOptionOrientation = 'DROPDOWN';
4040
4081
  this.dispatchTeis = false;
4041
4082
  this.actionSelected = new EventEmitter();
4042
4083
  this.reactStateUpdaters = null;
@@ -4098,8 +4139,9 @@ class LineListTableComponent extends ReactWrapperModule {
4098
4139
  }, []);
4099
4140
  useEffect(() => {
4100
4141
  setLoading(true);
4101
- this.lineListService
4142
+ const subscription = this.lineListService
4102
4143
  .getLineListData(this.programId, orgUnitState, this.programStageId, pager.page, pager.pageSize, attributeFiltersState, startDateState, endDateState, this.ouMode, this.filterRootOrgUnit, this.useOuModeWithOlderDHIS2Instance)
4144
+ .pipe(take(1)) // Automatically unsubscribe after the first emission
4103
4145
  .subscribe((response) => {
4104
4146
  let entityColumns = [];
4105
4147
  let responsePager;
@@ -4114,7 +4156,7 @@ class LineListTableComponent extends ReactWrapperModule {
4114
4156
  else if ('trackedEntities' in response.data ||
4115
4157
  'instances' in response.data) {
4116
4158
  const responseData = response.data;
4117
- responsePager = response.data.pager || {
4159
+ responsePager = responseData.pager || {
4118
4160
  page: responseData.page,
4119
4161
  pageSize: responseData.pageSize,
4120
4162
  total: responseData.total,
@@ -4123,23 +4165,18 @@ class LineListTableComponent extends ReactWrapperModule {
4123
4165
  const { columns, data, filteredEntityColumns } = getTrackedEntityData(response, this.programId, pager, this.isOptionSetNameVisible, this.filters);
4124
4166
  entityColumns = columns;
4125
4167
  entityData = data;
4126
- //TODO: Should be done on the parent
4127
4168
  const checkValues = [
4128
- ...new Set(response.data?.trackedEntities?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4169
+ ...new Set(responseData?.trackedEntities?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4129
4170
  .filter((dataValue) => /^[A-Za-z]{3}$/.test(dataValue.value))
4130
4171
  .map((dataValue) => dataValue.value)) ??
4131
- response.data?.instances?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4172
+ responseData?.instances?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
4132
4173
  .filter((dataValue) => /^[A-Za-z]{3}$/.test(dataValue.value))
4133
4174
  .map((dataValue) => dataValue.value))),
4134
4175
  ];
4135
- if (checkValues) {
4136
- // this.firstValue.emit(firstTei);
4176
+ if (checkValues.length > 0) {
4137
4177
  setCheckValue(checkValues);
4138
4178
  setValuesMatch(!checkValues.includes(this.buttonFilter));
4139
4179
  }
4140
- // setFilteredColumns(filteredEntityColumns);
4141
- // Ensure filter inputs do not disappear when no data is returned
4142
- // If filteredEntityColumns is empty, keep the previous columns instead of clearing them
4143
4180
  setFilteredColumns((prev) => filteredEntityColumns.length > 0 ? filteredEntityColumns : prev);
4144
4181
  filteredDataColumns = filteredEntityColumns;
4145
4182
  }
@@ -4151,10 +4188,13 @@ class LineListTableComponent extends ReactWrapperModule {
4151
4188
  }
4152
4189
  const finalColumns = addActionsColumn([{ label: '#', key: 'index' }, ...entityColumns], this.actionOptions);
4153
4190
  setLoading(false);
4154
- setColumns(...[finalColumns]);
4155
- setData(...[entityData]);
4156
- setPager(...[responsePager]);
4191
+ setColumns(finalColumns);
4192
+ setData(entityData);
4193
+ setPager(responsePager);
4157
4194
  });
4195
+ return () => {
4196
+ subscription.unsubscribe(); // Ensure cleanup on unmount
4197
+ };
4158
4198
  }, [
4159
4199
  programIdState,
4160
4200
  orgUnitState,
@@ -4166,19 +4206,11 @@ class LineListTableComponent extends ReactWrapperModule {
4166
4206
  pager.pageSize,
4167
4207
  isOptionSetNameVisibleState,
4168
4208
  ]);
4169
- const getDropdownOptions = (row) => {
4170
- return (this.actionOptions || []).map((option) => ({
4171
- ...option,
4172
- onClick: () => {
4173
- option.onClick?.(row);
4174
- this.actionSelected.emit({ action: option.label, row });
4175
- },
4176
- }));
4177
- };
4178
4209
  const handleApprovalClick = () => {
4179
4210
  setIsButtonLoading(true);
4180
- this.lineListService
4211
+ const subscription = this.lineListService
4181
4212
  .getLineListData(this.programId, this.orgUnit, this.programStageId, pager.page, 1000, this.attributeFilters, this.startDate, this.endDate, this.ouMode)
4213
+ .pipe(take(1)) // Ensures the observable emits only once
4182
4214
  .subscribe((response) => {
4183
4215
  if ('trackedEntities' in response.data ||
4184
4216
  'instances' in response.data) {
@@ -4186,9 +4218,7 @@ class LineListTableComponent extends ReactWrapperModule {
4186
4218
  // Map TEIs to objects with teiId and enrollmentId
4187
4219
  const teiEnrollmentList = trackedEntityInstances
4188
4220
  .map((tei) => {
4189
- // Find the enrollment where program matches this.programId
4190
4221
  const matchingEnrollment = tei.enrollments?.find((enrollment) => enrollment.program === this.programId);
4191
- // Return object with teiId and enrollmentId (if found)
4192
4222
  return matchingEnrollment
4193
4223
  ? {
4194
4224
  teiId: tei.trackedEntity,
@@ -4196,13 +4226,17 @@ class LineListTableComponent extends ReactWrapperModule {
4196
4226
  }
4197
4227
  : null;
4198
4228
  })
4199
- .filter((item) => item !== null); // Remove null entries (no matching enrollment)
4229
+ .filter(Boolean); // Removes null entries
4200
4230
  this.approvalSelected.emit(teiEnrollmentList);
4201
4231
  }
4202
4232
  else {
4203
4233
  this.approvalSelected.emit([]); // Emit empty array if no TEIs
4204
4234
  }
4235
+ setIsButtonLoading(false); // Stop loading after processing
4205
4236
  });
4237
+ return () => {
4238
+ subscription.unsubscribe(); // Cleanup to avoid potential memory leaks
4239
+ };
4206
4240
  };
4207
4241
  const handleInputChange = (key, value) => {
4208
4242
  setInputValues((prevValues) => ({
@@ -4247,15 +4281,7 @@ class LineListTableComponent extends ReactWrapperModule {
4247
4281
  React__default.createElement(Modal$1, { hide: hide },
4248
4282
  React__default.createElement(ModalTitle$1, null, "Select organization unit"),
4249
4283
  React__default.createElement(ModalContent$1, null,
4250
- React__default.createElement(OrgUnitFormField$1
4251
- // label="Select organization unit"
4252
- , {
4253
- // label="Select organization unit"
4254
- key: selectedOrgUnit, onSelectOrgUnit: (selectedOrgUnit) => {
4255
- // console.log(
4256
- // 'this is the selected org unit key',
4257
- // selectedOrgUnit
4258
- // );
4284
+ React__default.createElement(OrgUnitFormField$1, { key: selectedOrgUnit, onSelectOrgUnit: (selectedOrgUnit) => {
4259
4285
  setSelectedOrgUnit(selectedOrgUnit.displayName);
4260
4286
  setOrgUnitState(selectedOrgUnit.id);
4261
4287
  setHide(true);
@@ -4266,17 +4292,18 @@ class LineListTableComponent extends ReactWrapperModule {
4266
4292
  this.showFilters && (React__default.createElement(DataTableToolbar, { className: "table-top-toolbar" },
4267
4293
  React__default.createElement("div", { style: {
4268
4294
  display: 'flex',
4295
+ alignItems: 'center',
4269
4296
  gap: '10px',
4270
4297
  flexWrap: 'wrap',
4271
4298
  } },
4272
- React__default.createElement(InputField$1, { key: "location", label: "Location:", value: selectedOrgUnit, onFocus: () => setHide(false), className: "custom-input" }),
4273
- React__default.createElement(CalendarInput, { label: "Start Date:", calendar: "gregory", locale: "en-GB", timeZone: "Africa/Dar_es_Salaam", className: "custom-input", date: startDateState, onDateSelect: (selectedDate) => {
4299
+ React__default.createElement(InputField$1, { key: "location", label: "Registraring unit:", value: selectedOrgUnit, clearable: true, onFocus: () => setHide(false), className: "custom-input" }),
4300
+ React__default.createElement(CalendarInput, { label: "Start date:", calendar: "gregory", locale: "en-GB", timeZone: "Africa/Dar_es_Salaam", className: "custom-input", date: startDateState, onDateSelect: (selectedDate) => {
4274
4301
  setStartDateState(selectedDate.calendarDateString);
4275
4302
  } }),
4276
- React__default.createElement(CalendarInput, { label: "End Date:", calendar: "gregory", locale: "en-GB", timeZone: "Africa/Dar_es_Salaam", className: "custom-input", date: endDateState, onDateSelect: (selectedDate) => {
4303
+ React__default.createElement(CalendarInput, { label: "End date:", calendar: "gregory", locale: "en-GB", timeZone: "Africa/Dar_es_Salaam", className: "custom-input", date: endDateState, onDateSelect: (selectedDate) => {
4277
4304
  setEndDateState(selectedDate.calendarDateString);
4278
4305
  } }),
4279
- (visibleFilters ?? []).map(({ label, key }) => (React__default.createElement(InputField$1, { key: key, label: `${label}:`, className: "custom-input", value: inputValues[key] || '', onChange: (e) => {
4306
+ (visibleFilters ?? []).map(({ label, key }) => (React__default.createElement(InputField$1, { key: key, label: `${label}:`, className: "custom-input", value: inputValues[key] || '', clearable: true, onChange: (e) => {
4280
4307
  if ('target' in e && e.target) {
4281
4308
  setInputValues((prevValues) => ({
4282
4309
  ...prevValues,
@@ -4300,12 +4327,19 @@ class LineListTableComponent extends ReactWrapperModule {
4300
4327
  }
4301
4328
  handleInputChange(key, currentValue);
4302
4329
  } }))),
4303
- React__default.createElement(Button$1, { onClick: () => setShowAllFilters(!showAllFilters), className: "custom-button", secondary: true, large: true }, showAllFilters ? 'Less Filters' : 'More Filters')))),
4330
+ React__default.createElement(Button$1, { onClick: () => setShowAllFilters(!showAllFilters) }, showAllFilters ? 'Less Filters' : 'More Filters')))),
4304
4331
  React__default.createElement(DataTable, null,
4305
4332
  React__default.createElement(TableHead, null,
4306
4333
  React__default.createElement(DataTableRow, null, columns.map((col) => (React__default.createElement(DataTableColumnHeader, { key: col.key }, col.label))))),
4307
- React__default.createElement(TableBody, null, data.length > 0 ? (data.map((row) => (React__default.createElement(DataTableRow, { key: row.index }, columns.map((col) => (React__default.createElement(DataTableCell, { key: col.key }, col.key === 'actions' ? (React__default.createElement(DropdownMenu$1, { dropdownOptions: getDropdownOptions(row), onClick: (option) => option.onClick?.() })) : (row[col.key] || '--' // Display '--' if value is undefined or null
4308
- )))))))) : (React__default.createElement(DataTableRow, null,
4334
+ React__default.createElement(TableBody, null, data.length > 0 ? (data.map((row) => (React__default.createElement(DataTableRow, { key: row.index }, columns.map((col) => (React__default.createElement(DataTableCell, { key: col.key }, col.key === 'actions'
4335
+ ? this.actionOptions && (React__default.createElement(DataTableActions, { actionOptions: this.actionOptions, actionOptionOrientation: this.actionOptionOrientation, onClick: (option) => {
4336
+ this.actionSelected.emit({
4337
+ action: option.label,
4338
+ row,
4339
+ });
4340
+ } }))
4341
+ : row[col.key] || '--' // Display '--' if value is undefined or null
4342
+ ))))))) : (React__default.createElement(DataTableRow, null,
4309
4343
  React__default.createElement(DataTableCell, { colSpan: columns.length?.toString(), style: {
4310
4344
  textAlign: 'center',
4311
4345
  fontWeight: 'bold',
@@ -4362,11 +4396,11 @@ class LineListTableComponent extends ReactWrapperModule {
4362
4396
  this.render();
4363
4397
  }
4364
4398
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.8", ngImport: i0, type: LineListTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
4365
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.8", type: LineListTableComponent, isStandalone: false, selector: "ng-dhis2-ui-line-list", inputs: { programId: "programId", orgUnit: "orgUnit", programStageId: "programStageId", actionOptions: "actionOptions", attributeFilters: "attributeFilters", useOuModeWithOlderDHIS2Instance: "useOuModeWithOlderDHIS2Instance", startDate: "startDate", endDate: "endDate", filters: "filters", ouMode: "ouMode", dispatchTeis: "dispatchTeis", isButtonLoading: "isButtonLoading", buttonLabel: "buttonLabel", buttonFilter: "buttonFilter", filterRootOrgUnit: "filterRootOrgUnit", showFilters: "showFilters", isOptionSetNameVisible: "isOptionSetNameVisible" }, outputs: { actionSelected: "actionSelected", approvalSelected: "approvalSelected", firstValue: "firstValue" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["::ng-deep .custom-input{display:flex!important;gap:4px!important;align-items:center!important;border:1px solid #9FADBA!important;border-radius:4px!important;padding-left:16px}::ng-deep .custom-input input{border:none!important;outline:none!important;box-shadow:none!important}::ng-deep .custom-input input:focus{border:none!important;box-shadow:none!important}::ng-deep .custom-input label{color:gray!important;opacity:1}::ng-deep .table-top-toolbar{background-color:var(--colors-grey100)!important;color:var(--colors-grey900)!important}\n"] }); }
4399
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.8", type: LineListTableComponent, isStandalone: false, selector: "ng-dhis2-ui-line-list", inputs: { programId: "programId", orgUnit: "orgUnit", programStageId: "programStageId", actionOptions: "actionOptions", actionOptionOrientation: "actionOptionOrientation", attributeFilters: "attributeFilters", useOuModeWithOlderDHIS2Instance: "useOuModeWithOlderDHIS2Instance", startDate: "startDate", endDate: "endDate", filters: "filters", ouMode: "ouMode", dispatchTeis: "dispatchTeis", isButtonLoading: "isButtonLoading", buttonLabel: "buttonLabel", buttonFilter: "buttonFilter", filterRootOrgUnit: "filterRootOrgUnit", showFilters: "showFilters", isOptionSetNameVisible: "isOptionSetNameVisible" }, outputs: { actionSelected: "actionSelected", approvalSelected: "approvalSelected", firstValue: "firstValue" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["::ng-deep .custom-input{display:flex!important;gap:4px!important;align-items:center!important;border:1px solid #9fadba!important;border-radius:4px!important;margin:0;padding:0 0 0 8px;background-color:#fff!important}::ng-deep .custom-input div{margin:0!important}::ng-deep .custom-input label{font-size:12px;padding-right:0}::ng-deep .custom-input input{border:none!important;font-size:12px!important;display:flex;align-items:center;padding-left:4px;height:40px;outline:none!important;box-shadow:none!important}::ng-deep .custom-input input:focus{border:none!important;box-shadow:none!important}::ng-deep .custom-input label{color:gray!important;opacity:1}::ng-deep .table-top-toolbar{background-color:var(--colors-grey100)!important;color:var(--colors-grey900)!important}\n"] }); }
4366
4400
  }
4367
4401
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.8", ngImport: i0, type: LineListTableComponent, decorators: [{
4368
4402
  type: Component,
4369
- args: [{ selector: 'ng-dhis2-ui-line-list', template: '<ng-content></ng-content>', standalone: false, styles: ["::ng-deep .custom-input{display:flex!important;gap:4px!important;align-items:center!important;border:1px solid #9FADBA!important;border-radius:4px!important;padding-left:16px}::ng-deep .custom-input input{border:none!important;outline:none!important;box-shadow:none!important}::ng-deep .custom-input input:focus{border:none!important;box-shadow:none!important}::ng-deep .custom-input label{color:gray!important;opacity:1}::ng-deep .table-top-toolbar{background-color:var(--colors-grey100)!important;color:var(--colors-grey900)!important}\n"] }]
4403
+ args: [{ selector: 'ng-dhis2-ui-line-list', template: '<ng-content></ng-content>', standalone: false, styles: ["::ng-deep .custom-input{display:flex!important;gap:4px!important;align-items:center!important;border:1px solid #9fadba!important;border-radius:4px!important;margin:0;padding:0 0 0 8px;background-color:#fff!important}::ng-deep .custom-input div{margin:0!important}::ng-deep .custom-input label{font-size:12px;padding-right:0}::ng-deep .custom-input input{border:none!important;font-size:12px!important;display:flex;align-items:center;padding-left:4px;height:40px;outline:none!important;box-shadow:none!important}::ng-deep .custom-input input:focus{border:none!important;box-shadow:none!important}::ng-deep .custom-input label{color:gray!important;opacity:1}::ng-deep .table-top-toolbar{background-color:var(--colors-grey100)!important;color:var(--colors-grey900)!important}\n"] }]
4370
4404
  }], propDecorators: { programId: [{
4371
4405
  type: Input
4372
4406
  }], orgUnit: [{
@@ -4375,6 +4409,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.8", ngImpor
4375
4409
  type: Input
4376
4410
  }], actionOptions: [{
4377
4411
  type: Input
4412
+ }], actionOptionOrientation: [{
4413
+ type: Input
4378
4414
  }], attributeFilters: [{
4379
4415
  type: Input
4380
4416
  }], useOuModeWithOlderDHIS2Instance: [{
@@ -4424,6 +4460,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.8", ngImpor
4424
4460
  }]
4425
4461
  }] });
4426
4462
 
4463
+ // Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
4464
+ // Use of this source code is governed by a BSD-style
4465
+ // license that can be found in the LICENSE file.
4466
+
4427
4467
  const dropdownStyles = {
4428
4468
  layer: {
4429
4469
  zIndex: '1 !important',
@@ -5975,9 +6015,24 @@ class BaseFormFieldComponent extends ReactWrapperModule {
5975
6015
  checkValueUniqueness();
5976
6016
  }
5977
6017
  }, [this.isValueAssigned()]);
6018
+ // TODO: START: Improve the approach of handling observables
6019
+ // useEffect(() => {
6020
+ // const fieldErrorSubscription = this.fieldError$
6021
+ // .pipe(filter((error) => error !== initialError))
6022
+ // .subscribe({
6023
+ // next: (error: string | undefined) => {
6024
+ // setInitialError(error);
6025
+ // },
6026
+ // });
6027
+ // return () => {
6028
+ // fieldErrorSubscription.unsubscribe();
6029
+ // };
6030
+ // });
6031
+ // TODO: END: Improve the approach of handling observables
5978
6032
  useEffect(() => {
5979
6033
  const fieldErrorSubscription = this.fieldError$
5980
- .pipe(filter((error) => error !== initialError))
6034
+ .pipe(filter((error) => error !== initialError), take(1) // Take only the first emitted value, then unsubscribe
6035
+ )
5981
6036
  .subscribe({
5982
6037
  next: (error) => {
5983
6038
  setInitialError(error);
@@ -5986,7 +6041,7 @@ class BaseFormFieldComponent extends ReactWrapperModule {
5986
6041
  return () => {
5987
6042
  fieldErrorSubscription.unsubscribe();
5988
6043
  };
5989
- });
6044
+ }, [initialError]); // Add initialError as a dependency to avoid stale closures
5990
6045
  const onChange = (payload) => setSelected(payload.selected);
5991
6046
  const arrayValue = useMemo(() => {
5992
6047
  if (value && value.length > 0) {
@@ -6873,14 +6928,30 @@ class SelectionFiltersComponent extends ReactWrapperModule {
6873
6928
  const [selected, setSelected] = useState([]);
6874
6929
  const [rootOrgUnits, setRootOrgUnits] = useState();
6875
6930
  const [config, setConfig] = useState();
6931
+ // TODO: START::: Improve approach on handling observables
6932
+ // useEffect(() => {
6933
+ // zip(this.getAppConfig(), this.getRootOrgUnits()).subscribe({
6934
+ // next: ([appConfig, rootOrgUnits]) => {
6935
+ // setConfig(appConfig);
6936
+ // setRootOrgUnits(rootOrgUnits);
6937
+ // },
6938
+ // error: (error) => console.error(error),
6939
+ // });
6940
+ // }, []);
6941
+ // TODO: END::: Improve approach on handling observables
6876
6942
  useEffect(() => {
6877
- zip(this.getAppConfig(), this.getRootOrgUnits()).subscribe({
6943
+ const subscription = zip(this.getAppConfig(), this.getRootOrgUnits())
6944
+ .pipe(take(1)) // Ensures only one emission
6945
+ .subscribe({
6878
6946
  next: ([appConfig, rootOrgUnits]) => {
6879
6947
  setConfig(appConfig);
6880
6948
  setRootOrgUnits(rootOrgUnits);
6881
6949
  },
6882
6950
  error: (error) => console.error(error),
6883
6951
  });
6952
+ return () => {
6953
+ subscription.unsubscribe(); // Cleanup to avoid memory leaks
6954
+ };
6884
6955
  }, []);
6885
6956
  return config ? (React__default.createElement(Provider, { config: config, plugin: false, parentAlertsAdd: undefined, showAlertsInPlugin: false }, React__default.createElement(Modal$1, { position: "middle", large: true },
6886
6957
  React__default.createElement(ModalTitle$1, null, "Organisation unit"),