diff --git a/.cursor/rules/code-guidelines.mdc b/.cursor/rules/code-guidelines.mdc new file mode 100644 index 0000000000..6304eca4fb --- /dev/null +++ b/.cursor/rules/code-guidelines.mdc @@ -0,0 +1,22 @@ +--- +description: General code guidelines for bugs and features. +globs: src/*.js +alwaysApply: false +--- + +## Logging +When logging things to the browser console please use the following format. + +``` +import log from 'loglevel'; +import { errorCreator } from 'capture-core-utils'; + +log.error( + errorCreator('Message to be logged')( + { + // Any details + }, + ), +); + +``` \ No newline at end of file diff --git a/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.component.js b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.component.js new file mode 100644 index 0000000000..2c31e8f7ed --- /dev/null +++ b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.component.js @@ -0,0 +1,61 @@ +// @flow +import React from 'react'; +import { withStyles } from '@material-ui/core'; +import { colors, spacers } from '@dhis2/ui'; +import i18n from '@dhis2/d2-i18n'; +import { SingleEventRegistrationEntry } from '../DataEntries'; +import type { Props } from './EventRegistrationEntryWrapper.types'; + +const getStyles = () => ({ + container: { + marginBottom: spacers.dp12, + padding: spacers.dp16, + background: colors.white, + border: '1px solid', + borderColor: colors.grey400, + borderRadius: 3, + }, + title: { + padding: '8px 0 0px 8px', + fontWeight: 500, + marginBottom: spacers.dp16, + }, + flexContainer: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + flexWrap: 'wrap', + }, + flexItem: { + flex: 1, + minWidth: '500px', + }, + noAccessContainer: { + padding: spacers.dp16, + }, +}); + +const EventRegistrationEntryWrapperPlain = ({ + classes, + selectedScopeId, + dataEntryId, + eventAccess, +}: Props) => { + if (!eventAccess?.write) { + return ( +
+ {i18n.t('You don\'t have access to create an event in the current selections')} +
+ ); + } + + return ( + + ); +}; + +export const EventRegistrationEntryWrapperComponent = withStyles(getStyles)(EventRegistrationEntryWrapperPlain); diff --git a/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.container.js b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.container.js new file mode 100644 index 0000000000..6ecb28ad7d --- /dev/null +++ b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.container.js @@ -0,0 +1,56 @@ +// @flow +import { connect, useDispatch, useSelector } from 'react-redux'; +import * as React from 'react'; +import { useEffect, useRef } from 'react'; +import { compose } from 'redux'; +import { batchActions } from 'redux-batched-actions'; +import { EventRegistrationEntryWrapperComponent } from './EventRegistrationEntryWrapper.component'; +import { withLoadingIndicator } from '../../HOC/withLoadingIndicator'; +import type { ContainerProps, StateProps, ReduxState } from './EventRegistrationEntryWrapper.types'; +import { getOpenDataEntryActions } from '../DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry'; +import { useCategoryCombinations } from '../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; +import { makeEventAccessSelector } from './selectors'; +import { itemId } from './constants'; + +const makeMapStateToProps = () => { + const eventAccessSelector = makeEventAccessSelector(); + return (state: ReduxState, ownProps: ContainerProps): StateProps => ({ + selectedScopeId: ownProps.selectedScopeId, + dataEntryId: ownProps.dataEntryId, + orgUnitId: state.currentSelections.orgUnitId, + ready: state.dataEntries[ownProps.dataEntryId]?.itemId === itemId, + eventAccess: eventAccessSelector(state), + }); +}; + +const openSingleEventDataEntry = (InnerComponent: React.ComponentType) => ( + (props: ContainerProps) => { + const hasRun = useRef(false); + const { selectedScopeId } = props; + const dispatch = useDispatch(); + const selectedCategories = useSelector((state: ReduxState) => state.currentSelections.categories); + const { isLoading, programCategory } = useCategoryCombinations(selectedScopeId); + + useEffect(() => { + if (!isLoading && !hasRun.current) { + dispatch( + batchActions([ + ...getOpenDataEntryActions(programCategory, selectedCategories), + ]), + ); + hasRun.current = true; + } + }, [selectedCategories, dispatch, isLoading, programCategory]); + + return ( + + ); + }); + +export const EventRegistrationEntryWrapper = compose( + openSingleEventDataEntry, + connect(makeMapStateToProps(), () => ({})), + withLoadingIndicator(), +)(EventRegistrationEntryWrapperComponent); diff --git a/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.types.js b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.types.js new file mode 100644 index 0000000000..8804fb77d3 --- /dev/null +++ b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/EventRegistrationEntryWrapper.types.js @@ -0,0 +1,30 @@ +// @flow +export type Props = {| + classes: Object, + selectedScopeId: string, + dataEntryId: string, + orgUnitId: string, + eventAccess?: {| + read: boolean, + write: boolean, + |}, +|}; + +export type ContainerProps = {| + selectedScopeId: string, + dataEntryId: string, +|}; + +export type StateProps = {| + selectedScopeId: string, + orgUnitId: string, + dataEntryId: string, + eventAccess: {| + read: boolean, + write: boolean, + |}, + ready: boolean, +|}; + +export type ReduxState = Object; +export type MapStateToProps = (ReduxState, ContainerProps) => StateProps; diff --git a/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/constants.js b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/constants.js new file mode 100644 index 0000000000..5ee70343d8 --- /dev/null +++ b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/constants.js @@ -0,0 +1,5 @@ +// @flow + +// Constants duplicated from SingleEventRegistrationEntry +export const dataEntryId = 'singleEvent'; +export const itemId = 'newEvent'; diff --git a/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/index.js b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/index.js new file mode 100644 index 0000000000..c5a906b67a --- /dev/null +++ b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/index.js @@ -0,0 +1,2 @@ +// @flow +export { EventRegistrationEntryWrapper } from './EventRegistrationEntryWrapper.container'; diff --git a/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/selectors.js b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/selectors.js new file mode 100644 index 0000000000..4ac674b951 --- /dev/null +++ b/src/core_modules/capture-core/components/EventRegistrationEntryWrapper/selectors.js @@ -0,0 +1,17 @@ +// @flow + +import { createSelector } from 'reselect'; +import { getProgramEventAccess } from '../../metaData'; + +const programIdSelector = state => state.currentSelections.programId; +const categoriesMetaSelector = state => state.currentSelections.categoriesMeta; +const programStageIdSelector = state => state.currentSelections.stageId; + +// $FlowFixMe[missing-annot] automated comment +export const makeEventAccessSelector = () => createSelector( + programIdSelector, + categoriesMetaSelector, + programStageIdSelector, + (programId: string, categoriesMeta: ?Object, programStageId: ?string) => + programId && getProgramEventAccess(programId, programStageId, categoriesMeta), +); diff --git a/src/core_modules/capture-core/components/ListView/types/listView.types.js b/src/core_modules/capture-core/components/ListView/types/listView.types.js index c367075b4e..2c5e13e059 100644 --- a/src/core_modules/capture-core/components/ListView/types/listView.types.js +++ b/src/core_modules/capture-core/components/ListView/types/listView.types.js @@ -104,6 +104,7 @@ export type InterfaceProps = $ReadOnly<{| onSetColumnOrder: SetColumnOrder, onSort: Sort, onUpdateFilter: UpdateFilter, + onChangeTemplate?: (selectedTemplateId?: string) => void, rowIdKey: string, rowsPerPage: number, sortById: string, diff --git a/src/core_modules/capture-core/components/Pages/MainPage/EventWorkingListsInit/EventWorkingListsInit.container.type.js b/src/core_modules/capture-core/components/Pages/MainPage/EventWorkingListsInit/EventWorkingListsInit.container.type.js index 87a92ad444..cd33f369b1 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/EventWorkingListsInit/EventWorkingListsInit.container.type.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/EventWorkingListsInit/EventWorkingListsInit.container.type.js @@ -1,5 +1,7 @@ // @flow export type Props = $ReadOnly<{| programId: string, - orgUnitId: string + orgUnitId: string, + selectedTemplateId?: string, + onChangeTemplate?: (selectedTemplateId?: string) => void |}>; diff --git a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js index 45c7318ad0..ceaf9552ab 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.component.js @@ -12,10 +12,11 @@ import { WithoutCategorySelectedMessage } from './WithoutCategorySelectedMessage import { withErrorMessageHandler, withLoadingIndicator } from '../../../HOC'; import { SearchBox } from '../../SearchBox'; import { TemplateSelector } from '../../TemplateSelector'; +import { NoSelectionsInfoBox } from './NoSelectionsInfoBox'; +import { EventRegistrationEntryWrapper } from '../../EventRegistrationEntryWrapper'; import { InvalidCategoryCombinationForOrgUnitMessage, } from './InvalidCategoryCombinationForOrgUnitMessage/InvalidCategoryCombinationForOrgUnitMessage'; -import { NoSelectionsInfoBox } from './NoSelectionsInfoBox'; const getStyles = () => ({ listContainer: { @@ -56,9 +57,12 @@ const MainPagePlain = ({ const showMainPage = useMemo(() => { const noProgramSelected = !programId; const noOrgUnitSelected = !orgUnitId; - const isEventProgram = !trackedEntityTypeId; - return noProgramSelected || noOrgUnitSelected || isEventProgram || displayFrontPageList || selectedTemplateId; - }, [programId, orgUnitId, trackedEntityTypeId, displayFrontPageList, selectedTemplateId]); + + return noProgramSelected || + noOrgUnitSelected || + displayFrontPageList || + selectedTemplateId; + }, [programId, orgUnitId, displayFrontPageList, selectedTemplateId]); return ( <> @@ -88,14 +92,30 @@ const MainPagePlain = ({ )} ) : ( -
-
- -
-
- -
-
+ <> + {trackedEntityTypeId ? ( +
+
+ +
+
+ +
+
+ ) : ( +
+
+ +
+
+ +
+
+ )} + )} ); diff --git a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js index 56779b2ee2..94eaafeca0 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/MainPage.container.js @@ -115,7 +115,7 @@ const MainPageContainer = () => { const selectedProgram = programCollection.get(programId); // $FlowFixMe[prop-missing] const trackedEntityTypeId = selectedProgram?.trackedEntityType?.id; - const displayFrontPageList = trackedEntityTypeId && selectedProgram?.displayFrontPageList; + const displayFrontPageList = selectedProgram?.displayFrontPageList; const MainPageStatus = useMainPageStatus({ programId, selectedProgram, diff --git a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsType/WorkingListsType.component.js b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsType/WorkingListsType.component.js index 732f0d07d5..a97df1a33f 100644 --- a/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsType/WorkingListsType.component.js +++ b/src/core_modules/capture-core/components/Pages/MainPage/WorkingListsType/WorkingListsType.component.js @@ -8,7 +8,14 @@ import type { Props } from './workingListsType.types'; export const WorkingListsType = ({ programId, orgUnitId, selectedTemplateId, onChangeTemplate }: Props) => { const { programType } = useProgramInfo(programId); if (programType === programTypes.EVENT_PROGRAM) { - return ; + return ( + + ); } if (programType === programTypes.TRACKER_PROGRAM) { diff --git a/src/core_modules/capture-core/components/TemplateSelector/EventTemplateSelector/EventTemplateSelector.container.js b/src/core_modules/capture-core/components/TemplateSelector/EventTemplateSelector/EventTemplateSelector.container.js new file mode 100644 index 0000000000..2faf1cb042 --- /dev/null +++ b/src/core_modules/capture-core/components/TemplateSelector/EventTemplateSelector/EventTemplateSelector.container.js @@ -0,0 +1,30 @@ +// @flow +import React from 'react'; +import { TemplateSelector as TemplateSelectorComponent } from '../TemplateSelector.component'; +import { useNavigate, buildUrlQueryString, useLocationQuery } from '../../../utils/routing'; +import { useEventTemplates } from '../hooks'; + +export const EventTemplateSelector = () => { + const { navigate } = useNavigate(); + const { programId, orgUnitId } = useLocationQuery(); + const { eventTemplates, loading: loadingEventTemplates } = useEventTemplates(programId); + + const onSelectTemplate = template => + navigate(`/?${buildUrlQueryString({ orgUnitId, programId, selectedTemplateId: template.id })}`); + const onCreateTemplate = () => { + const urlQueryString = buildUrlQueryString({ + orgUnitId, + programId, + selectedTemplateId: `${programId}-default`, + }); + navigate(`/?${urlQueryString}`); + }; + + return programId && !loadingEventTemplates ? ( + + ) : null; +}; diff --git a/src/core_modules/capture-core/components/TemplateSelector/EventTemplateSelector/index.js b/src/core_modules/capture-core/components/TemplateSelector/EventTemplateSelector/index.js new file mode 100644 index 0000000000..d5de8470d5 --- /dev/null +++ b/src/core_modules/capture-core/components/TemplateSelector/EventTemplateSelector/index.js @@ -0,0 +1,2 @@ +// @flow +export { EventTemplateSelector } from './EventTemplateSelector.container'; diff --git a/src/core_modules/capture-core/components/TemplateSelector/TEITemplateSelector/TEITemplateSelector.container.js b/src/core_modules/capture-core/components/TemplateSelector/TEITemplateSelector/TEITemplateSelector.container.js new file mode 100644 index 0000000000..4c0fdaf7a8 --- /dev/null +++ b/src/core_modules/capture-core/components/TemplateSelector/TEITemplateSelector/TEITemplateSelector.container.js @@ -0,0 +1,31 @@ +// @flow +import React from 'react'; +import { TemplateSelector as TemplateSelectorComponent } from '../TemplateSelector.component'; +import { useNavigate, buildUrlQueryString, useLocationQuery } from '../../../utils/routing'; +import { useTEITemplates, useProgramStageTemplates } from '../hooks'; + +export const TEITemplateSelector = () => { + const { navigate } = useNavigate(); + const { programId, orgUnitId } = useLocationQuery(); + const { TEITemplates, loading: loadingTEITemplates } = useTEITemplates(programId); + const { programStageTemplates, loading: loadingProgramStageTemplates } = useProgramStageTemplates(programId); + + const onSelectTemplate = template => + navigate(`/?${buildUrlQueryString({ orgUnitId, programId, selectedTemplateId: template.id })}`); + const onCreateTemplate = () => { + const urlQueryString = buildUrlQueryString({ + orgUnitId, + programId, + selectedTemplateId: `${programId}-default`, + }); + navigate(`/?${urlQueryString}`); + }; + + return programId && !loadingTEITemplates && !loadingProgramStageTemplates ? ( + + ) : null; +}; diff --git a/src/core_modules/capture-core/components/TemplateSelector/TEITemplateSelector/index.js b/src/core_modules/capture-core/components/TemplateSelector/TEITemplateSelector/index.js new file mode 100644 index 0000000000..2833eca8bd --- /dev/null +++ b/src/core_modules/capture-core/components/TemplateSelector/TEITemplateSelector/index.js @@ -0,0 +1,2 @@ +// @flow +export { TEITemplateSelector } from './TEITemplateSelector.container'; diff --git a/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.container.js b/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.container.js index a567f4ffbc..8e645fce34 100644 --- a/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.container.js +++ b/src/core_modules/capture-core/components/TemplateSelector/TemplateSelector.container.js @@ -1,31 +1,22 @@ // @flow import React from 'react'; -import { TemplateSelector as TemplateSelectorComponent } from './TemplateSelector.component'; -import { useNavigate, buildUrlQueryString, useLocationQuery } from '../../utils/routing'; -import { useTEITemplates, useProgramStageTemplates } from './hooks'; +import { useLocationQuery } from '../../utils/routing'; +import { useProgramInfo, programTypes } from '../../hooks/useProgramInfo'; +import { TEITemplateSelector } from './TEITemplateSelector'; +import { EventTemplateSelector } from './EventTemplateSelector'; export const TemplateSelector = () => { - const { navigate } = useNavigate(); - const { programId, orgUnitId } = useLocationQuery(); - const { TEITemplates, loading: loadingTEITemplates } = useTEITemplates(programId); - const { programStageTemplates, loading: loadingProgramStageTemplates } = useProgramStageTemplates(programId); + const { programId } = useLocationQuery(); + const { programType } = useProgramInfo(programId); - const onSelectTemplate = template => - navigate(`/?${buildUrlQueryString({ orgUnitId, programId, selectedTemplateId: template.id })}`); - const onCreateTemplate = () => { - const urlQueryString = buildUrlQueryString({ - orgUnitId, - programId, - selectedTemplateId: `${programId}-default`, - }); - navigate(`/?${urlQueryString}`); - }; + if (!programId) { + return null; + } - return programId && !loadingTEITemplates && !loadingProgramStageTemplates ? ( - - ) : null; + // Use the appropriate template selector based on program type + if (programType === programTypes.EVENT_PROGRAM) { + return ; + } + + return ; }; diff --git a/src/core_modules/capture-core/components/TemplateSelector/hooks/index.js b/src/core_modules/capture-core/components/TemplateSelector/hooks/index.js index 5371b69ec3..921ce4cdba 100644 --- a/src/core_modules/capture-core/components/TemplateSelector/hooks/index.js +++ b/src/core_modules/capture-core/components/TemplateSelector/hooks/index.js @@ -1,3 +1,4 @@ // @flow export { useTEITemplates } from './useTEITemplates'; export { useProgramStageTemplates } from './useProgramStageTemplates'; +export { useEventTemplates } from './useEventTemplates'; diff --git a/src/core_modules/capture-core/components/TemplateSelector/hooks/useEventTemplates.js b/src/core_modules/capture-core/components/TemplateSelector/hooks/useEventTemplates.js new file mode 100644 index 0000000000..aef1ae5966 --- /dev/null +++ b/src/core_modules/capture-core/components/TemplateSelector/hooks/useEventTemplates.js @@ -0,0 +1,33 @@ +// @flow +import { useMemo } from 'react'; +import { useApiDataQuery } from '../../../utils/reactQueryHelpers'; + +export const useEventTemplates = (programId: string) => { + const query = useMemo(() => ({ + resource: 'eventFilters', + params: { + filter: `program:eq:${programId}`, + fields: ` + id,displayName,eventQueryCriteria,access,externalAccess,publicAccess, + user[id,username], + userAccesses[id,access], + userGroupAccesses[id,access] + `, + }, + }), [programId]); + + const { data, isLoading, error } = useApiDataQuery( + ['eventTemplates', programId], + query, + { + enabled: !!programId, + select: (response: any) => response?.eventFilters || [], + }, + ); + + return { + error, + loading: isLoading, + eventTemplates: data || [], + }; +}; diff --git a/src/core_modules/capture-core/components/TemplateSelector/index.js b/src/core_modules/capture-core/components/TemplateSelector/index.js index f66c549b86..4dcea37113 100644 --- a/src/core_modules/capture-core/components/TemplateSelector/index.js +++ b/src/core_modules/capture-core/components/TemplateSelector/index.js @@ -1 +1,4 @@ +// @flow export { TemplateSelector } from './TemplateSelector.container'; +export { TEITemplateSelector } from './TEITemplateSelector'; +export { EventTemplateSelector } from './EventTemplateSelector'; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ColumnSetup/eventWorkingListsColumnSetup.types.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ColumnSetup/eventWorkingListsColumnSetup.types.js index e187abbcab..00ffdb7bea 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ColumnSetup/eventWorkingListsColumnSetup.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ColumnSetup/eventWorkingListsColumnSetup.types.js @@ -10,6 +10,7 @@ type ExtractedProps = {| customColumnOrder?: CustomColumnOrder, onLoadView: Function, onUpdateList: Function, + onChangeTemplate?: (selectedTemplateId?: string) => void, |}; // had to add customColumnOrder as a non optional type or else it would not be removed. Also, if customColumnOrder is @@ -31,4 +32,5 @@ export type EventWorkingListsColumnSetupOutputProps = {| defaultColumns: EventWorkingListsColumnConfigs, onLoadView: Function, onUpdateList: Function, + onChangeTemplate?: (selectedTemplateId?: string) => void, |}; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/CurrentViewChangesResolver/currentViewChangesResolver.types.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/CurrentViewChangesResolver/currentViewChangesResolver.types.js index eb599fda73..bb6cfc6feb 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/CurrentViewChangesResolver/currentViewChangesResolver.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/CurrentViewChangesResolver/currentViewChangesResolver.types.js @@ -31,4 +31,5 @@ export type CurrentViewChangesResolverOutputProps = {| sortById?: string, sortByDirection?: string, currentViewHasTemplateChanges?: boolean, + onChangeTemplate?: (selectedTemplateId?: string) => void, |}; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/DataSourceSetup/eventWorkingListsDataSourceSetup.types.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/DataSourceSetup/eventWorkingListsDataSourceSetup.types.js index e8d62c5e5b..cf19bdb3b1 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/DataSourceSetup/eventWorkingListsDataSourceSetup.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/DataSourceSetup/eventWorkingListsDataSourceSetup.types.js @@ -26,4 +26,5 @@ export type EventWorkingListsDataSourceSetupOutputProps = {| columns: EventWorkingListsColumnConfigs, dataSource?: DataSource, rowIdKey: string, + onChangeTemplate?: (selectedTemplateId?: string) => void, |}; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.component.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.component.js index e36f40b210..c9baebf1d2 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.component.js @@ -7,7 +7,7 @@ import { EventWorkingListsReduxProvider } from './ReduxProvider'; import { useProgramStageInfo } from '../../../metaDataMemoryStores/programCollection/helpers'; import type { Props } from './EventWorkingLists.types'; -export const EventWorkingLists = ({ storeId, programId, programStageId, orgUnitId }: Props) => { +export const EventWorkingLists = ({ storeId, programId, programStageId, orgUnitId, selectedTemplateId, onChangeTemplate }: Props) => { const { program, programStage, error } = useProgramStageInfo(programStageId, programId); useEffect(() => { @@ -29,6 +29,8 @@ export const EventWorkingLists = ({ storeId, programId, programStageId, orgUnitI // $FlowFixMe programStage={programStage} orgUnitId={orgUnitId} + selectedTemplateId={selectedTemplateId} + onChangeTemplate={onChangeTemplate} /> ); diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.types.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.types.js index c5eaa65af0..49b8894304 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/EventWorkingLists.types.js @@ -5,4 +5,6 @@ export type Props = {| orgUnitId: string, programId?: string, programStageId?: string, + selectedTemplateId?: string, + onChangeTemplate?: (selectedTemplateId?: string) => void, |}; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/EventWorkingListsReduxProvider.container.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/EventWorkingListsReduxProvider.container.js index bcb525bcc6..c232bd9ed7 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/EventWorkingListsReduxProvider.container.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/EventWorkingListsReduxProvider.container.js @@ -1,5 +1,5 @@ // @flow -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useDataEngine } from '@dhis2/app-runtime'; import { makeQuerySingleResource } from 'capture-core/utils/api'; @@ -14,7 +14,14 @@ import type { Props } from './eventWorkingListsReduxProvider.types'; import { computeDownloadRequest } from './downloadRequest'; import { convertToClientConfig } from '../helpers/eventFilters'; -export const EventWorkingListsReduxProvider = ({ storeId, program, programStage, orgUnitId }: Props) => { +export const EventWorkingListsReduxProvider = ({ + storeId, + program, + programStage, + orgUnitId, + selectedTemplateId, + onChangeTemplate, +}: Props) => { const dispatch = useDispatch(); const dataEngine = useDataEngine(); @@ -26,9 +33,33 @@ export const EventWorkingListsReduxProvider = ({ storeId, program, programStage, onResetListColumnOrder, onClearFilters, onUpdateDefaultTemplate, + onSelectTemplate, + viewPreloaded, ...commonStateManagementRestProps } = useWorkingListsCommonStateManagement(storeId, SINGLE_EVENT_WORKING_LISTS_TYPE, program); + // Use selected template ID from props if provided + useEffect(() => { + if (selectedTemplateId && + selectedTemplateId !== currentTemplateId && + !viewPreloaded && + templates && + templates.length > 0) { + const template = templates.find(t => t.id === selectedTemplateId); + if (template) { + onSelectTemplate(selectedTemplateId, template.criteria?.programStage); + } + } + }, [selectedTemplateId, templates, currentTemplateId, onSelectTemplate, viewPreloaded]); + + // Custom onSelectTemplate that calls the provided onChangeTemplate + const handleSelectTemplate = useCallback((templateId, programStageId) => { + onSelectTemplate(templateId, programStageId); + if (onChangeTemplate) { + onChangeTemplate(templateId); + } + }, [onSelectTemplate, onChangeTemplate]); + const currentTemplate = currentTemplateId && templates && templates.find(template => template.id === currentTemplateId); @@ -103,6 +134,8 @@ export const EventWorkingListsReduxProvider = ({ storeId, program, programStage, onUpdateList={injectDownloadRequestToUpdateList} onDeleteEvent={onDeleteEvent} downloadRequest={downloadRequest} + onSelectTemplate={handleSelectTemplate} + onChangeTemplate={onChangeTemplate} /> ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/eventWorkingListsReduxProvider.types.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/eventWorkingListsReduxProvider.types.js index 33a53d67a6..9ddb450a72 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/eventWorkingListsReduxProvider.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/ReduxProvider/eventWorkingListsReduxProvider.types.js @@ -44,7 +44,9 @@ export type Props = $ReadOnly<{| storeId: string, program: Program, programStage: ProgramStage, - orgUnitId: string + orgUnitId: string, + selectedTemplateId?: string, + onChangeTemplate?: (selectedTemplateId?: string) => void |}>; export type EventWorkingListsReduxOutputProps = {| diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/TemplateSetup/EventWorkingListsTemplateSetup.component.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/TemplateSetup/EventWorkingListsTemplateSetup.component.js index fda2407a82..8856457f01 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/TemplateSetup/EventWorkingListsTemplateSetup.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/TemplateSetup/EventWorkingListsTemplateSetup.component.js @@ -23,6 +23,7 @@ export const EventWorkingListsTemplateSetup = ({ onUpdateTemplate, onDeleteTemplate, templates, + onChangeTemplate, ...passOnProps }: Props) => { const injectArgumentsForUpdateTemplate = React.useCallback((template) => { @@ -36,8 +37,9 @@ export const EventWorkingListsTemplateSetup = ({ sortById, sortByDirection, programId: program.id, + callBacks: { onChangeTemplate }, }); - }, [onUpdateTemplate, filters, columns, sortById, sortByDirection, program.id]); + }, [onUpdateTemplate, filters, columns, sortById, sortByDirection, program.id, onChangeTemplate]); const injectArgumentsForAddTemplate = React.useCallback((name) => { // $FlowFixMe For columns: fixing this will create really ugly code. SortById, sortByDirection: Rather complex logic results in sortById and sortByDirection always having a value here. @@ -51,11 +53,12 @@ export const EventWorkingListsTemplateSetup = ({ sortByDirection, clientId: uuid(), programId: program.id, - }); - }, [onAddTemplate, filters, columns, sortById, sortByDirection, program.id]); + }, { onChangeTemplate }); + }, [onAddTemplate, filters, columns, sortById, sortByDirection, program.id, onChangeTemplate]); const injectArgumentsForDeleteTemplate = React.useCallback(template => - onDeleteTemplate(template, program.id), [onDeleteTemplate, program.id]); + onDeleteTemplate(template, program.id, undefined, { onChangeTemplate }), + [onDeleteTemplate, program.id, onChangeTemplate]); return ( void, |}>; type RestProps = $Rest; @@ -42,4 +43,5 @@ export type EventWorkingListsTemplateSetupOutputProps = {| onDeleteTemplate: DeleteTemplate, templates?: WorkingListTemplates, templateSharingType: string, + onChangeTemplate?: (selectedTemplateId?: string) => void, |}; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/EventWorkingListsUpdateTrigger.component.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/EventWorkingListsUpdateTrigger.component.js index 0b1954c4eb..9f35e293a9 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/EventWorkingListsUpdateTrigger.component.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/EventWorkingListsUpdateTrigger.component.js @@ -12,6 +12,7 @@ export const EventWorkingListsUpdateTrigger = ({ lastTransactionOnListDataRefresh, onLoadView, onUpdateList, + onChangeTemplate, ...passOnProps }: Props) => { const forceUpdateOnMount = moment().diff(moment(listDataRefreshTimestamp || 0), 'minutes') > 5 || @@ -32,6 +33,7 @@ export const EventWorkingListsUpdateTrigger = ({ forceUpdateOnMount={forceUpdateOnMount} onLoadView={injectCustomUpdateContextToLoadList} onUpdateList={injectCustomUpdateContextToUpdateList} + onChangeTemplate={onChangeTemplate} /> ); }; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/eventWorkingListsUpdateTrigger.types.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/eventWorkingListsUpdateTrigger.types.js index 5402dd4880..d98e4032ba 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/eventWorkingListsUpdateTrigger.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/UpdateTrigger/eventWorkingListsUpdateTrigger.types.js @@ -9,6 +9,7 @@ type ExtractedProps = $ReadOnly<{| lastTransactionOnListDataRefresh?: number, onLoadView: Function, onUpdateList: Function, + onChangeTemplate?: (selectedTemplateId?: string) => void, |}>; type RestProps = $Rest void, |}>; diff --git a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/templates.epics.js b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/templates.epics.js index f99d504f20..d164a9ff24 100644 --- a/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/templates.epics.js +++ b/src/core_modules/capture-core/components/WorkingLists/EventWorkingLists/epics/templates.epics.js @@ -18,6 +18,7 @@ import { } from '../../WorkingListsCommon'; import { getTemplates } from './getTemplates'; import { SINGLE_EVENT_WORKING_LISTS_TYPE } from '../constants'; +import { getLocationQuery } from '../../../../utils/routing'; export const retrieveTemplatesEpic = ( action$: InputObservable, @@ -69,6 +70,7 @@ export const updateTemplateEpic = ( criteria: eventQueryCriteria, programId, storeId, + callBacks, } }) => { const eventFilterData = { name, @@ -90,6 +92,10 @@ export const updateTemplateEpic = ( const isActiveTemplate = store.value.workingListsTemplates[storeId].selectedTemplateId === id; + if (callBacks?.onChangeTemplate) { + callBacks.onChangeTemplate(id); + } + return updateTemplateSuccess( id, eventQueryCriteria, { @@ -145,6 +151,7 @@ export const addTemplateEpic = ( clientId, programId, storeId, + callBacks, } = action.payload; const eventFilterData = { @@ -160,7 +167,13 @@ export const addTemplateEpic = ( }).then((result) => { const isActiveTemplate = store.value.workingListsTemplates[storeId].selectedTemplateId === clientId; - return addTemplateSuccess(result.response.uid, clientId, { storeId, isActiveTemplate }); + const templateId = result.response.uid; + + if (callBacks?.onChangeTemplate) { + callBacks.onChangeTemplate(templateId); + } + + return addTemplateSuccess(templateId, clientId, { storeId, isActiveTemplate }); }).catch((error) => { log.error( errorCreator('could not add template')({ @@ -191,21 +204,28 @@ export const deleteTemplateEpic = ( action$.pipe( ofType(workingListsCommonActionTypes.TEMPLATE_DELETE), filter(({ payload: { workingListsType } }) => workingListsType === SINGLE_EVENT_WORKING_LISTS_TYPE), - concatMap(({ payload: { template, storeId } }) => { + concatMap(({ payload: { template, storeId, callBacks } }) => { const requestPromise = mutate({ resource: 'eventFilters', id: template.id, type: 'delete', - }).then(() => deleteTemplateSuccess(template, storeId)) - .catch((error) => { - log.error( - errorCreator('could not delete template')({ - error, - template, - }), - ); - return deleteTemplateError(template, storeId); - }); + }).then(() => { + const { programId } = getLocationQuery(); + + if (callBacks?.onChangeTemplate) { + callBacks.onChangeTemplate(`${programId}-default`); + } + + return deleteTemplateSuccess(template, storeId); + }).catch((error) => { + log.error( + errorCreator('could not delete template')({ + error, + template, + }), + ); + return deleteTemplateError(template, storeId); + }); return from(requestPromise).pipe( takeUntil( diff --git a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js index 046c59d246..df0560372b 100644 --- a/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js +++ b/src/core_modules/capture-core/components/WorkingLists/WorkingListsBase/workingListsBase.types.js @@ -216,6 +216,7 @@ export type InterfaceProps = $ReadOnly<{| onUpdateFilter: UpdateFilter, onUpdateList: UpdateList, onUpdateTemplate?: UpdateTemplate, + onChangeTemplate?: (selectedTemplateId?: string) => void, orgUnitId: string, programId: string, rowIdKey: string,