From f3673d8ac78a4468be7f9e4e69e02622f071e763 Mon Sep 17 00:00:00 2001 From: sniedzielski <52816247+sniedzielski@users.noreply.github.com> Date: Fri, 24 May 2024 09:47:56 +0200 Subject: [PATCH] CM-812: Added enrollment for group (#78) * CM-812: Added enrollment page for group * CM-812: adjustment to group enrollment summary in group searcher --- src/actions.js | 34 ++ src/components/EnrollmentGroupHeadPanel.js | 131 ++++++ src/components/GroupSearcher.js | 38 +- .../dialogs/AdvancedCriteriaGroupForm.js | 406 ++++++++++++++++++ src/components/dialogs/GroupMenu.js | 55 +++ .../dialogs/GroupPreviewEnrollmentDialog.js | 123 ++++++ src/constants.js | 2 + src/index.js | 9 +- src/pages/EnrollmentGroupPage.js | 70 +++ src/pages/GroupsPage.js | 2 +- src/reducer.js | 29 ++ src/translations/en.json | 12 +- 12 files changed, 900 insertions(+), 11 deletions(-) create mode 100644 src/components/EnrollmentGroupHeadPanel.js create mode 100644 src/components/dialogs/AdvancedCriteriaGroupForm.js create mode 100644 src/components/dialogs/GroupMenu.js create mode 100644 src/components/dialogs/GroupPreviewEnrollmentDialog.js create mode 100644 src/pages/EnrollmentGroupPage.js diff --git a/src/actions.js b/src/actions.js index b6719c9..fb8aeb5 100644 --- a/src/actions.js +++ b/src/actions.js @@ -25,6 +25,15 @@ const ENROLLMENT_SUMMARY_FULL_PROJECTION = () => [ 'numberOfIndividualsToUpload', ]; +const ENROLLMENT_GROUP_SUMMARY_FULL_PROJECTION = () => [ + 'totalNumberOfGroups', + 'numberOfSelectedGroups', + 'numberOfGroupsAssignedToProgramme', + 'numberOfGroupsNotAssignedToProgramme', + 'numberOfGroupsAssignedToSelectedProgramme', + 'numberOfGroupsToUpload', +]; + export function fetchWorkflows() { const payload = formatQuery( 'workflow', @@ -103,6 +112,15 @@ export function fetchIndividualEnrollmentSummary(params) { return graphql(payload, ACTION_TYPE.ENROLLMENT_SUMMARY); } +export function fetchGroupEnrollmentSummary(params) { + const payload = formatQuery( + 'groupEnrollmentSummary', + params, + ENROLLMENT_GROUP_SUMMARY_FULL_PROJECTION(), + ); + return graphql(payload, ACTION_TYPE.ENROLLMENT_GROUP_SUMMARY); +} + export function fetchIndividuals(params) { const payload = formatPageQueryWithCount('individual', params, INDIVIDUAL_FULL_PROJECTION); return graphql(payload, ACTION_TYPE.SEARCH_INDIVIDUALS); @@ -277,6 +295,22 @@ export function confirmEnrollment(params, clientMutationLabel) { ); } +export function confirmGroupEnrollment(params, clientMutationLabel) { + // eslint-disable-next-line max-len + const mutation = formatMutation('confirmGroupEnrollment', formatConfirmEnrollmentGQL(params), clientMutationLabel); + const requestedDateTime = new Date(); + return graphql( + mutation.payload, + [REQUEST(ACTION_TYPE.MUTATION), SUCCESS(ACTION_TYPE.CONFIRM_GROUP_ENROLLMENT), ERROR(ACTION_TYPE.MUTATION)], + { + actionType: ACTION_TYPE.UPDATE_GROUP, + clientMutationId: mutation.clientMutationId, + clientMutationLabel, + requestedDateTime, + }, + ); +} + export function updateGroupIndividual(groupIndividual, clientMutationLabel) { const mutation = formatMutation( 'editIndividualInGroup', diff --git a/src/components/EnrollmentGroupHeadPanel.js b/src/components/EnrollmentGroupHeadPanel.js new file mode 100644 index 0000000..63ea0b4 --- /dev/null +++ b/src/components/EnrollmentGroupHeadPanel.js @@ -0,0 +1,131 @@ +/* eslint-disable max-len */ +/* eslint-disable camelcase */ +import React from 'react'; +import { injectIntl } from 'react-intl'; + +import { Grid, Divider } from '@material-ui/core'; +import { withStyles, withTheme } from '@material-ui/core/styles'; + +import { + decodeId, + FormPanel, + PublishedComponent, + formatMessage, + withModulesManager, +} from '@openimis/fe-core'; +import AdvancedCriteriaGroupForm from './dialogs/AdvancedCriteriaGroupForm'; +import { CLEARED_STATE_FILTER } from '../constants'; + +const styles = (theme) => ({ + tableTitle: theme.table.title, + item: theme.paper.item, + fullHeight: { + height: '100%', + }, +}); + +class EnrollmentGroupHeadPanel extends FormPanel { + constructor(props) { + super(props); + this.state = { + appliedCustomFilters: [CLEARED_STATE_FILTER], + appliedFiltersRowStructure: [CLEARED_STATE_FILTER], + }; + } + + updateJsonExt = (value) => { + this.updateAttributes({ + jsonExt: value, + }); + }; + + getDefaultAppliedCustomFilters = () => { + const { jsonExt } = this.props?.edited ?? {}; + try { + const jsonData = JSON.parse(jsonExt); + const advancedCriteria = jsonData.advanced_criteria || []; + return advancedCriteria.map(({ custom_filter_condition }) => { + const [field, filter, typeValue] = custom_filter_condition.split('__'); + const [type, value] = typeValue.split('='); + return { + custom_filter_condition, + field, + filter, + type, + value, + }; + }); + } catch (error) { + return []; + } + }; + + setAppliedCustomFilters = (appliedCustomFilters) => { + this.setState({ appliedCustomFilters }); + }; + + setAppliedFiltersRowStructure = (appliedFiltersRowStructure) => { + this.setState({ appliedFiltersRowStructure }); + }; + + render() { + // eslint-disable-next-line no-unused-vars + const { edited, classes, intl } = this.props; + const enrollment = { ...edited }; + const { appliedCustomFilters, appliedFiltersRowStructure } = this.state; + return ( + <> + + + this.updateAttribute('benefitPlan', benefitPlan)} + value={enrollment?.benefitPlan} + type="GROUP" + /> + + + this.updateAttribute('status', status)} + value={enrollment?.status} + /> + + + + + <> + + {formatMessage(intl, 'individual', 'individual.enrollment.criteria')} + + + + + + > + + > + ); + } +} + +export default withModulesManager(injectIntl(withTheme(withStyles(styles)(EnrollmentGroupHeadPanel)))); diff --git a/src/components/GroupSearcher.js b/src/components/GroupSearcher.js index 1100419..d09f425 100644 --- a/src/components/GroupSearcher.js +++ b/src/components/GroupSearcher.js @@ -11,6 +11,7 @@ import { coreConfirm, clearConfirm, journalize, + decodeId, } from '@openimis/fe-core'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; @@ -30,6 +31,7 @@ import { DEFAULT_PAGE_SIZE, ROWS_PER_PAGE_OPTIONS, RIGHT_GROUP_UPDATE, RIGHT_GROUP_DELETE, INDIVIDUAL_MODULE_NAME, INDIVIDUAL_LABEL, + INDIVIDUAL_GROUP_MENU_CONTRIBUTION_KEY, } from '../constants'; import GroupFilter from './GroupFilter'; import { applyNumberCircle } from '../util/searcher-utils'; @@ -53,11 +55,14 @@ function GroupSearcher({ confirmed, submittingMutation, clearGroupExport, + isModalEnrollment, mutation, coreConfirm, clearConfirm, journalize, CLEARED_STATE_FILTER, + benefitPlanToEnroll, + advancedCriteria, }) { const [groupToDelete, setGroupToDelete] = useState(null); const [deletedGroupUuids, setDeletedGroupUuids] = useState([]); @@ -130,7 +135,7 @@ function GroupSearcher({ ? `${group?.head?.firstName} ${group?.head?.lastName}` : formatMessage(intl, 'group', 'noHeadSpecified')), ]; - if (rights.includes(RIGHT_GROUP_UPDATE)) { + if (rights.includes(RIGHT_GROUP_UPDATE) && isModalEnrollment === false) { formatters.push((group) => ( )); } - if (rights.includes(RIGHT_GROUP_DELETE)) { + if (rights.includes(RIGHT_GROUP_DELETE) && isModalEnrollment === false) { formatters.push((group) => ( deletedGroupUuids.includes(group.id); - const defaultFilters = () => ({ - isDeleted: { - value: false, - filter: 'isDeleted: false', - }, - }); + const defaultFilters = () => { + const filters = { + isDeleted: { + value: false, + filter: 'isDeleted: false', + }, + }; + if (isModalEnrollment && advancedCriteria !== null && advancedCriteria !== undefined) { + filters.customFilters = { + value: advancedCriteria, + filter: `customFilters: [${advancedCriteria}]`, + }; + filters.benefitPlanToEnroll = { + value: benefitPlanToEnroll, + filter: `benefitPlanToEnroll: "${decodeId(benefitPlanToEnroll)}"`, + }; + } + return filters; + }; const [failedExport, setFailedExport] = useState(false); @@ -244,6 +262,10 @@ function GroupSearcher({ applyNumberCircle={applyNumberCircle} rowDisabled={isRowDisabled} rowLocked={isRowDisabled} + // eslint-disable-next-line react/jsx-props-no-spreading, max-len + {...(isModalEnrollment === false ? { + actionsContributionKey: INDIVIDUAL_GROUP_MENU_CONTRIBUTION_KEY, isCustomFiltering: true, + } : { isCustomFiltering: false })} /> {failedExport && ( diff --git a/src/components/dialogs/AdvancedCriteriaGroupForm.js b/src/components/dialogs/AdvancedCriteriaGroupForm.js new file mode 100644 index 0000000..dc1b495 --- /dev/null +++ b/src/components/dialogs/AdvancedCriteriaGroupForm.js @@ -0,0 +1,406 @@ +/* eslint-disable max-len */ +import React, { useEffect, useState } from 'react'; +import { injectIntl } from 'react-intl'; +import Button from '@material-ui/core/Button'; +import { Divider, Grid, Paper } from '@material-ui/core'; +import { + decodeId, + formatMessage, + formatMessageWithValues, + fetchCustomFilter, + coreConfirm, + clearConfirm, +} from '@openimis/fe-core'; +import { withTheme, withStyles } from '@material-ui/core/styles'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import AddCircle from '@material-ui/icons/Add'; +import Typography from '@material-ui/core/Typography'; +import AdvancedCriteriaRowValue from './AdvancedCriteriaRowValue'; +import { CLEARED_STATE_FILTER, GROUP, INDIVIDUAL } from '../../constants'; +import { isBase64Encoded, isEmptyObject } from '../../utils'; +import { confirmGroupEnrollment, fetchGroupEnrollmentSummary } from '../../actions'; +import IndividualPreviewEnrollmentDialog from './IndividualPreviewEnrollmentDialog'; +import GroupPreviewEnrollmentDialog from './GroupPreviewEnrollmentDialog'; + +const styles = (theme) => ({ + item: theme.paper.item, +}); + +function AdvancedCriteriaGroupForm({ + intl, + classes, + object, + objectToSave, + fetchCustomFilter, + customFilters, + moduleName, + objectType, + setAppliedCustomFilters, + // eslint-disable-next-line no-unused-vars + appliedFiltersRowStructure, + setAppliedFiltersRowStructure, + updateAttributes, + getDefaultAppliedCustomFilters, + additionalParams, + fetchGroupEnrollmentSummary, + enrollmentGroupSummary, + fetchedEnrollmentGroupSummary, + confirmGroupEnrollment, + confirmed, + clearConfirm, + coreConfirm, + rights, + edited, +}) { + // eslint-disable-next-line no-unused-vars + const [currentFilter, setCurrentFilter] = useState({ + field: '', filter: '', type: '', value: '', amount: '', + }); + const [filters, setFilters] = useState(getDefaultAppliedCustomFilters()); + const [filtersToApply, setFiltersToApply] = useState(null); + + const getBenefitPlanDefaultCriteria = () => { + const { jsonExt } = edited?.benefitPlan ?? {}; + try { + const jsonData = JSON.parse(jsonExt); + return jsonData.advanced_criteria || []; + } catch (error) { + return []; + } + }; + + useEffect(() => { + if (!getDefaultAppliedCustomFilters().length) { + setFilters(getBenefitPlanDefaultCriteria()); + } + }, [edited]); + + const createParams = (moduleName, objectTypeName, uuidOfObject = null, additionalParams = null) => { + const params = [ + `moduleName: "${moduleName}"`, + `objectTypeName: "${objectTypeName}"`, + ]; + if (uuidOfObject) { + params.push(`uuidOfObject: "${uuidOfObject}"`); + } + if (additionalParams) { + params.push(`additionalParams: ${JSON.stringify(JSON.stringify(additionalParams))}`); + } + return params; + }; + + const fetchFilters = (params) => { + fetchCustomFilter(params); + }; + + const handleClose = () => { + setCurrentFilter(CLEARED_STATE_FILTER); + }; + + const handleAddFilter = () => { + setCurrentFilter(CLEARED_STATE_FILTER); + setFilters([...filters, CLEARED_STATE_FILTER]); + }; + + function updateJsonExt(inputJsonExt, outputFilters) { + const existingData = JSON.parse(inputJsonExt || '{}'); + // eslint-disable-next-line no-prototype-builtins + if (!existingData.hasOwnProperty('advanced_criteria')) { + existingData.advanced_criteria = []; + } + const filterData = JSON.parse(outputFilters); + existingData.advanced_criteria = filterData; + const updatedJsonExt = JSON.stringify(existingData); + return updatedJsonExt; + } + + const handleRemoveFilter = () => { + setCurrentFilter(CLEARED_STATE_FILTER); + setAppliedFiltersRowStructure([CLEARED_STATE_FILTER]); + setFilters([CLEARED_STATE_FILTER]); + }; + + const saveCriteria = () => { + setAppliedFiltersRowStructure(filters); + const outputFilters = JSON.stringify( + filters.map(({ + filter, value, field, type, + }) => ({ + custom_filter_condition: `${field}__${filter}__${type}=${value}`, + })), + ); + const jsonExt = updateJsonExt(objectToSave.jsonExt, outputFilters); + updateAttributes(jsonExt); + setAppliedCustomFilters(outputFilters); + + // Parse the jsonExt string to extract advanced_criteria + const jsonData = JSON.parse(jsonExt); + const advancedCriteria = jsonData.advanced_criteria || []; + + // Extract custom_filter_condition values and construct customFilters array + const customFilters = advancedCriteria.map((criterion) => `"${criterion.custom_filter_condition}"`); + setFiltersToApply(customFilters); + const params = [ + `customFilters: [${customFilters}]`, + `benefitPlanId: "${decodeId(object.id)}"`, + ]; + fetchGroupEnrollmentSummary(params); + handleClose(); + }; + + useEffect(() => { + if (object && isEmptyObject(object) === false) { + let paramsToFetchFilters = []; + if (objectType === INDIVIDUAL) { + paramsToFetchFilters = createParams( + moduleName, + objectType, + isBase64Encoded(object.id) ? decodeId(object.id) : object.id, + additionalParams, + ); + } else { + paramsToFetchFilters = createParams( + moduleName, + objectType, + additionalParams, + ); + } + fetchFilters(paramsToFetchFilters); + } + }, [object]); + + useEffect(() => {}, [filters]); + + const openConfirmEnrollmentDialog = () => { + coreConfirm( + formatMessage(intl, 'individual', 'individual.enrollment.confirmTitle'), + formatMessageWithValues(intl, 'individual', 'individual.enrollment.confirmGroupMessageDialog', { benefitPlanName: object.name }), + ); + }; + + useEffect(() => { + if (confirmed) { + const outputFilters = JSON.stringify( + filters.map(({ + filter, value, field, type, + }) => ({ + custom_filter_condition: `${field}__${filter}__${type}=${value}`, + })), + ); + const jsonExt = updateJsonExt(objectToSave.jsonExt, outputFilters); + const jsonData = JSON.parse(jsonExt); + const advancedCriteria = jsonData.advanced_criteria || []; + + // Extract custom_filter_condition values and construct customFilters array + const customFilters = advancedCriteria.map((criterion) => `"${criterion.custom_filter_condition}"`); + setFiltersToApply(customFilters); + const params = { + customFilters: `[${customFilters}]`, + benefitPlanId: `"${decodeId(object.id)}"`, + status: `"${objectToSave.status}"`, + }; + confirmGroupEnrollment( + params, + formatMessage(intl, 'individual', 'individual.enrollment.mutationLabel'), + ); + } + return () => confirmed && clearConfirm(false); + }, [confirmed]); + + return ( + <> + {filters.map((filter, index) => ( + + ))} + { !confirmed ? ( + + + + {formatMessage(intl, 'individual', 'individual.enrollment.addFilters')} + + + // eslint-disable-next-line react/jsx-no-useless-fragment + ) : (<>>) } + + + + {formatMessage(intl, 'individual', 'individual.enrollment.clearAllFilters')} + + + + + {formatMessage(intl, 'individual', 'individual.enrollment.previewEnrollment')} + + + + + {fetchedEnrollmentGroupSummary && ( + + + {formatMessage(intl, 'individual', 'individual.enrollment.summary')} + + + + + + + {formatMessage(intl, 'individual', 'individual.enrollment.totalNumberOfGroups')} + + + {enrollmentGroupSummary.totalNumberOfGroups} + + + + + + + {formatMessage(intl, 'individual', 'individual.enrollment.numberOfSelectedGroups')} + + + {enrollmentGroupSummary.numberOfSelectedGroups} + + + + + + + {formatMessage(intl, 'individual', 'individual.enrollment.numberOfGroupsAssignedToProgramme')} + + + {enrollmentGroupSummary.numberOfGroupsAssignedToProgramme} + + + + + + + {formatMessage(intl, 'individual', 'individual.enrollment.numberOfGroupsNotAssignedToProgramme')} + + + {enrollmentGroupSummary.numberOfGroupsNotAssignedToProgramme} + + + + + + + {/* eslint-disable-next-line max-len */} + {formatMessage(intl, 'individual', 'individual.enrollment.numberOfGroupsAssignedToSelectedProgramme')} + + + {enrollmentGroupSummary.numberOfGroupsAssignedToSelectedProgramme} + + + + + + + {/* eslint-disable-next-line max-len */} + {formatMessage(intl, 'individual', 'individual.enrollment.numberOfGroupsToUpload')} + + + {enrollmentGroupSummary.numberOfGroupsToUpload} + + + + + + + + openConfirmEnrollmentDialog()} + variant="contained" + color="primary" + autoFocus + disabled={!object || confirmed || enrollmentGroupSummary.numberOfGroupsToUpload === '0'} + > + {formatMessage(intl, 'individual', 'individual.enrollment.confirmEnrollment')} + + + + + + + )} + > + ); +} + +// eslint-disable-next-line no-unused-vars +const mapStateToProps = (state, props) => ({ + rights: !!state.core && !!state.core.user && !!state.core.user.i_user ? state.core.user.i_user.rights : [], + confirmed: state.core.confirmed, + fetchingCustomFilters: state.core.fetchingCustomFilters, + errorCustomFilters: state.core.errorCustomFilters, + fetchedCustomFilters: state.core.fetchedCustomFilters, + customFilters: state.core.customFilters, + fetchingEnrollmentGroupSummary: state.individual.fetchingEnrollmentGroupSummary, + errorEnrollmentGroupSummary: state.individual.errorEnrollmentGroupSummary, + fetchedEnrollmentGroupSummary: state.individual.fetchedEnrollmentGroupSummary, + enrollmentGroupSummary: state.individual.enrollmentGroupSummary, +}); + +const mapDispatchToProps = (dispatch) => bindActionCreators({ + fetchCustomFilter, + fetchGroupEnrollmentSummary, + confirmGroupEnrollment, + clearConfirm, + coreConfirm, +}, dispatch); + +export default injectIntl( + withTheme(withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(AdvancedCriteriaGroupForm))), +); diff --git a/src/components/dialogs/GroupMenu.js b/src/components/dialogs/GroupMenu.js new file mode 100644 index 0000000..cda7cb1 --- /dev/null +++ b/src/components/dialogs/GroupMenu.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { + MenuItem, +} from '@material-ui/core'; +import { injectIntl } from 'react-intl'; +import { + useModulesManager, + formatMessage, + coreAlert, +} from '@openimis/fe-core'; +import { withTheme, withStyles } from '@material-ui/core/styles'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { fetchWorkflows } from '../../actions'; + +const styles = (theme) => ({ + item: theme.paper.item, +}); + +function GroupMenu({ + intl, +}) { + const modulesManager = useModulesManager(); + + function enrollmentGroupPageUrl() { + return `${modulesManager.getRef('individual.route.groupEnrollment')}`; + } + + return ( + + + {formatMessage(intl, 'individual', 'individual.enrollment.buttonLabel')} + + + ); +} + +const mapStateToProps = (state) => ({ + rights: !!state.core && !!state.core.user && !!state.core.user.i_user ? state.core.user.i_user.rights : [], + confirmed: state.core.confirmed, + workflows: state.socialProtection.workflows, +}); + +const mapDispatchToProps = (dispatch) => bindActionCreators({ + fetchWorkflows, + coreAlert, +}, dispatch); + +export default injectIntl( + withTheme( + withStyles(styles)( + connect(mapStateToProps, mapDispatchToProps)(GroupMenu), + ), + ), +); diff --git a/src/components/dialogs/GroupPreviewEnrollmentDialog.js b/src/components/dialogs/GroupPreviewEnrollmentDialog.js new file mode 100644 index 0000000..28aa6a5 --- /dev/null +++ b/src/components/dialogs/GroupPreviewEnrollmentDialog.js @@ -0,0 +1,123 @@ +/* eslint-disable max-len */ +import React, { useState } from 'react'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import { + useModulesManager, + useTranslations, +} from '@openimis/fe-core'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { INDIVIDUAL_MODULE_NAME } from '../../constants'; +import GroupSearcher from '../GroupSearcher'; + +function GroupPreviewEnrollmentDialog({ + classes, + rights, + advancedCriteria, + benefitPlanToEnroll, + enrollmentSummary, + confirmed, +}) { + const [isOpen, setIsOpen] = useState(false); + + const handleOpen = () => { + setIsOpen(true); + }; + + const handleClose = () => { + setIsOpen(false); + }; + + const modulesManager = useModulesManager(); + const { formatMessage } = useTranslations(INDIVIDUAL_MODULE_NAME, modulesManager); + + return ( + <> + + {formatMessage('individual.enrollment.previewGroups')} + + + + {formatMessage('individual.enrollment.previewGroups')} + + + + + + + + + + + + {formatMessage('individual.enrollment.close')} + + + + + + > + ); +} + +const mapStateToProps = (state) => ({ + rights: !!state.core && !!state.core.user && !!state.core.user.i_user ? state.core.user.i_user.rights : [], + confirmed: state.core.confirmed, +}); + +const mapDispatchToProps = (dispatch) => bindActionCreators({ +}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(GroupPreviewEnrollmentDialog); diff --git a/src/constants.js b/src/constants.js index 632b698..a1522e4 100644 --- a/src/constants.js +++ b/src/constants.js @@ -66,10 +66,12 @@ export const INDIVIDUAL_MODULE_NAME = 'individual'; export const FETCH_BENEFIT_PLAN_SCHEMA_FIELDS_REF = 'socialProtection.fetchBenefitPlanSchemaFields'; export const INDIVIDUAL_ENROLMENT_DIALOG_CONTRIBUTION_KEY = 'individual.IndividualsEnrolmentDialog'; export const INDIVIDUALS_UPLOAD_FORM_CONTRIBUTION_KEY = 'individual.IndividualsUploadDialog'; +export const INDIVIDUAL_GROUP_MENU_CONTRIBUTION_KEY = 'individual.group.GroupMenu'; export const CLEARED_STATE_FILTER = { field: '', filter: '', type: '', value: '', }; export const INDIVIDUAL = 'Individual'; +export const GROUP = 'Group'; export const INTEGER = 'integer'; export const STRING = 'string'; export const BOOLEAN = 'boolean'; diff --git a/src/index.js b/src/index.js index ba9a610..d9e79d1 100644 --- a/src/index.js +++ b/src/index.js @@ -41,7 +41,7 @@ import { INDIVIDUAL_LABEL, INDIVIDUAL_MODULE_NAME, RIGHT_GROUP_SEARCH, - RIGHT_INDIVIDUAL_SEARCH + RIGHT_INDIVIDUAL_SEARCH, } from './constants'; import { GroupCreateTaskItemFormatters, GroupCreateTaskTableHeaders } from './components/tasks/GroupCreateTasks'; import IndividualsUploadDialog from './components/dialogs/IndividualsUploadDialog'; @@ -53,6 +53,8 @@ import { } from './components/GroupIndividualHistoryTab'; import AdvancedCriteriaRowValue from './components/dialogs/AdvancedCriteriaRowValue'; import IndividualPicker from './pickers/IndividualPicker'; +import EnrollmentGroupPage from './pages/EnrollmentGroupPage'; +import GroupMenu from './components/dialogs/GroupMenu'; const ROUTE_INDIVIDUALS = 'individuals'; const ROUTE_INDIVIDUAL = 'individuals/individual'; @@ -60,6 +62,7 @@ const ROUTE_INDIVIDUAL_FROM_GROUP = 'groups/group/individuals/individual'; const ROUTE_GROUPS = 'groups'; const ROUTE_GROUP = 'groups/group'; const ROUTE_ENROLLMENT = 'individuals/enrollment'; +const ROUTE_GROUP_ENROLLMENT = 'groups/enrollment'; const BENEFIT_PLAN_TABS_LABEL_REF_KEY = 'socialProtection.BenefitPlansListTabLabel'; const BENEFIT_PLAN_TABS_PANEL_REF_KEY = 'socialProtection.BenefitPlansListTabPanel'; @@ -72,6 +75,7 @@ const DEFAULT_CONFIG = { { path: ROUTE_INDIVIDUALS, component: IndividualsPage }, { path: ROUTE_GROUPS, component: GroupsPage }, { path: ROUTE_ENROLLMENT, component: EnrollmentPage }, + { path: ROUTE_GROUP_ENROLLMENT, component: EnrollmentGroupPage }, { path: `${ROUTE_INDIVIDUAL}/:individual_uuid?`, component: IndividualPage }, { path: `${ROUTE_INDIVIDUAL_FROM_GROUP}/:individual_uuid?`, component: IndividualPage }, { path: `${ROUTE_GROUP}/:group_uuid?`, component: GroupPage }, @@ -93,6 +97,7 @@ const DEFAULT_CONFIG = { refs: [ { key: 'individual.route.individual', ref: ROUTE_INDIVIDUAL }, { key: 'individual.route.enrollment', ref: ROUTE_ENROLLMENT }, + { key: 'individual.route.groupEnrollment', ref: ROUTE_GROUP_ENROLLMENT }, { key: 'individual.route.group', ref: ROUTE_GROUP }, { key: 'individual.GroupIndividualSearcher', ref: GroupIndividualSearcher }, { key: 'individual.actions.fetchIndividuals', ref: fetchIndividuals }, @@ -104,8 +109,10 @@ const DEFAULT_CONFIG = { { key: 'individual.GroupIndividualHistorySearcher', ref: GroupIndividualHistorySearcher }, { key: 'individual.AdvancedCriteriaRowValue', ref: AdvancedCriteriaRowValue }, { key: 'individual.IndividualPicker', ref: IndividualPicker }, + { key: 'individual.group.GroupMenu', ref: GroupMenu }, ], 'individual.IndividualsUploadDialog': IndividualsUploadDialog, + 'individual.group.GroupMenu': GroupMenu, 'individual.TabPanel.label': [ BenefitPlansListTabLabel, IndividalChangelogTabLabel, diff --git a/src/pages/EnrollmentGroupPage.js b/src/pages/EnrollmentGroupPage.js new file mode 100644 index 0000000..eab1aab --- /dev/null +++ b/src/pages/EnrollmentGroupPage.js @@ -0,0 +1,70 @@ +import React, { useState } from 'react'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; + +import { makeStyles } from '@material-ui/styles'; + +import { + Form, + useHistory, + useModulesManager, + useTranslations, + coreConfirm, + clearConfirm, + journalize, +} from '@openimis/fe-core'; +import EnrollmentGroupHeadPanel from '../components/EnrollmentGroupHeadPanel'; + +const useStyles = makeStyles((theme) => ({ + page: theme.page, +})); + +function EnrollmentGroupPage({ + rights, +}) { + const modulesManager = useModulesManager(); + const classes = useStyles(); + const history = useHistory(); + const { formatMessage } = useTranslations('individual', modulesManager); + + const [editedEnrollment, setEditedEnrollment] = useState({ status: 'ACTIVE' }); + + const back = () => history.goBack(); + + const actions = []; + + return ( + + {}} + save={null} + HeadPanel={EnrollmentGroupHeadPanel} + rights={rights} + actions={actions} + /> + + ); +} + +const mapDispatchToProps = (dispatch) => bindActionCreators({ + coreConfirm, + clearConfirm, + journalize, +}, dispatch); + +// eslint-disable-next-line no-unused-vars +const mapStateToProps = (state, props) => ({ + rights: state.core?.user?.i_user?.rights ?? [], + confirmed: state.core.confirmed, + submittingMutation: state.payroll.submittingMutation, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(EnrollmentGroupPage); diff --git a/src/pages/GroupsPage.js b/src/pages/GroupsPage.js index 20bf949..8388f60 100644 --- a/src/pages/GroupsPage.js +++ b/src/pages/GroupsPage.js @@ -18,7 +18,7 @@ function GroupsPage(props) { rights.includes(RIGHT_GROUP_SEARCH) && ( - + ) ); diff --git a/src/reducer.js b/src/reducer.js index d92c30e..22b6137 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -41,6 +41,8 @@ export const ACTION_TYPE = { CONFIRM_ENROLLMENT: 'CONFIRM_ENROLLMENT', GET_INDIVIDUAL_UPLOAD_HISTORY: 'GET_INDIVIDUAL_UPLOAD_HISTORY', SEARCH_GROUP_INDIVIDUAL_HISTORY: 'SEARCH_GROUP_INDIVIDUAL_HISTORY', + ENROLLMENT_GROUP_SUMMARY: 'ENROLLMENT_GROUP_SUMMARY', + CONFIRM_GROUP_ENROLLMENT: 'CONFIRM_GROUP_ENROLLMENT', }; function reducer( @@ -123,6 +125,11 @@ function reducer( groupIndividualHistory: [], groupIndividualHistoryPageInfo: {}, groupIndividualHistoryTotalCount: 0, + + enrollmentGroupSummary: [], + enrollmentGroupSummaryError: null, + fetchingEnrollmentGroupSummary: true, + fetchedEnrollmentGroupSummary: false, }, action, ) { @@ -514,6 +521,28 @@ function reducer( fetchingEnrollmentSummary: false, enrollmentSummaryError: formatServerError(action.payload), }; + case REQUEST(ACTION_TYPE.ENROLLMENT_GROUP_SUMMARY): + return { + ...state, + fetchingEnrollmentGroupSummary: true, + fetchedEnrollmentGroupSummary: false, + enrollmentGroupSummary: {}, + enrollmentGroupSummaryError: null, + }; + case SUCCESS(ACTION_TYPE.ENROLLMENT_GROUP_SUMMARY): + return { + ...state, + fetchingEnrollmentGroupSummary: false, + fetchedEnrollmentGroupSummary: true, + enrollmentGroupSummary: action.payload.data.groupEnrollmentSummary, + enrollmentGroupSummaryError: formatGraphQLError(action.payload), + }; + case ERROR(ACTION_TYPE.ENROLLMENT_GROUP_SUMMARY): + return { + ...state, + fetchingEnrollmentGroupSummary: false, + enrollmentGroupSummaryError: formatServerError(action.payload), + }; case REQUEST(ACTION_TYPE.GET_INDIVIDUAL_UPLOAD_HISTORY): return { ...state, diff --git a/src/translations/en.json b/src/translations/en.json index d67254a..6838f82 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -72,6 +72,7 @@ "enrollment": { "buttonLabel": "ENROLLMENT", "title": "Enrollment of Individuals to the Programme", + "titleGroup": "Enrollment of Groups to the Programme", "label": "Enroll Individuals to the program", "cancel": "Cancel", "summary": "Preview Summary of Enrollment", @@ -89,8 +90,17 @@ "numberOfIndividualsAssignedToSelectedProgramme": "Number Of Individuals Already Assigned to Selected Programme", "numberOfIndividualsToBeUploaded": "Number Of Individuals to be Uploaded", "confirmMessageDialog": "Are you sure you want to confirm the enrollment of the selected individuals into the {benefitPlanName} Programme?", + "confirmGroupMessageDialog": "Are you sure you want to confirm the enrollment of the selected groups into the {benefitPlanName} Programme?", "previewIndividuals": "Preview Individuals", - "close": "Close" + "previewGroups": "Preview Groups", + "close": "Close", + "totalNumberOfGroups": "Total Number of Groups", + "numberOfSelectedGroups": "Number Of Selected Groups", + "numberOfGroupsAssignedToProgramme": "Number Of Groups Assigned to Programme", + "numberOfGroupsNotAssignedToProgramme": "Number Of Groups not Assigned To Programme", + "numberOfGroupsAssignedToSelectedProgramme": "Number of Groups Assigned to Selected Programme", + "numberOfGroupsToUpload": "Number of Groups to Upload" + }, "saveButton.tooltip.enabled": "Save changes", "saveButton.tooltip.disabled": "Please fill General Information fields first",