Skip to content

Commit

Permalink
Merge pull request #54 from openimis/feature/CM-717
Browse files Browse the repository at this point in the history
CM-717: As an auditor I want to have clear information about the groups individual belonged to
  • Loading branch information
jdolkowski authored Mar 6, 2024
2 parents cba0e82 + 5257752 commit 53e341f
Show file tree
Hide file tree
Showing 10 changed files with 416 additions and 8 deletions.
17 changes: 17 additions & 0 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ const GROUP_FULL_PROJECTION = [
'userUpdated {username}',
];

const GROUP_INDIVIDUAL_HISTORY_FULL_PROJECTION = [
'id',
'individual {id, firstName, lastName, dob}',
'group {id}',
'role',
'isDeleted',
'dateCreated',
'dateUpdated',
'jsonExt',
'version',
];

const GROUP_HISTORY_FULL_PROJECTION = GROUP_FULL_PROJECTION.filter(
(item) => item !== 'head {firstName, lastName}',
);
Expand Down Expand Up @@ -125,6 +137,11 @@ export function fetchGroupHistory(params) {
return graphql(payload, ACTION_TYPE.SEARCH_GROUP_HISTORY);
}

export function fetchGroupIndividualHistory(params) {
const payload = formatPageQueryWithCount('groupIndividualHistory', params, GROUP_INDIVIDUAL_HISTORY_FULL_PROJECTION);
return graphql(payload, ACTION_TYPE.SEARCH_GROUP_INDIVIDUAL_HISTORY);
}

export function fetchUploadHistory(params) {
const payload = formatPageQueryWithCount('individualDataUploadHistory', params, UPLOAD_HISTORY_FULL_PROJECTION());
return graphql(payload, ACTION_TYPE.GET_INDIVIDUAL_UPLOAD_HISTORY);
Expand Down
80 changes: 80 additions & 0 deletions src/components/GroupIndividualHistoryFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useEffect } from 'react';
import { injectIntl } from 'react-intl';
import { formatMessage } from '@openimis/fe-core';
import { Grid } from '@material-ui/core';
import { withTheme, withStyles } from '@material-ui/core/styles';
import _debounce from 'lodash/debounce';
import { DEFAULT_DEBOUNCE_TIME } from '../constants';
import { defaultFilterStyles } from '../util/styles';
import GroupIndividualRolePicker from '../pickers/GroupIndividualRolePicker';
import GroupPicker from '../pickers/GroupPicker';

function GroupIndividualHistoryFilter({
intl, classes, filters, onChangeFilters, groupId,
}) {
const debouncedOnChangeFilters = _debounce(onChangeFilters, DEFAULT_DEBOUNCE_TIME);

const filterValue = (filterName) => filters?.[filterName]?.value;

const onChangeStringFilter = (filterName, lookup = null) => (value) => {
if (lookup) {
debouncedOnChangeFilters([
{
id: filterName,
value,
filter: `${filterName}_${lookup}: "${value}"`,
},
]);
} else {
onChangeFilters([
{
id: filterName,
value,
filter: `${filterName}: "${value}"`,
},
]);
}
};

const handleGroupId = onChangeStringFilter('group_Id');
useEffect(() => {
if (filters?.group_Id?.value !== groupId) {
handleGroupId(groupId);
}
}, [groupId]);

return (
<Grid container className={classes.form}>
<Grid item xs={2} className={classes.item}>
<GroupPicker
withNull
nullLabel={formatMessage(intl, 'individual', 'any')}
value={filterValue('group_Id')}
onChange={(value) => onChangeFilters([
{
id: 'group_Id',
value,
filter: `group_Id: "${value}"`,
},
])}
/>
</Grid>
<Grid item xs={2} className={classes.item}>
<GroupIndividualRolePicker
withNull
nullLabel={formatMessage(intl, 'individual', 'any')}
value={filterValue('role')}
onChange={(value) => onChangeFilters([
{
id: 'role',
value,
filter: `role: ${value}`,
},
])}
/>
</Grid>
</Grid>
);
}

export default injectIntl(withTheme(withStyles(defaultFilterStyles)(GroupIndividualHistoryFilter)));
116 changes: 116 additions & 0 deletions src/components/GroupIndividualHistorySearcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl } from 'react-intl';
import {
decodeId,
useModulesManager,
useTranslations,
Searcher,
withHistory,
withModulesManager,
} from '@openimis/fe-core';
import { DEFAULT_PAGE_SIZE, EMPTY_STRING, ROWS_PER_PAGE_OPTIONS } from '../constants';
import GroupIndividualHistoryFilter from './GroupIndividualHistoryFilter';
import { fetchGroupIndividualHistory } from '../actions';

function GroupIndividualHistorySearcher({
individualId,
}) {
const modulesManager = useModulesManager();
const dispatch = useDispatch();
const { formatDateFromISO, formatMessageWithValues } = useTranslations('individual', modulesManager);

const fetchingGroupIndividualHistory = useSelector((state) => state.individual.fetchingGroupIndividualHistory);
const fetchedGroupIndividualHistory = useSelector((state) => state.individual.fetchedGroupIndividualHistory);
const errorGroupIndividualHistory = useSelector((state) => state.individual.errorGroupIndividualHistory);
const groupIndividualHistory = useSelector((state) => state.individual.groupIndividualHistory);
const groupIndividualHistoryPageInfo = useSelector((state) => state.individual.groupIndividualHistoryPageInfo);
const groupIndividualHistoryTotalCount = useSelector((state) => state.individual.groupIndividualHistoryTotalCount);
const fetch = (params) => (individualId ? dispatch(fetchGroupIndividualHistory(params)) : null);

const headers = () => {
const headers = [
'group.id',
'groupIndividual.dateJoined',
'groupIndividual.individual.role',
'groupIndividual.statusOfMembership',
];
return headers;
};

const itemFormatters = () => {
const formatters = [
(groupIndividualHistory) => decodeId(groupIndividualHistory.group.id),
(groupIndividualHistory) => (
groupIndividualHistory.dateCreated
? formatDateFromISO(modulesManager, groupIndividualHistory.dateCreated)
: EMPTY_STRING
),
(groupIndividualHistory) => groupIndividualHistory.role,
(groupIndividualHistory) => (
groupIndividualHistory.version === groupIndividualHistoryTotalCount ? 'Active' : 'Past'
),
];
return formatters;
};

const rowIdentifier = (groupIndividualHistory) => groupIndividualHistory.id;

const sorts = () => [
['id', true],
['dateCreated', true],
];

const defaultFilters = () => {
const filters = {
};
if (individualId !== null && individualId !== undefined) {
filters.individual_Id = {
value: individualId,
filter: `individual_Id: "${individualId}"`,
};
}
return filters;
};

const groupIndividualHistoryFilter = (props) => (
<GroupIndividualHistoryFilter
intl={props.intl}
classes={props.classes}
filters={props.filters}
onChangeFilters={props.onChangeFilters}
/>
);

return (
<div>
<Searcher
module="individual"
FilterPane={groupIndividualHistoryFilter}
fetch={fetch}
items={groupIndividualHistory}
itemsPageInfo={groupIndividualHistoryPageInfo}
fetchingItems={fetchingGroupIndividualHistory}
fetchedItems={fetchedGroupIndividualHistory}
errorItems={errorGroupIndividualHistory}
tableTitle={formatMessageWithValues('groupIndividualHistoryList.searcherResultsTitle', {
groupIndividualHistoryTotalCount,
})}
headers={headers}
itemFormatters={itemFormatters}
sorts={sorts}
rowsPerPageOptions={ROWS_PER_PAGE_OPTIONS}
defaultPageSize={DEFAULT_PAGE_SIZE}
defaultOrderBy="-version"
rowIdentifier={rowIdentifier}
defaultFilters={defaultFilters()}
cacheFiltersKey="groupIndividualHistoryFilterCache"
resetFiltersOnUnmount
/>
</div>
);
}

export default withHistory(
withModulesManager(injectIntl((GroupIndividualHistorySearcher))),
);
39 changes: 39 additions & 0 deletions src/components/GroupIndividualHistoryTab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { Tab } from '@material-ui/core';
import { formatMessage, PublishedComponent } from '@openimis/fe-core';
import { GROUP_INDIVIDUAL_HISTORY_TAB_VALUE } from '../constants';

function GroupIndividualHistoryTabLabel({
intl, onChange, tabStyle, isSelected,
}) {
return (
<Tab
onChange={onChange}
className={tabStyle(GROUP_INDIVIDUAL_HISTORY_TAB_VALUE)}
selected={isSelected(GROUP_INDIVIDUAL_HISTORY_TAB_VALUE)}
value={GROUP_INDIVIDUAL_HISTORY_TAB_VALUE}
label={formatMessage(intl, 'individual', 'groupIndividualHistory.label')}
/>
);
}

function GroupIndividualHistoryTabPanel({
value, rights, individual,
}) {
return (
<PublishedComponent
pubRef="policyHolder.TabPanel"
module="individual"
index={GROUP_INDIVIDUAL_HISTORY_TAB_VALUE}
value={value}
>
<PublishedComponent
rights={rights}
individualId={individual?.id}
pubRef="individual.GroupIndividualHistorySearcher"
/>
</PublishedComponent>
);
}

export { GroupIndividualHistoryTabLabel, GroupIndividualHistoryTabPanel };
83 changes: 83 additions & 0 deletions src/components/GroupTabPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useState } from 'react';
import { Paper, Grid } from '@material-ui/core';
import { Contributions } from '@openimis/fe-core';
import { injectIntl } from 'react-intl';
import { withTheme, withStyles } from '@material-ui/core/styles';
import {
BENEFIT_PLANS_LIST_TAB_VALUE,
GROUPS_TABS_LABEL_CONTRIBUTION_KEY,
GROUPS_TABS_PANEL_CONTRIBUTION_KEY, INDIVIDUALS_LIST_TAB_VALUE,
} from '../constants';

const styles = (theme) => ({
paper: theme.paper.paper,
tableTitle: theme.table.title,
tabs: {
display: 'flex',
alignItems: 'center',
},
selectedTab: {
borderBottom: '4px solid white',
},
unselectedTab: {
borderBottom: '4px solid transparent',
},
button: {
marginLeft: 'auto',
padding: theme.spacing(1),
fontSize: '0.875rem',
textTransform: 'none',
},
});

function GroupTabPanel({
intl,
rights,
classes,
individual,
setConfirmedAction,
group, editedGroupIndividual,
setEditedGroupIndividual,
groupIndividualIds,
}) {
const [activeTab, setActiveTab] = useState(individual ? BENEFIT_PLANS_LIST_TAB_VALUE : INDIVIDUALS_LIST_TAB_VALUE);

const isSelected = (tab) => tab === activeTab;

const tabStyle = (tab) => (isSelected(tab) ? classes.selectedTab : classes.unselectedTab);

const handleChange = (_, tab) => setActiveTab(tab);

return (
<Paper className={classes.paper}>
<Grid container className={`${classes.tableTitle} ${classes.tabs}`}>
<Contributions
contributionKey={GROUPS_TABS_LABEL_CONTRIBUTION_KEY}
intl={intl}
rights={rights}
value={activeTab}
onChange={handleChange}
isSelected={isSelected}
tabStyle={tabStyle}
group={group}
individual={individual}
editedGroupIndividual={editedGroupIndividual}
setEditedGroupIndividual={setEditedGroupIndividual}
/>
</Grid>
<Contributions
contributionKey={GROUPS_TABS_PANEL_CONTRIBUTION_KEY}
rights={rights}
value={activeTab}
individual={individual}
group={group}
groupIndividualIds={groupIndividualIds}
setConfirmedAction={setConfirmedAction}
editedGroupIndividual={editedGroupIndividual}
setEditedGroupIndividual={setEditedGroupIndividual}
/>
</Paper>
);
}

export default injectIntl(withTheme(withStyles(styles)(GroupTabPanel)));
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ export const INDIVIDUALS_LIST_TAB_VALUE = 'IndividualsListTab';
export const INDIVIDUAL_CHANGELOG_TAB_VALUE = 'IndividualChangelogTab';
export const INDIVIDUAL_TASK_TAB_VALUE = 'IndividualTaskTab';
export const GROUP_CHANGELOG_TAB_VALUE = 'GroupChangelogTab';
export const GROUP_INDIVIDUAL_HISTORY_TAB_VALUE = 'GroupIndividualHistoryTab';
export const GROUP_TASK_TAB_VALUE = 'GroupTaskTab';
export const BENEFITS_TAB_VALUE = 'BenefitTaskTab';
export const INDIVIDUAL_TABS_LABEL_CONTRIBUTION_KEY = 'individual.TabPanel.label';
export const INDIVIDUAL_TABS_PANEL_CONTRIBUTION_KEY = 'individual.TabPanel.panel';

export const GROUPS_TABS_LABEL_CONTRIBUTION_KEY = 'group.TabPanel.label';
export const GROUPS_TABS_PANEL_CONTRIBUTION_KEY = 'group.TabPanel.panel';

export const BENEFIT_PLAN_TABS_LABEL_CONTRIBUTION_KEY = 'individual.BenefitPlansListTabLabel';
export const BENEFIT_PLAN_TABS_PANEL_CONTRIBUTION_KEY = 'individual.BenefitPlansListTabPanel';
export const TASK_CONTRIBUTION_KEY = 'tasksManagement.tasks';
Expand Down
Loading

0 comments on commit 53e341f

Please sign in to comment.