@iapps/ng-dhis2-ui 1.1.47 → 1.1.48
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.
- package/fesm2022/iapps-ng-dhis2-ui.mjs +344 -85
- package/fesm2022/iapps-ng-dhis2-ui.mjs.map +1 -1
- package/lib/modules/form/components/org-unit-form-field.component.d.ts +14 -0
- package/lib/modules/form/hooks/field-validation.hook.d.ts +11 -0
- package/lib/modules/form/hooks/index.d.ts +1 -0
- package/lib/modules/form/utils/field-type.util.d.ts +1 -1
- package/lib/modules/form/utils/field.util.d.ts +1 -1
- package/lib/modules/line-list/containers/line-list.component.d.ts +2 -1
- package/lib/shared/hooks/dynamic-styles.hook.d.ts +1 -0
- package/lib/shared/hooks/index.d.ts +1 -0
- package/lib/shared/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -25,9 +25,9 @@ import { Layer } from '@dhis2-ui/layer';
|
|
|
25
25
|
import { CircularLoader } from '@dhis2-ui/loader';
|
|
26
26
|
import { MenuItem, MenuDivider } from '@dhis2-ui/menu';
|
|
27
27
|
import parse from 'html-react-parser';
|
|
28
|
-
import { Button as Button$1, IconDimensionData16, IconClock16, IconDimensionOrgUnit16, spacers as spacers$1, colors as colors$1, IconEdit16, theme as theme$1, Tooltip, SingleSelectField, SingleSelectOption, InputField as InputField$1, TabBar, Tab, Transfer, Modal as Modal$1, ModalTitle as ModalTitle$1, ModalContent as ModalContent$1, ModalActions as ModalActions$1, ButtonStrip as ButtonStrip$1, Checkbox, OrganisationUnitTree, MultiSelect, MultiSelectOption, IconWarningFilled16, Layer as Layer$1, Popper, FlyoutMenu, MenuItem as MenuItem$1, IconMore24, CircularLoader as CircularLoader$1, IconFilter16, DataTable, TableHead, DataTableRow, DataTableColumnHeader, TableBody, DataTableCell, TableFoot, Pagination, FileInputField, FileListItem, MultiSelectField, TextAreaField } from '@dhis2/ui';
|
|
28
|
+
import { Button as Button$1, IconDimensionData16, IconClock16, IconDimensionOrgUnit16, spacers as spacers$1, colors as colors$1, IconEdit16, theme as theme$1, Tooltip, SingleSelectField, SingleSelectOption, InputField as InputField$1, TabBar, Tab, Transfer, Modal as Modal$1, ModalTitle as ModalTitle$1, ModalContent as ModalContent$1, ModalActions as ModalActions$1, ButtonStrip as ButtonStrip$1, Checkbox, OrganisationUnitTree, MultiSelect, MultiSelectOption, IconWarningFilled16, Layer as Layer$1, Popper, FlyoutMenu, MenuItem as MenuItem$1, IconMore24, CircularLoader as CircularLoader$1, IconFilter16, DataTable, TableHead, DataTableRow, DataTableColumnHeader, TableBody, DataTableCell, TableFoot, Pagination, Chip, FileInputField, FileListItem, MultiSelectField, TextAreaField } from '@dhis2/ui';
|
|
29
29
|
import { getNowInCalendar, generateFixedPeriods } from '@dhis2/multi-calendar-dates';
|
|
30
|
-
import { isObject, isString, sortBy, isArray, find, camelCase, flatten, head, isEmpty } from 'lodash';
|
|
30
|
+
import { isObject, isString, sortBy, isArray, find, camelCase, flatten, debounce, head, isEmpty } from 'lodash';
|
|
31
31
|
import cx from 'classnames';
|
|
32
32
|
import { map as map$1, switchMap } from 'rxjs/operators';
|
|
33
33
|
import * as i2 from '@angular/forms';
|
|
@@ -1564,6 +1564,62 @@ TransferOption.propTypes = {
|
|
|
1564
1564
|
// Use of this source code is governed by a BSD-style
|
|
1565
1565
|
// license that can be found in the LICENSE file.
|
|
1566
1566
|
|
|
1567
|
+
// Helper function to generate a unique class name
|
|
1568
|
+
const generateClassName$2 = (base) => `${base}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1569
|
+
// Helper function to convert camelCased properties to hyphenated CSS properties
|
|
1570
|
+
const convertStylesToCSS$2 = (className, styles) => {
|
|
1571
|
+
let cssString = `.${className} {`;
|
|
1572
|
+
for (const key in styles) {
|
|
1573
|
+
if (typeof styles[key] === 'object' && key.startsWith('@media')) {
|
|
1574
|
+
const mediaStyles = convertStylesToCSS$2(className, styles[key]);
|
|
1575
|
+
cssString += `${key} { ${mediaStyles} }`;
|
|
1576
|
+
}
|
|
1577
|
+
else if (key.startsWith('&:')) {
|
|
1578
|
+
const pseudoClass = key.replace('&', '');
|
|
1579
|
+
cssString += `${pseudoClass} { ${convertStylesToCSS$2('', styles[key])} }`;
|
|
1580
|
+
}
|
|
1581
|
+
else {
|
|
1582
|
+
const cssKey = key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
1583
|
+
cssString += `${cssKey}: ${styles[key]}; `;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
cssString += '}';
|
|
1587
|
+
return cssString;
|
|
1588
|
+
};
|
|
1589
|
+
// Custom hook for dynamic styles with generics
|
|
1590
|
+
const useDynamicStyles$2 = (stylesObj) => {
|
|
1591
|
+
const [classNames, setClassNames] = useState({});
|
|
1592
|
+
useEffect(() => {
|
|
1593
|
+
const convertedStyles = Object.keys(stylesObj).map((key) => {
|
|
1594
|
+
const className = generateClassName$2(key);
|
|
1595
|
+
const styleElement = document.createElement('style');
|
|
1596
|
+
styleElement.innerHTML = convertStylesToCSS$2(className, stylesObj[key]);
|
|
1597
|
+
document.head.appendChild(styleElement);
|
|
1598
|
+
return {
|
|
1599
|
+
key,
|
|
1600
|
+
className,
|
|
1601
|
+
styleElement,
|
|
1602
|
+
};
|
|
1603
|
+
});
|
|
1604
|
+
setClassNames(convertedStyles.reduce((classNameEntity, convertedStyle) => {
|
|
1605
|
+
return {
|
|
1606
|
+
...classNameEntity,
|
|
1607
|
+
[convertedStyle.key]: convertedStyle.className,
|
|
1608
|
+
};
|
|
1609
|
+
}, {}));
|
|
1610
|
+
return () => {
|
|
1611
|
+
convertedStyles.forEach((convertedStyle) => {
|
|
1612
|
+
document.head.removeChild(convertedStyle.styleElement);
|
|
1613
|
+
});
|
|
1614
|
+
};
|
|
1615
|
+
}, []);
|
|
1616
|
+
return classNames;
|
|
1617
|
+
};
|
|
1618
|
+
|
|
1619
|
+
// Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
|
|
1620
|
+
// Use of this source code is governed by a BSD-style
|
|
1621
|
+
// license that can be found in the LICENSE file.
|
|
1622
|
+
|
|
1567
1623
|
// Copyright 2023 UDSM DHIS2 Lab. All rights reserved.
|
|
1568
1624
|
// Use of this source code is governed by a BSD-style
|
|
1569
1625
|
// license that can be found in the LICENSE file.
|
|
@@ -3364,6 +3420,9 @@ class LineListTableComponent extends ReactWrapperModule {
|
|
|
3364
3420
|
const [filteredColumns, setFilteredColumns] = useState();
|
|
3365
3421
|
const [inputValues, setInputValues] = useState({});
|
|
3366
3422
|
const [filters, setFilters] = useState(false);
|
|
3423
|
+
//TODO: this will be migrated on the parent component
|
|
3424
|
+
const [checkValue, setCheckValue] = useState([]);
|
|
3425
|
+
const [valueMatch, setValuesMatch] = useState(false);
|
|
3367
3426
|
// Store updaters in refs for Angular to access
|
|
3368
3427
|
const updateRefs = useRef({
|
|
3369
3428
|
setProgramIdState,
|
|
@@ -3413,10 +3472,16 @@ class LineListTableComponent extends ReactWrapperModule {
|
|
|
3413
3472
|
const { columns, data, filteredEntityColumns } = getTrackedEntityData(response, this.programId, pager, this.filters);
|
|
3414
3473
|
entityColumns = columns;
|
|
3415
3474
|
entityData = data;
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3475
|
+
//TODO: Should be done on the parent
|
|
3476
|
+
let checkValues = [
|
|
3477
|
+
...new Set(response.data?.trackedEntityInstances?.[0]?.enrollments?.[0]?.events?.flatMap((event) => event.dataValues
|
|
3478
|
+
.filter((dataValue) => /^[A-Za-z]{3}$/.test(dataValue.value))
|
|
3479
|
+
.map((dataValue) => dataValue.value))),
|
|
3480
|
+
];
|
|
3481
|
+
if (checkValues) {
|
|
3482
|
+
// this.firstValue.emit(firstTei);
|
|
3483
|
+
setCheckValue(checkValues);
|
|
3484
|
+
setValuesMatch(!checkValues.includes(this.buttonFilter));
|
|
3420
3485
|
}
|
|
3421
3486
|
// setFilteredColumns(filteredEntityColumns);
|
|
3422
3487
|
// Ensure filter inputs do not disappear when no data is returned
|
|
@@ -3508,9 +3573,12 @@ class LineListTableComponent extends ReactWrapperModule {
|
|
|
3508
3573
|
padding: 8,
|
|
3509
3574
|
marginBottom: 16,
|
|
3510
3575
|
} },
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3576
|
+
valueMatch &&
|
|
3577
|
+
this.dispatchTeis &&
|
|
3578
|
+
(isButtonLoading ? (React__default.createElement(CircularLoader$1, { small: true })) : (
|
|
3579
|
+
//TODO: this implementation should be done where the library will be consumed
|
|
3580
|
+
React__default.createElement(Button$1, { style: {
|
|
3581
|
+
backgroundColor: '#3b82f6',
|
|
3514
3582
|
color: 'white',
|
|
3515
3583
|
padding: '8px 16px',
|
|
3516
3584
|
borderRadius: '4px',
|
|
@@ -3559,7 +3627,7 @@ class LineListTableComponent extends ReactWrapperModule {
|
|
|
3559
3627
|
React__default.createElement(DataTableRow, null, columns.map((col) => (React__default.createElement(DataTableColumnHeader, { key: col.key }, col.label))))),
|
|
3560
3628
|
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
|
|
3561
3629
|
)))))))) : (React__default.createElement(DataTableRow, null,
|
|
3562
|
-
React__default.createElement(DataTableCell, { colSpan: columns.length, style: {
|
|
3630
|
+
React__default.createElement(DataTableCell, { colSpan: columns.length?.toString(), style: {
|
|
3563
3631
|
textAlign: 'center',
|
|
3564
3632
|
fontWeight: 'bold',
|
|
3565
3633
|
color: 'grey',
|
|
@@ -3612,7 +3680,7 @@ class LineListTableComponent extends ReactWrapperModule {
|
|
|
3612
3680
|
this.render();
|
|
3613
3681
|
}
|
|
3614
3682
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.8", ngImport: i0, type: LineListTableComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
3615
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.8", type: LineListTableComponent, isStandalone: false, selector: "app-line-list", inputs: { programId: "programId", orgUnit: "orgUnit", programStageId: "programStageId", actionOptions: "actionOptions", attributeFilters: "attributeFilters", startDate: "startDate", endDate: "endDate", filters: "filters", ouMode: "ouMode", dispatchTeis: "dispatchTeis", showFilterButton: "showFilterButton", isButtonLoading: "isButtonLoading", buttonLabel: "buttonLabel" }, outputs: { actionSelected: "actionSelected", approvalSelected: "approvalSelected", firstValue: "firstValue" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["[data-test=dhis2-uiwidgets-pagination]{display:flex!important;flex-direction:row!important;justify-content:space-between!important;align-items:center!important}\n"] }); }
|
|
3683
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.8", type: LineListTableComponent, isStandalone: false, selector: "app-line-list", inputs: { programId: "programId", orgUnit: "orgUnit", programStageId: "programStageId", actionOptions: "actionOptions", attributeFilters: "attributeFilters", startDate: "startDate", endDate: "endDate", filters: "filters", ouMode: "ouMode", dispatchTeis: "dispatchTeis", showFilterButton: "showFilterButton", isButtonLoading: "isButtonLoading", buttonLabel: "buttonLabel", buttonFilter: "buttonFilter" }, outputs: { actionSelected: "actionSelected", approvalSelected: "approvalSelected", firstValue: "firstValue" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["[data-test=dhis2-uiwidgets-pagination]{display:flex!important;flex-direction:row!important;justify-content:space-between!important;align-items:center!important}\n"] }); }
|
|
3616
3684
|
}
|
|
3617
3685
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.8", ngImport: i0, type: LineListTableComponent, decorators: [{
|
|
3618
3686
|
type: Component,
|
|
@@ -3649,6 +3717,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.8", ngImpor
|
|
|
3649
3717
|
type: Input
|
|
3650
3718
|
}], firstValue: [{
|
|
3651
3719
|
type: Output
|
|
3720
|
+
}], buttonFilter: [{
|
|
3721
|
+
type: Input
|
|
3652
3722
|
}] } });
|
|
3653
3723
|
|
|
3654
3724
|
class LineListModule {
|
|
@@ -3818,7 +3888,7 @@ class DataTableUIComponent extends ReactWrapperModule {
|
|
|
3818
3888
|
React__default.createElement(DropdownMenu, { dropdownOptions: getDropdownOptions(row), onClick: (option) => option.onClick?.(row) }))))))),
|
|
3819
3889
|
React__default.createElement(TableFoot, null,
|
|
3820
3890
|
React__default.createElement(DataTableRow, null,
|
|
3821
|
-
React__default.createElement(DataTableCell, { colSpan: columns.length + 1 })))))));
|
|
3891
|
+
React__default.createElement(DataTableCell, { colSpan: (columns.length || 0 + 1).toString() })))))));
|
|
3822
3892
|
};
|
|
3823
3893
|
}
|
|
3824
3894
|
async ngAfterViewInit() {
|
|
@@ -4801,6 +4871,230 @@ class FormValue {
|
|
|
4801
4871
|
// Use of this source code is governed by a BSD-style
|
|
4802
4872
|
// license that can be found in the LICENSE file.
|
|
4803
4873
|
|
|
4874
|
+
// Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
|
|
4875
|
+
// Use of this source code is governed by a BSD-style
|
|
4876
|
+
// license that can be found in the LICENSE file.
|
|
4877
|
+
const useFieldValidation = (props) => {
|
|
4878
|
+
const { form, touched, field, value } = props;
|
|
4879
|
+
const hasError = useMemo(() => {
|
|
4880
|
+
return touched && (form.get(field.id) || form.get(field.key))?.invalid;
|
|
4881
|
+
}, [value, touched]);
|
|
4882
|
+
const validationError = useMemo(() => {
|
|
4883
|
+
if (!touched) {
|
|
4884
|
+
return undefined;
|
|
4885
|
+
}
|
|
4886
|
+
const error = (form.get(field.id) || form.get(field.key))?.errors;
|
|
4887
|
+
if (!error) {
|
|
4888
|
+
return undefined;
|
|
4889
|
+
}
|
|
4890
|
+
if (error['min']) {
|
|
4891
|
+
return `${field.label || 'Value'} should not be less than ${error['min'].min}!`;
|
|
4892
|
+
}
|
|
4893
|
+
else if (error['max']) {
|
|
4894
|
+
return `${field.label || 'Value'} should not be greater than ${error['max'].max}!`;
|
|
4895
|
+
}
|
|
4896
|
+
else if (error['pattern']) {
|
|
4897
|
+
if (field.type === 'email') {
|
|
4898
|
+
return `The email address provided is not valid`;
|
|
4899
|
+
}
|
|
4900
|
+
else if (field.type === 'tel') {
|
|
4901
|
+
return 'Phone number provided is not valid';
|
|
4902
|
+
}
|
|
4903
|
+
else {
|
|
4904
|
+
return `${field.label || 'Value'} should have appropriate format`;
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
return error['required']
|
|
4908
|
+
? `${field.label || 'Value'} is required`
|
|
4909
|
+
: undefined;
|
|
4910
|
+
}, [hasError]);
|
|
4911
|
+
return { hasError, validationError };
|
|
4912
|
+
};
|
|
4913
|
+
|
|
4914
|
+
// Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
|
|
4915
|
+
// Use of this source code is governed by a BSD-style
|
|
4916
|
+
// license that can be found in the LICENSE file.
|
|
4917
|
+
|
|
4918
|
+
// Copyright 2025 UDSM DHIS2 Lab. All rights reserved.
|
|
4919
|
+
// Use of this source code is governed by a BSD-style
|
|
4920
|
+
// license that can be found in the LICENSE file.
|
|
4921
|
+
// @flow
|
|
4922
|
+
const orgUnitFieldStyles = {
|
|
4923
|
+
container: {
|
|
4924
|
+
border: '1px solid',
|
|
4925
|
+
borderColor: colors$1.grey400,
|
|
4926
|
+
borderRadius: '3px',
|
|
4927
|
+
height: '100%',
|
|
4928
|
+
width: '100%',
|
|
4929
|
+
zIndex: 0,
|
|
4930
|
+
backgroundColor: 'white',
|
|
4931
|
+
},
|
|
4932
|
+
searchField: {
|
|
4933
|
+
height: '40px',
|
|
4934
|
+
overflow: 'hidden',
|
|
4935
|
+
borderBottomLeftRadius: 0,
|
|
4936
|
+
borderBottomRightRadius: 0,
|
|
4937
|
+
boxShadow: 'none',
|
|
4938
|
+
border: 'none',
|
|
4939
|
+
},
|
|
4940
|
+
debounceFieldContainer: {
|
|
4941
|
+
padding: '8px',
|
|
4942
|
+
background: colors$1.grey100,
|
|
4943
|
+
borderTopLeftRadius: '3px',
|
|
4944
|
+
borderTopRightRadius: '3px',
|
|
4945
|
+
},
|
|
4946
|
+
orgUnitTreeContainer: {
|
|
4947
|
+
borderBottomLeftRadius: '3px',
|
|
4948
|
+
borderBottomRightRadius: '3px',
|
|
4949
|
+
overflow: 'auto',
|
|
4950
|
+
},
|
|
4951
|
+
orgUnitTreeLoader: {
|
|
4952
|
+
padding: '8px',
|
|
4953
|
+
},
|
|
4954
|
+
orgUnitFieldLabel: {
|
|
4955
|
+
marginBottom: '8px',
|
|
4956
|
+
display: 'block',
|
|
4957
|
+
boxSizing: 'border-box',
|
|
4958
|
+
fontSize: '14px',
|
|
4959
|
+
lineHeight: '19px',
|
|
4960
|
+
color: 'rgb(33, 41, 52)',
|
|
4961
|
+
padding: '0',
|
|
4962
|
+
},
|
|
4963
|
+
orgUnitFieldRequiredLabel: {
|
|
4964
|
+
paddingInlineStart: '4px',
|
|
4965
|
+
},
|
|
4966
|
+
};
|
|
4967
|
+
const OrgUnitFormField = (props) => {
|
|
4968
|
+
const { onSelectOrgUnit, label, required, selected, disabled, key } = props;
|
|
4969
|
+
const d2 = window?.d2Web;
|
|
4970
|
+
const classes = useDynamicStyles$2(orgUnitFieldStyles);
|
|
4971
|
+
const config = useMemo(() => {
|
|
4972
|
+
return d2.systemInfo?.toInitObject();
|
|
4973
|
+
}, []);
|
|
4974
|
+
const rootOrgUnits = useMemo(() => {
|
|
4975
|
+
return d2.currentUser?.organisationUnits || [];
|
|
4976
|
+
}, []);
|
|
4977
|
+
const getExpandedItems = () => {
|
|
4978
|
+
if (rootOrgUnits && rootOrgUnits.length === 1) {
|
|
4979
|
+
return [`/${rootOrgUnits[0].id}`];
|
|
4980
|
+
}
|
|
4981
|
+
else if (rootOrgUnits?.length > 1) {
|
|
4982
|
+
return rootOrgUnits.map((root) => root.path);
|
|
4983
|
+
}
|
|
4984
|
+
return undefined;
|
|
4985
|
+
};
|
|
4986
|
+
const initiallyExpanded = getExpandedItems();
|
|
4987
|
+
const [searchText, setSearchText] = useState(undefined);
|
|
4988
|
+
const [loading, setLoading] = useState(true);
|
|
4989
|
+
const [searchLoading, setSearchLoading] = useState();
|
|
4990
|
+
const [searchData, setSearchData] = useState();
|
|
4991
|
+
const [selectedOrgUnit, setSelectedOrgUnit] = React__default.useState();
|
|
4992
|
+
const [expanded, setExpanded] = React__default.useState(initiallyExpanded);
|
|
4993
|
+
const [showOrgUnitTree, setShowOrgUnitTree] = useState(!selected);
|
|
4994
|
+
useEffect(() => {
|
|
4995
|
+
setLoading(true);
|
|
4996
|
+
if (selected) {
|
|
4997
|
+
d2.httpInstance
|
|
4998
|
+
.get(`organisationUnits/${selected}.json?fields=id,name,path,code,ancestors[id,name]`)
|
|
4999
|
+
.then((response) => {
|
|
5000
|
+
if (response.data) {
|
|
5001
|
+
setSelectedOrgUnit(response.data);
|
|
5002
|
+
handleExpand({ path: response.data['path'] });
|
|
5003
|
+
setLoading(false);
|
|
5004
|
+
}
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
else {
|
|
5008
|
+
setLoading(false);
|
|
5009
|
+
}
|
|
5010
|
+
}, [selected]);
|
|
5011
|
+
const highlighted = useMemo(() => {
|
|
5012
|
+
if (selectedOrgUnit) {
|
|
5013
|
+
return [selectedOrgUnit['path']];
|
|
5014
|
+
}
|
|
5015
|
+
return undefined;
|
|
5016
|
+
}, [selectedOrgUnit]);
|
|
5017
|
+
const selectedOrgUnitLabel = useMemo(() => {
|
|
5018
|
+
if (selectedOrgUnit) {
|
|
5019
|
+
return [
|
|
5020
|
+
...(selectedOrgUnit['ancestors'] || []),
|
|
5021
|
+
selectedOrgUnit,
|
|
5022
|
+
]
|
|
5023
|
+
.map((orgUnit) => orgUnit['name'])
|
|
5024
|
+
.join(' / ');
|
|
5025
|
+
}
|
|
5026
|
+
return '';
|
|
5027
|
+
}, [selectedOrgUnit]);
|
|
5028
|
+
const handleExpand = ({ path }) => {
|
|
5029
|
+
if (expanded && !expanded.includes(path)) {
|
|
5030
|
+
setExpanded([...expanded, path]);
|
|
5031
|
+
}
|
|
5032
|
+
};
|
|
5033
|
+
const handleCollapse = ({ path }) => {
|
|
5034
|
+
const pathIndex = expanded?.indexOf(path);
|
|
5035
|
+
if (pathIndex && pathIndex !== -1 && expanded) {
|
|
5036
|
+
const updatedExpanded = pathIndex === 0
|
|
5037
|
+
? expanded.slice(1)
|
|
5038
|
+
: [...expanded.slice(0, pathIndex), ...expanded.slice(pathIndex + 1)];
|
|
5039
|
+
setExpanded(updatedExpanded);
|
|
5040
|
+
}
|
|
5041
|
+
};
|
|
5042
|
+
const handleOrgUnitSearch = async (query) => {
|
|
5043
|
+
setSearchLoading(true);
|
|
5044
|
+
try {
|
|
5045
|
+
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`);
|
|
5046
|
+
setSearchData((searchResponse.data || {})['organisationUnits']);
|
|
5047
|
+
setSearchLoading(false);
|
|
5048
|
+
}
|
|
5049
|
+
catch (e) {
|
|
5050
|
+
setSearchLoading(false);
|
|
5051
|
+
}
|
|
5052
|
+
};
|
|
5053
|
+
const debouncedSearch = useCallback(debounce((value) => {
|
|
5054
|
+
handleOrgUnitSearch(value);
|
|
5055
|
+
}, 500), []);
|
|
5056
|
+
const handleFilterChange = (event) => {
|
|
5057
|
+
setSearchText(event.value);
|
|
5058
|
+
debouncedSearch(event.value);
|
|
5059
|
+
};
|
|
5060
|
+
const ready = useMemo(() => {
|
|
5061
|
+
return searchText?.length ? !searchLoading : !loading;
|
|
5062
|
+
}, [searchText, searchLoading, loading]);
|
|
5063
|
+
const renderOrgUnitTree = () => {
|
|
5064
|
+
if (searchText && searchText?.length > 0) {
|
|
5065
|
+
if (searchLoading) {
|
|
5066
|
+
return (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
|
|
5067
|
+
React__default.createElement(CircularLoader$1, { small: true })));
|
|
5068
|
+
}
|
|
5069
|
+
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) => {
|
|
5070
|
+
setSelectedOrgUnit(event);
|
|
5071
|
+
onSelectOrgUnit(event.id);
|
|
5072
|
+
} })));
|
|
5073
|
+
}
|
|
5074
|
+
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) => {
|
|
5075
|
+
setSelectedOrgUnit(event);
|
|
5076
|
+
onSelectOrgUnit(event.id);
|
|
5077
|
+
} }));
|
|
5078
|
+
};
|
|
5079
|
+
const handleBlur = () => {
|
|
5080
|
+
// onBlur && onBlur(null);
|
|
5081
|
+
};
|
|
5082
|
+
return (config && (React__default.createElement(Provider, { config: config, plugin: false, parentAlertsAdd: undefined, showAlertsInPlugin: false },
|
|
5083
|
+
React__default.createElement("div", null,
|
|
5084
|
+
label && (React__default.createElement("div", { className: classes.orgUnitFieldLabel },
|
|
5085
|
+
React__default.createElement("span", null, label),
|
|
5086
|
+
required ? (React__default.createElement("span", { className: classes.orgUnitFieldRequiredLabel }, "*")) : (React__default.createElement(React__default.Fragment, null)))),
|
|
5087
|
+
selected && !showOrgUnitTree ? (selectedOrgUnit ? (React__default.createElement(Tooltip, { content: selectedOrgUnitLabel },
|
|
5088
|
+
React__default.createElement(Chip, { onRemove: () => {
|
|
5089
|
+
setShowOrgUnitTree(true);
|
|
5090
|
+
} }, selectedOrgUnit.name))) : (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
|
|
5091
|
+
React__default.createElement(CircularLoader$1, { small: true })))) : (React__default.createElement("div", { className: classes.container },
|
|
5092
|
+
React__default.createElement("div", { className: classes.debounceFieldContainer },
|
|
5093
|
+
React__default.createElement(InputField$1, { placeholder: "Search", value: searchText, disabled: disabled, onChange: handleFilterChange })),
|
|
5094
|
+
loading ? (React__default.createElement("div", { className: classes.orgUnitTreeLoader },
|
|
5095
|
+
React__default.createElement(CircularLoader$1, { small: true }))) : (React__default.createElement("div", { className: classes.orgUnitTreeContainer, style: { maxHeight: '200px', overflowY: 'auto' } }, renderOrgUnitTree()))))))));
|
|
5096
|
+
};
|
|
5097
|
+
|
|
4804
5098
|
// Copyright 2024 UDSM DHIS2 Lab. All rights reserved.
|
|
4805
5099
|
// Use of this source code is governed by a BSD-style
|
|
4806
5100
|
// license that can be found in the LICENSE file.
|
|
@@ -4890,45 +5184,15 @@ class BaseFormFieldComponent extends ReactWrapperModule {
|
|
|
4890
5184
|
}
|
|
4891
5185
|
return [];
|
|
4892
5186
|
}, [value]);
|
|
4893
|
-
const hasError =
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
if (!touched) {
|
|
4900
|
-
return undefined;
|
|
4901
|
-
}
|
|
4902
|
-
const field = this.field();
|
|
4903
|
-
const error = (this.form().get(field.id) || this.form().get(field.key))
|
|
4904
|
-
?.errors;
|
|
4905
|
-
if (!error) {
|
|
4906
|
-
return undefined;
|
|
4907
|
-
}
|
|
4908
|
-
if (error['min']) {
|
|
4909
|
-
return `${this.label() || 'Value'} should not be less than ${error['min'].min}!`;
|
|
4910
|
-
}
|
|
4911
|
-
else if (error['max']) {
|
|
4912
|
-
return `${this.label() || 'Value'} should not be greater than ${error['max'].max}!`;
|
|
4913
|
-
}
|
|
4914
|
-
else if (error['pattern']) {
|
|
4915
|
-
if (field.type === 'email') {
|
|
4916
|
-
return `The email address provided is not valid`;
|
|
4917
|
-
}
|
|
4918
|
-
else if (field.type === 'tel') {
|
|
4919
|
-
return 'Phone number provided is not valid';
|
|
4920
|
-
}
|
|
4921
|
-
else {
|
|
4922
|
-
return `${this.label() || 'Value'} should have appropriate format`;
|
|
4923
|
-
}
|
|
4924
|
-
}
|
|
4925
|
-
return error['required']
|
|
4926
|
-
? `${this.label() || 'Value'} is required`
|
|
4927
|
-
: undefined;
|
|
4928
|
-
}, [hasError]);
|
|
5187
|
+
const { hasError, validationError } = useFieldValidation({
|
|
5188
|
+
field: this.field(),
|
|
5189
|
+
form: this.form(),
|
|
5190
|
+
value,
|
|
5191
|
+
touched,
|
|
5192
|
+
});
|
|
4929
5193
|
switch (this.field().controlType) {
|
|
4930
5194
|
case 'textarea':
|
|
4931
|
-
return (React__default.createElement(TextAreaField, { error: hasError, validationText: validationError,
|
|
5195
|
+
return (React__default.createElement(TextAreaField, { error: hasError, validationText: validationError, inputWidth: this.fieldConfig()?.inputWidth, required: this.field().required, name: this.field().id, label: this.label(), rows: 5, placeholder: this.placeholder(), value: value, onChange: (event) => {
|
|
4932
5196
|
this.ngZone.run(() => {
|
|
4933
5197
|
(this.form().get(this.field().id) ||
|
|
4934
5198
|
this.form().get(this.field().key))?.setValue(event.value);
|
|
@@ -4945,34 +5209,14 @@ class BaseFormFieldComponent extends ReactWrapperModule {
|
|
|
4945
5209
|
});
|
|
4946
5210
|
} }));
|
|
4947
5211
|
case 'org-unit':
|
|
4948
|
-
return (React__default.createElement(
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
});
|
|
4955
|
-
setTouched(true);
|
|
4956
|
-
} }),
|
|
4957
|
-
showOrgUnit && (React__default.createElement(this.FieldOrgUnitSelector, { selectedOrgUnits: [], onSelectOrgUnit: (selectedOrgUnits) => {
|
|
4958
|
-
if ((selectedOrgUnits || [])[0]) {
|
|
4959
|
-
const selectedOrgUnit = selectedOrgUnits[0];
|
|
4960
|
-
setDisplayValue(selectedOrgUnit.name);
|
|
4961
|
-
setValue(selectedOrgUnit.id);
|
|
4962
|
-
setShowOrgUnit(false);
|
|
4963
|
-
setTouched(true);
|
|
4964
|
-
this.ngZone.run(() => {
|
|
4965
|
-
(this.form().get(this.field().id) ||
|
|
4966
|
-
this.form().get(this.field().key))?.setValue(selectedOrgUnit.id);
|
|
4967
|
-
this.immediateUpdate.emit({
|
|
4968
|
-
form: this.form(),
|
|
4969
|
-
value: selectedOrgUnit.id,
|
|
4970
|
-
});
|
|
4971
|
-
});
|
|
4972
|
-
}
|
|
4973
|
-
} }))));
|
|
5212
|
+
return (React__default.createElement(OrgUnitFormField, { label: this.label(), key: this.field().id, required: this.field().required, onSelectOrgUnit: (selectedOrgUnit) => {
|
|
5213
|
+
setValue(selectedOrgUnit);
|
|
5214
|
+
this.ngZone.run(() => {
|
|
5215
|
+
this.update.emit({ form: this.form(), value });
|
|
5216
|
+
});
|
|
5217
|
+
}, selected: value, onBlur: (event) => { } }));
|
|
4974
5218
|
case 'transfer':
|
|
4975
|
-
return (React__default.createElement(Transfer, { filterable: true, filterPlaceholder: "Search", selected: selected,
|
|
5219
|
+
return (React__default.createElement(Transfer, { filterable: true, filterPlaceholder: "Search", selected: selected, leftHeader: React__default.createElement("div", { style: {
|
|
4976
5220
|
fontSize: 14,
|
|
4977
5221
|
padding: '8px 4px',
|
|
4978
5222
|
} }, this.field().availableOptionsLabel), rightHeader: React__default.createElement("div", { style: {
|
|
@@ -4989,7 +5233,10 @@ class BaseFormFieldComponent extends ReactWrapperModule {
|
|
|
4989
5233
|
this.update.emit({ form: this.form(), value });
|
|
4990
5234
|
} }));
|
|
4991
5235
|
case 'checkbox':
|
|
4992
|
-
return (React__default.createElement(Checkbox, { checked: Boolean(value), error: hasError, label: this.label(), name: this.field().id, disabled: this.field()?.disabled ||
|
|
5236
|
+
return (React__default.createElement(Checkbox, { checked: Boolean(value), error: hasError, label: this.label(), name: this.field().id, disabled: this.field()?.disabled ||
|
|
5237
|
+
this.field()?.generated ||
|
|
5238
|
+
this.field()?.unique ||
|
|
5239
|
+
false, onChange: (event) => {
|
|
4993
5240
|
this.ngZone.run(() => {
|
|
4994
5241
|
(this.form().get(this.field().id) ||
|
|
4995
5242
|
this.form().get(this.field().key))?.setValue(event.checked);
|
|
@@ -5002,7 +5249,10 @@ class BaseFormFieldComponent extends ReactWrapperModule {
|
|
|
5002
5249
|
});
|
|
5003
5250
|
} }));
|
|
5004
5251
|
case 'dropdown':
|
|
5005
|
-
return (React__default.createElement(SingleSelectField, { filterable: (this.field().options || []).length > 5, clearable: true, error: hasError, validationText: validationError, inputWidth: this.fieldConfig()?.inputWidth, disabled: this.field()?.disabled ||
|
|
5252
|
+
return (React__default.createElement(SingleSelectField, { filterable: (this.field().options || []).length > 5, clearable: true, error: hasError, validationText: validationError, inputWidth: this.fieldConfig()?.inputWidth, disabled: this.field()?.disabled ||
|
|
5253
|
+
this.field()?.generated ||
|
|
5254
|
+
this.field()?.unique ||
|
|
5255
|
+
false, required: this.field().required, className: "select", label: this.label(), selected: value, onChange: (event) => {
|
|
5006
5256
|
this.ngZone.run(() => {
|
|
5007
5257
|
(this.form().get(this.field().id) ||
|
|
5008
5258
|
this.form().get(this.field().key))?.setValue(event.selected);
|
|
@@ -5015,7 +5265,10 @@ class BaseFormFieldComponent extends ReactWrapperModule {
|
|
|
5015
5265
|
setTouched(true);
|
|
5016
5266
|
} }, (this.field().options || []).map((option) => (React__default.createElement(SingleSelectOption, { key: crypto.randomUUID() || option.key, label: option.label, value: option.value })))));
|
|
5017
5267
|
case 'multi-dropdown':
|
|
5018
|
-
return (React__default.createElement(MultiSelectField, { clearText: "Clear", clearable: true, empty: "No data found", filterable: (this.field().options || []).length > 5, filterPlaceholder: "Type to filter options", error: hasError, validationText: validationError, inputWidth: this.fieldConfig()?.inputWidth, disabled: this.field()?.disabled ||
|
|
5268
|
+
return (React__default.createElement(MultiSelectField, { clearText: "Clear", clearable: true, empty: "No data found", filterable: (this.field().options || []).length > 5, filterPlaceholder: "Type to filter options", error: hasError, validationText: validationError, inputWidth: this.fieldConfig()?.inputWidth, disabled: this.field()?.disabled ||
|
|
5269
|
+
this.field()?.generated ||
|
|
5270
|
+
this.field()?.unique ||
|
|
5271
|
+
false, label: this.label(), required: this.field().required, loadingText: "Loading options", noMatchText: "No options found", onChange: (event) => {
|
|
5019
5272
|
const selectedValue = (event.selected || []).join(',');
|
|
5020
5273
|
this.ngZone.run(() => {
|
|
5021
5274
|
(this.form().get(this.field().id) ||
|
|
@@ -5029,7 +5282,7 @@ class BaseFormFieldComponent extends ReactWrapperModule {
|
|
|
5029
5282
|
setTouched(true);
|
|
5030
5283
|
}, selected: arrayValue }, (this.field().options || []).map((option) => (React__default.createElement(MultiSelectOption, { key: option.key, label: option.label, value: option.value })))));
|
|
5031
5284
|
case 'file':
|
|
5032
|
-
return (React__default.createElement(FileInputField, { accept: "*", buttonLabel: "Upload a file", label: this.label(), name: this.field().id,
|
|
5285
|
+
return (React__default.createElement(FileInputField, { accept: "*", buttonLabel: "Upload a file", label: this.label(), name: this.field().id, onChange: (event) => {
|
|
5033
5286
|
this.uploadedFiles.push((event?.files || [])[0]);
|
|
5034
5287
|
this.ngZone.run(() => {
|
|
5035
5288
|
this.form().get(this.field().id) ||
|
|
@@ -5051,7 +5304,13 @@ class BaseFormFieldComponent extends ReactWrapperModule {
|
|
|
5051
5304
|
});
|
|
5052
5305
|
}, removeText: "Remove" })))));
|
|
5053
5306
|
default:
|
|
5054
|
-
return (React__default.createElement(InputField$1, { error: hasError, validationText: validationError, type: this.field().type, inputWidth: this.fieldConfig()?.inputWidth, required: this.field().required, name: this.field().id, label: this.label(), min: this.field().min?.toString(), max: this.field().max?.toString(), placeholder: this.placeholder(), value: value, disabled: this.field()?.disabled ||
|
|
5307
|
+
return (React__default.createElement(InputField$1, { error: hasError, validationText: validationError, type: this.field().type, inputWidth: this.fieldConfig()?.inputWidth, required: this.field().required, name: this.field().id, label: this.label(), min: this.field().min?.toString(), max: this.field().max?.toString(), placeholder: this.placeholder(), value: value, disabled: this.field()?.disabled ||
|
|
5308
|
+
this.field()?.generated ||
|
|
5309
|
+
this.field()?.unique ||
|
|
5310
|
+
false, readOnly: this.field()?.disabled ||
|
|
5311
|
+
this.field()?.generated ||
|
|
5312
|
+
this.field()?.unique ||
|
|
5313
|
+
false, onChange: (event) => {
|
|
5055
5314
|
this.ngZone.run(() => {
|
|
5056
5315
|
(this.form().get(this.field().id) ||
|
|
5057
5316
|
this.form().get(this.field().key))?.setValue(event.value);
|
|
@@ -5881,8 +6140,8 @@ class SelectionFiltersComponent extends ReactWrapperModule {
|
|
|
5881
6140
|
setTouched(true);
|
|
5882
6141
|
}
|
|
5883
6142
|
} }))),
|
|
5884
|
-
React__default.createElement(InputField$1, { className: "input-field", label: "Start Date", type: "
|
|
5885
|
-
React__default.createElement(InputField$1, { className: "input-field", label: "End Date", type: "
|
|
6143
|
+
React__default.createElement(InputField$1, { className: "input-field", label: "Start Date", type: "date", value: filters.startDate, onChange: (event) => setFilters({ ...filters, startDate: event.value }) }),
|
|
6144
|
+
React__default.createElement(InputField$1, { className: "input-field", label: "End Date", type: "date", value: filters.endDate, onChange: (event) => setFilters({ ...filters, endDate: event.value }) }),
|
|
5886
6145
|
React__default.createElement("div", { style: {
|
|
5887
6146
|
display: 'flex',
|
|
5888
6147
|
flexDirection: 'column',
|
|
@@ -5901,12 +6160,12 @@ class SelectionFiltersComponent extends ReactWrapperModule {
|
|
|
5901
6160
|
display: 'grid',
|
|
5902
6161
|
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
5903
6162
|
gap: '16px',
|
|
5904
|
-
} }, filters.programAttributesFilters.map((filter, index) => (React__default.createElement("div", { key: index, style: { gridColumn: getAttributeColumnSpan(index) } }, filter.hasOptions && filter.options.length > 0 ? (React__default.createElement(SingleSelectField, { className: "single-select", label: filter.name || 'Select Option',
|
|
6163
|
+
} }, filters.programAttributesFilters.map((filter, index) => (React__default.createElement("div", { key: index, style: { gridColumn: getAttributeColumnSpan(index) } }, filter.hasOptions && filter.options.length > 0 ? (React__default.createElement(SingleSelectField, { className: "single-select", label: filter.name || 'Select Option', selected: filter.value, onChange: (event) => handleAttributeChange(index, event.selected) }, filter.options.map((option, i) => (React__default.createElement(SingleSelectOption, { key: crypto.randomUUID() || i, label: option.name, value: option.code }))))) : (React__default.createElement(InputField$1, { className: "input-field", label: filter.name || 'Program name', type: filter.valueType, value: filter.value, onChange: (event) => handleAttributeChange(index, event.value) })))))),
|
|
5905
6164
|
React__default.createElement("div", { style: {
|
|
5906
6165
|
display: 'grid',
|
|
5907
6166
|
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
5908
6167
|
gap: '16px',
|
|
5909
|
-
} }, filters.programStageDataElementFilters.map((filter, index) => (React__default.createElement("div", { key: index, style: { gridColumn: getDataElementColumnSpan(index) } }, filter.hasOptions && filter.options.length > 0 ? (React__default.createElement(SingleSelectField, { className: "single-select", label: filter.name || 'Aggregation type',
|
|
6168
|
+
} }, filters.programStageDataElementFilters.map((filter, index) => (React__default.createElement("div", { key: index, style: { gridColumn: getDataElementColumnSpan(index) } }, filter.hasOptions && filter.options.length > 0 ? (React__default.createElement(SingleSelectField, { className: "single-select", label: filter.name || 'Aggregation type', selected: filter.value, onChange: (event) => handleDataElementChange(index, event.selected) }, filter.options.map((option, i) => (React__default.createElement(SingleSelectOption, { key: crypto.randomUUID() || i, label: option.name, value: option.code }))))) : (React__default.createElement(InputField$1, { className: "input-field", type: filter.valueType, label: filter.name || 'Program name', value: filter.value, onChange: (event) => handleDataElementChange(index, event.value) })))))),
|
|
5910
6169
|
React__default.createElement("div", { style: { textAlign: 'right', marginTop: '16px' } },
|
|
5911
6170
|
React__default.createElement(Button$1, { onClick: handleSearch }, "Search"))))));
|
|
5912
6171
|
};
|