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-15483] assign an user when scheduling an enrollment event #3419

Merged
merged 7 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,8 @@ Feature: The user interacts with the widgets on the enrollment add event page
When you click switch tab to Schedule
Then you should see Schedule tab
And you should see suggested date: 08-01

Scenario: You can assign a user when scheduling an event
Given you land on the enrollment edit event page by having typed /#/enrollmentEventNew?enrollmentId=zRfAPUpjoG3&orgUnitId=DiszpKrYNg8&programId=M3xtLkYBlKI&stageId=uvMKOn1oWvd&teiId=S3JjTA4QMNe
When you click switch tab to Schedule
Then you can assign a user when scheduling the event
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,14 @@ import '../sharedSteps';
import '../WidgetEnrollment';
import '../WidgetProfile';
import '../WidgetTab';

Then('you can assign a user when scheduling the event', () => {
cy.get('[data-test="assignee-section"]').within(() => {
cy.get('[data-test="capture-ui-input"]').click();
cy.get('[data-test="capture-ui-input"]').type('Tracker demo');
cy.contains('Tracker demo User').click();
});
cy.get('[data-test="assignee-section"]').within(() => {
cy.get('[data-test="dhis2-uicore-chip"]').contains('Tracker demo User').should('exist');
});
});
10 changes: 5 additions & 5 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2023-09-29T14:14:34.330Z\n"
"PO-Revision-Date: 2023-09-29T14:14:34.330Z\n"
"POT-Creation-Date: 2023-10-17T09:30:37.474Z\n"
"PO-Revision-Date: 2023-10-17T09:30:37.474Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -530,6 +530,9 @@ msgstr "start typing to search"
msgid "suggestions could not be retrieved"
msgstr "suggestions could not be retrieved"

msgid "No results found"
msgstr "No results found"

msgid "No items to display"
msgstr "No items to display"

Expand Down Expand Up @@ -950,9 +953,6 @@ msgstr "Organisation unit could not be loaded"
msgid "Possible duplicates found"
msgstr "Possible duplicates found"

msgid "No results found"
msgstr "No results found"

msgid "An error occurred loading possible duplicates"
msgstr "An error occurred loading possible duplicates"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UserField } from '../../../../../FormFields/UserField/UserField.compone
const getStyles = () => ({
container: {
display: 'flex',
alignItems: 'center',
alignItems: 'baseline',
padding: '8px 8px 8px 12px',
},
containerVertical: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// @flow
import * as React from 'react';
import { v4 as uuid } from 'uuid';
import { withStyles } from '@material-ui/core/styles';
import i18n from '@dhis2/d2-i18n';
import { colors } from '@dhis2/ui';
import { makeCancelablePromise } from 'capture-core-utils';
import { Input } from './Input.component';
import { SearchSuggestions } from './SearchSuggestions.component';
Expand All @@ -10,6 +12,13 @@ import type { User } from './types';
import { withApiUtils } from '../../../HOC';
import type { QuerySingleResource } from '../../../utils/api/api.types';

const getStyles = (theme: Theme) => ({
noMatchFound: {
color: colors.red600,
fontSize: theme.typography.pxToRem(14),
},
});

type Props = {
onSet: (user: User) => void,
inputWrapperClasses: Object,
Expand All @@ -18,6 +27,7 @@ type Props = {
inputPlaceholderText?: ?string,
useUpwardList?: ?boolean,
querySingleResource: QuerySingleResource,
...CssClasses,
};

type State = {
Expand All @@ -26,6 +36,7 @@ type State = {
suggestionsError?: ?string,
highlightedSuggestion?: ?User,
inputKey: number,
noMatch: boolean,
};

const exitBehaviours = {
Expand All @@ -46,6 +57,7 @@ class UserSearchPlain extends React.Component<Props, State> {
suggestions: [],
searchValue: '',
inputKey: 0,
noMatch: false,
};

this.domNames = {
Expand Down Expand Up @@ -82,6 +94,7 @@ class UserSearchPlain extends React.Component<Props, State> {
suggestions,
highlightedSuggestion: undefined,
searchValue,
noMatch: suggestions.length === 0,
});
}

Expand All @@ -90,6 +103,7 @@ class UserSearchPlain extends React.Component<Props, State> {
suggestions: [],
highlightedSuggestion: undefined,
searchValue: '',
noMatch: false,
});
}

Expand Down Expand Up @@ -133,11 +147,14 @@ class UserSearchPlain extends React.Component<Props, State> {
id: au.id,
name: au.displayName,
username: au.username,
firstName: au.firstName,
surname: au.surname,
}));
});

handleInputChange = (value: string) => {
this.cancelablePromise && this.cancelablePromise.cancel();
this.setState({ noMatch: false });

if (value.length > 1) {
const searchPromise = this.search(value);
Expand Down Expand Up @@ -294,18 +311,28 @@ class UserSearchPlain extends React.Component<Props, State> {
);
}

renderNoMatchFound() {
const { noMatch } = this.state;
const { classes } = this.props;

return noMatch ? (
<span className={classes.noMatchFound} >{i18n.t('No results found')}</span>
) : null;
}

render() {
return (
<div>
<SearchContext.Provider
value={this.domNames}
>
{this.renderInput()}
{this.renderNoMatchFound()}
{this.renderSuggestions()}
</SearchContext.Provider>
</div>
);
}
}

export const UserSearch = withApiUtils(UserSearchPlain);
export const UserSearch = withStyles(getStyles)(withApiUtils(UserSearchPlain));
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// @flow
export { UserField } from './UserField.component';
export type { User as UserFormField } from './types';
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export type User = {
id: string,
username: string,
name: string,
firstName: string,
surname: string,
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UserField } from '../../../FormFields/UserField/UserField.component';
const getStyles = () => ({
container: {
display: 'flex',
alignItems: 'center',
alignItems: 'baseline',
padding: '8px 8px 8px 12px',
},
containerVertical: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @flow
import React from 'react';
import i18n from '@dhis2/d2-i18n';
import { withStyles } from '@material-ui/core/styles';
import { UserField } from '../../FormFields/UserField/UserField.component';
import type { Props } from './Assignee.types';

const getStyles = () => ({
container: {
display: 'flex',
alignItems: 'baseline',
padding: '8px 8px 8px 12px',
},
label: {
fontSize: 14,
flexBasis: 200,
color: '#212934',
},
field: {
flexBasis: 150,
flexGrow: 1,
},
});

const AssigneePlain = (props: Props) => {
const { classes, assignee, ...passOnProps } = props;
return (
<div className={classes.container}>
<div className={classes.label}>{i18n.t('Assignee')}</div>
<div className={classes.field}>
{/* $FlowFixMe[cannot-spread-inexact] automated comment */}
<UserField inputPlaceholderText={i18n.t('Search for user')} value={assignee} {...passOnProps} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this field would benefit from having a No suggestions-state.
If a user types in a user that does not exist in the system, you should get a message that says something like Could not find... or No user found... etc. In the current functionality, it looks that it has not searched if you type quickly enough. Do you think this could be implemented?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @eirikhaugstulen
I agree that the users will benefit from a No suggestions-state (the design team also liked the idea). I added it in the last commits.
Regarding the second point, if the user types very fast, the search is prevented on purpose. Within the UserField.component, the DebounceField is used to introduce a delay between search requests. Input debouncing serves the purpose of limiting the frequency of API calls and enhancing overall performance.
Thanks!

</div>
</div>
);
};

export const Assignee = withStyles(getStyles)(AssigneePlain);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @flow
import type { UserFormField } from '../../FormFields/UserField';

export type Props = {
...CssClasses,
assignee?: UserFormField,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { Assignee } from './Assignee.component';
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const requestScheduleEvent = ({
onSaveExternal,
onSaveSuccessActionType,
onSaveErrorActionType,
assignedUser,
}: {
scheduleDate: string,
comments: Array<{value: string}>,
Expand All @@ -34,6 +35,7 @@ export const requestScheduleEvent = ({
onSaveExternal: (eventServerValues: Object, uid: string) => void,
onSaveSuccessActionType?: string,
onSaveErrorActionType?: string,
assignedUser?: {uid: string},
}) =>
actionCreator(scheduleEventWidgetActionTypes.EVENT_SCHEDULE_REQUEST)({
scheduleDate,
Expand All @@ -48,6 +50,7 @@ export const requestScheduleEvent = ({
onSaveExternal,
onSaveSuccessActionType,
onSaveErrorActionType,
assignedUser,
});

export const scheduleEvent = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ScheduleText } from './ScheduleText';
import { CommentSection } from '../WidgetComment';
import type { Props } from './widgetEventSchedule.types';
import { CategoryOptions } from './CategoryOptions/CategoryOptions.component';

import { Assignee } from './Assignee';

const styles = () => ({
wrapper: {
Expand Down Expand Up @@ -66,9 +66,12 @@ const WidgetEventSchedulePlain = ({
suggestedScheduleDate,
comments,
programCategory,
enableUserAssignment,
selectedCategories,
onClickCategoryOption,
onResetCategoryOption,
onSetAssignee,
assignee,
categoryOptionsError,
...passOnProps
}: Props) => (
Expand Down Expand Up @@ -119,6 +122,11 @@ const WidgetEventSchedulePlain = ({
handleAddComment={onAddComment}
/>
</DataSection>
{enableUserAssignment && (
<DataSection dataTest="assignee-section" sectionName={i18n.t('Assignee')}>
<Assignee onSet={onSetAssignee} assignee={assignee} />
</DataSection>
)}
<ScheduleButtons
hasChanges={scheduleDate !== suggestedScheduleDate}
onCancel={onCancel}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { requestScheduleEvent } from './WidgetEventSchedule.actions';
import { NoAccess } from './AccessVerification';
import { useCategoryCombinations } from '../DataEntryDhis2Helpers/AOC/useCategoryCombinations';
import { convertAssigneeToServer } from '../../converters';

export const WidgetEventSchedule = ({
enrollmentId,
Expand All @@ -43,6 +44,7 @@ export const WidgetEventSchedule = ({
const { currentUser, noteId } = useCommentDetails();
const [scheduleDate, setScheduleDate] = useState('');
const [comments, setComments] = useState([]);
const [assignee, setAssignee] = useState();
const { events } = useEventsInOrgUnit(orgUnitId, scheduleDate);
const { eventId } = useLocationQuery();
const eventCountInOrgUnit = events
Expand Down Expand Up @@ -80,6 +82,7 @@ export const WidgetEventSchedule = ({
onSaveExternal: onSave,
onSaveSuccessActionType,
onSaveErrorActionType,
...(assignee && { assignedUser: convertAssigneeToServer(assignee) }),
}));
}, [
dispatch,
Expand All @@ -96,6 +99,7 @@ export const WidgetEventSchedule = ({
onSaveSuccessActionType,
onSaveErrorActionType,
programCategory,
assignee,
]);

React.useEffect(() => {
Expand All @@ -119,6 +123,7 @@ export const WidgetEventSchedule = ({
setComments([...comments, newComment]);
};

const onSetAssignee = useCallback(user => setAssignee(user), []);
const onClickCategoryOption = useCallback((optionId: string, categoryId: string) => {
setSelectedCategories(prevCategoryOptions => ({
...prevCategoryOptions,
Expand Down Expand Up @@ -159,11 +164,13 @@ export const WidgetEventSchedule = ({

return (
<WidgetEventScheduleComponent
assignee={assignee}
stageId={stageId}
stageName={stage.name}
programId={programId}
programCategory={programCategory}
programName={program.name}
enableUserAssignment={stage?.enableUserAssignment}
scheduleDate={scheduleDate}
displayDueDateLabel={programStageScheduleConfig.displayDueDateLabel}
suggestedScheduleDate={suggestedScheduleDate}
Expand All @@ -178,6 +185,7 @@ export const WidgetEventSchedule = ({
categoryOptionsError={categoryOptionsError}
onClickCategoryOption={onClickCategoryOption}
onResetCategoryOption={onResetCategoryOption}
onSetAssignee={onSetAssignee}
{...passOnProps}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const scheduleEnrollmentEventEpic = (action$: InputObservable, store: Red
onSaveExternal,
onSaveSuccessActionType,
onSaveErrorActionType,
assignedUser,
} = action.payload;

const { events } = store.value;
Expand All @@ -46,6 +47,7 @@ export const scheduleEnrollmentEventEpic = (action$: InputObservable, store: Red
programStage: stageId,
status: 'SCHEDULE',
notes: comments ?? [],
assignedUser,
}] };

if (attributeCategoryOptions) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import type { ProgramCategory, CategoryOption } from './CategoryOptions/CategoryOptions.types';
import type { UserFormField } from '../FormFields/UserField';

export type ContainerProps = {|
programId: string,
Expand Down Expand Up @@ -38,7 +39,10 @@ export type Props = {|
selectedCategories?: ?{ [categoryId: string]: CategoryOption },
programCategory?: ProgramCategory,
categoryOptionsError?: ?{[categoryId: string]: { touched: boolean, valid: boolean} },
enableUserAssignment?: boolean,
onSchedule: () => void,
onSetAssignee: (user: UserFormField) => void,
assignee?: UserFormField,
onCancel: () => void,
setScheduleDate: (date: string) => void,
onAddComment: (comment: string) => void,
Expand Down
Loading
Loading