@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.
@@ -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
- let firstTei = response.data
3417
- ?.trackedEntityInstances?.[0]?.trackedEntityInstance;
3418
- if (firstTei) {
3419
- this.firstValue.emit(firstTei);
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
- this.dispatchTeis &&
3512
- (isButtonLoading ? (React__default.createElement(CircularLoader$1, { small: true })) : (React__default.createElement(Button$1, { style: {
3513
- backgroundColor: '#3b82f6', // Blue color
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 = useMemo(() => {
4894
- return (touched &&
4895
- (this.form().get(this.field().id) ||
4896
- this.form().get(this.field().key))?.invalid);
4897
- }, [value, touched]);
4898
- const validationError = useMemo(() => {
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, 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(), disabled: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, placeholder: this.placeholder(), value: value, onChange: (event) => {
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(React__default.Fragment, null,
4949
- React__default.createElement(InputField$1, { error: hasError, validationText: validationError, type: this.field().type, prefixIcon: React__default.createElement(IconDimensionOrgUnit16, null), required: this.field().required, name: this.field().id, label: this.label(), placeholder: this.placeholder(), value: displayValue, disabled: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, onFocus: () => {
4950
- setShowOrgUnit(true);
4951
- }, onBlur: () => {
4952
- this.ngZone.run(() => {
4953
- this.update.emit({ form: this.form(), value });
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, error: hasError, validationText: validationError, type: this.field().type, inputWidth: this.fieldConfig()?.inputWidth, disabled: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, required: this.field().required, name: this.field().id, label: this.label(), placeholder: this.placeholder(), value: selected, leftHeader: React__default.createElement("div", { style: {
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 || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, onChange: (event) => {
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 || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, required: this.field().required, name: this.field().id, className: "select", label: this.label(), selected: value, onChange: (event) => {
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 || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, label: this.label(), name: this.field().id, required: this.field().required, loadingText: "Loading options", noMatchText: "No options found", onChange: (event) => {
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, disabled: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, files: this.uploadedFiles, onChange: (event) => {
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 || this.field()?.generated || this.field()?.unique || false, readOnly: this.field()?.disabled || this.field()?.generated || this.field()?.unique || false, onChange: (event) => {
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: "DATE", value: filters.startDate, onChange: (event) => setFilters({ ...filters, startDate: event.value }), style: { width: '100%', height: '100%' } }),
5885
- React__default.createElement(InputField$1, { className: "input-field", label: "End Date", type: "DATE", value: filters.endDate, onChange: (event) => setFilters({ ...filters, endDate: event.value }), style: { width: '100%', height: '100%' } }),
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', type: filter.valueType, 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) })))))),
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', type: filter.valueType, 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) })))))),
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
  };