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 3 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');
});
});
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
@@ -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: 'center',
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 @@ -43,6 +43,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 +81,7 @@ export const WidgetEventSchedule = ({
onSaveExternal: onSave,
onSaveSuccessActionType,
onSaveErrorActionType,
...(assignee && { assignedUser: { uid: assignee.id } }),
}));
}, [
dispatch,
Expand All @@ -96,6 +98,7 @@ export const WidgetEventSchedule = ({
onSaveSuccessActionType,
onSaveErrorActionType,
programCategory,
assignee,
]);

React.useEffect(() => {
Expand All @@ -119,6 +122,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 +163,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 +184,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