Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [DHIS2-14327] respect displayFrontPageList for event programs #3983

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
22 changes: 22 additions & 0 deletions .cursor/rules/code-guidelines.mdc
Original file line number Diff line number Diff line change
@@ -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
},
),
);

```
Original file line number Diff line number Diff line change
@@ -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 (
<div className={classes.noAccessContainer}>
{i18n.t('You don\'t have access to create an event in the current selections')}
</div>
);
}

return (
<SingleEventRegistrationEntry
id={dataEntryId}
selectedScopeId={selectedScopeId}
/>
);
};

export const EventRegistrationEntryWrapperComponent = withStyles(getStyles)(EventRegistrationEntryWrapperPlain);
Original file line number Diff line number Diff line change
@@ -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<any>) => (
(props: ContainerProps) => {
const hasRun = useRef<boolean>(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 (
<InnerComponent
{...props}
/>
);
});

export const EventRegistrationEntryWrapper = compose(
openSingleEventDataEntry,
connect(makeMapStateToProps(), () => ({})),
withLoadingIndicator(),
)(EventRegistrationEntryWrapperComponent);
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @flow

// Constants duplicated from SingleEventRegistrationEntry
export const dataEntryId = 'singleEvent';
export const itemId = 'newEvent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { EventRegistrationEntryWrapper } from './EventRegistrationEntryWrapper.container';
Original file line number Diff line number Diff line change
@@ -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),
);
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export type InterfaceProps = $ReadOnly<{|
onSetColumnOrder: SetColumnOrder,
onSort: Sort,
onUpdateFilter: UpdateFilter,
onChangeTemplate?: (selectedTemplateId?: string) => void,
rowIdKey: string,
rowsPerPage: number,
sortById: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @flow
export type Props = $ReadOnly<{|
programId: string,
orgUnitId: string
orgUnitId: string,
selectedTemplateId?: string,
onChangeTemplate?: (selectedTemplateId?: string) => void
|}>;
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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 (
<>
Expand Down Expand Up @@ -88,14 +92,30 @@ const MainPagePlain = ({
)}
</>
) : (
<div className={classes.container}>
<div className={`${classes.half} ${classes.searchBoxWrapper}`}>
<SearchBox programId={programId} />
</div>
<div className={classes.quarter}>
<TemplateSelector />
</div>
</div>
<>
{trackedEntityTypeId ? (
<div className={classes.container}>
<div className={`${classes.half} ${classes.searchBoxWrapper}`}>
<SearchBox programId={programId} />
</div>
<div className={classes.quarter}>
<TemplateSelector />
</div>
</div>
) : (
<div className={classes.container}>
<div className={classes.half}>
<EventRegistrationEntryWrapper
dataEntryId="singleEvent"
selectedScopeId={programId}
/>
</div>
<div className={classes.quarter}>
<TemplateSelector />
</div>
</div>
)}
</>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <EventWorkingListsInit programId={programId} orgUnitId={orgUnitId} />;
return (
<EventWorkingListsInit
programId={programId}
orgUnitId={orgUnitId}
selectedTemplateId={selectedTemplateId}
onChangeTemplate={onChangeTemplate}
/>
);
}

if (programType === programTypes.TRACKER_PROGRAM) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 ? (
<TemplateSelectorComponent
templates={eventTemplates}
onSelectTemplate={onSelectTemplate}
onCreateTemplate={onCreateTemplate}
/>
) : null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { EventTemplateSelector } from './EventTemplateSelector.container';
Original file line number Diff line number Diff line change
@@ -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 ? (
<TemplateSelectorComponent
templates={[...TEITemplates, ...programStageTemplates]}
onSelectTemplate={onSelectTemplate}
onCreateTemplate={onCreateTemplate}
/>
) : null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { TEITemplateSelector } from './TEITemplateSelector.container';
Loading
Loading