Skip to content

Commit

Permalink
CM-879: add undo delete individual (#71)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan <[email protected]>
  • Loading branch information
jdolkowski and Jan authored Apr 30, 2024
1 parent c106a9c commit 6af5f69
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 46 deletions.
16 changes: 16 additions & 0 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,22 @@ export function deleteIndividual(individual, clientMutationLabel) {
);
}

export function undoDeleteIndividual(individual, clientMutationLabel) {
const individualUuids = `ids: ["${individual?.id}"]`;
const mutation = formatMutation('undoDeleteIndividual', individualUuids, clientMutationLabel);
const requestedDateTime = new Date();
return graphql(
mutation.payload,
[REQUEST(ACTION_TYPE.MUTATION), SUCCESS(ACTION_TYPE.UNDO_DELETE_INDIVIDUAL), ERROR(ACTION_TYPE.MUTATION)],
{
actionType: ACTION_TYPE.UNDO_DELETE_INDIVIDUAL,
clientMutationId: mutation.clientMutationId,
clientMutationLabel,
requestedDateTime,
},
);
}

export function deleteGroupIndividual(groupIndividual, clientMutationLabel) {
const groupIndividualUuids = `ids: ["${groupIndividual?.id}"]`;
const mutation = formatMutation('removeIndividualFromGroup', groupIndividualUuids, clientMutationLabel);
Expand Down
11 changes: 2 additions & 9 deletions src/components/GroupIndividualFilter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React from 'react';
import { injectIntl } from 'react-intl';
import { TextInput, PublishedComponent, formatMessage } from '@openimis/fe-core';
import { Grid } from '@material-ui/core';
Expand All @@ -9,7 +9,7 @@ import { defaultFilterStyles } from '../util/styles';
import GroupIndividualRolePicker from '../pickers/GroupIndividualRolePicker';

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

Expand Down Expand Up @@ -37,13 +37,6 @@ function GroupIndividualFilter({
}
};

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}>
Expand Down
30 changes: 20 additions & 10 deletions src/components/GroupIndividualSearcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,16 +288,25 @@ function GroupIndividualSearcher({
return setFailedExport(false);
}, [groupIndividualExport]);

const defaultFilters = () => ({
isDeleted: {
value: false,
filter: 'isDeleted: false',
},
group_Id: {
value: groupId,
filter: `group_Id: "${groupId}"`,
},
});
const defaultFilters = () => {
const filters = {
isDeleted: {
value: false,
filter: 'isDeleted: false',
},
individual_IsDeleted: {
value: false,
filter: 'individual_IsDeleted: false',
},
};
if (groupId) {
filters.group_Id = {
value: groupId,
filter: `group_Id: "${groupId}"`,
};
}
return filters;
};

const groupBeneficiaryFilter = (props) => (
<GroupIndividualFilter
Expand Down Expand Up @@ -361,6 +370,7 @@ function GroupIndividualSearcher({
}}
exportFieldLabel={formatMessage(intl, 'individual', 'export.label')}
cacheFiltersKey="groupIndividualsFilterCache"
resetFiltersOnUnmount
/>
{failedExport && (
<Dialog open={failedExport} fullWidth maxWidth="sm">
Expand Down
40 changes: 32 additions & 8 deletions src/components/IndividualFilter.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import React from 'react';
import { injectIntl } from 'react-intl';
import { TextInput, PublishedComponent } from '@openimis/fe-core';
import { Grid } from '@material-ui/core';
import { TextInput, PublishedComponent, formatMessage } from '@openimis/fe-core';
import { Grid, FormControlLabel, Checkbox } from '@material-ui/core';
import { withTheme, withStyles } from '@material-ui/core/styles';
import _debounce from 'lodash/debounce';
import { CONTAINS_LOOKUP, DEFAULT_DEBOUNCE_TIME, EMPTY_STRING } from '../constants';
import {
CONTAINS_LOOKUP, DEFAULT_DEBOUNCE_TIME, EMPTY_STRING, INDIVIDUAL_MODULE_NAME,
} from '../constants';
import { defaultFilterStyles } from '../util/styles';

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

const filterValue = (filterName) => filters?.[filterName]?.value;
const filterValue = (k) => (!!filters && !!filters[k] ? filters[k].value : null);

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

Expand All @@ -36,19 +38,29 @@ function IndividualFilter({
}
};

const onChangeFilter = (k, v) => {
onChangeFilters([
{
id: k,
value: v,
filter: `${k}: ${v}`,
},
]);
};

return (
<Grid container className={classes.form}>
<Grid item xs={2} className={classes.item}>
<TextInput
module="individual"
module={INDIVIDUAL_MODULE_NAME}
label="individual.firstName"
value={filterTextFieldValue('firstName')}
onChange={onChangeStringFilter('firstName', CONTAINS_LOOKUP)}
/>
</Grid>
<Grid item xs={2} className={classes.item}>
<TextInput
module="individual"
module={INDIVIDUAL_MODULE_NAME}
label="individual.lastName"
value={filterTextFieldValue('lastName')}
onChange={onChangeStringFilter('lastName', CONTAINS_LOOKUP)}
Expand All @@ -57,7 +69,7 @@ function IndividualFilter({
<Grid item xs={2} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module="individual"
module={INDIVIDUAL_MODULE_NAME}
label="individual.dob"
value={filterValue('dob')}
onChange={(v) => onChangeFilters([
Expand All @@ -69,6 +81,18 @@ function IndividualFilter({
])}
/>
</Grid>
<Grid item xs={2} className={classes.item}>
<FormControlLabel
control={(
<Checkbox
checked={filterValue('isDeleted')}
onChange={(event) => onChangeFilter('isDeleted', event.target.checked)}
name="isDeleted"
/>
)}
label={formatMessage(intl, INDIVIDUAL_MODULE_NAME, 'isDeleted')}
/>
</Grid>
</Grid>
);
}
Expand Down
50 changes: 45 additions & 5 deletions src/components/IndividualSearcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import {
} from '@material-ui/core';
import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete';
import UndoIcon from '@material-ui/icons/Undo';
import {
fetchIndividuals, deleteIndividual, downloadIndividuals, clearIndividualExport,
fetchIndividuals, deleteIndividual, downloadIndividuals, clearIndividualExport, undoDeleteIndividual,
} from '../actions';
import {
DEFAULT_PAGE_SIZE,
Expand Down Expand Up @@ -74,12 +75,15 @@ function IndividualSearcher({
isModalEnrollment,
advancedCriteria,
benefitPlanToEnroll,
undoDeleteIndividual,
}) {
const dispatch = useDispatch();
const [individualToDelete, setIndividualToDelete] = useState(null);
const [individualToUndo, setIndividualToUndo] = useState(null);
const [appliedCustomFilters, setAppliedCustomFilters] = useState([CLEARED_STATE_FILTER]);
const [appliedFiltersRowStructure, setAppliedFiltersRowStructure] = useState([CLEARED_STATE_FILTER]);
const [deletedIndividualUuids, setDeletedIndividualUuids] = useState([]);
const [undoIndividualUuids, setUndoIndividualUuids] = useState([]);
const [exportFields, setExportFields] = useState([
'id',
'first_name',
Expand Down Expand Up @@ -123,13 +127,23 @@ function IndividualSearcher({
formatMessage(intl, 'individual', 'individual.delete.confirm.message'),
);

const openUndoIndividualConfirmDialog = () => coreConfirm(
formatMessageWithValues(intl, 'individual', 'individual.undo.confirm.title', {
firstName: individualToUndo.firstName,
lastName: individualToUndo.lastName,
}),
formatMessage(intl, 'individual', 'individual.undo.confirm.message'),
);

const onDoubleClick = (individual, newTab = false) => rights.includes(RIGHT_INDIVIDUAL_UPDATE)
&& !deletedIndividualUuids.includes(individual.id)
&& historyPush(modulesManager, history, 'individual.route.individual', [individual?.id], newTab);

const onDelete = (individual) => setIndividualToDelete(individual);
const onUndo = (individual) => setIndividualToUndo(individual);

useEffect(() => individualToDelete && openDeleteIndividualConfirmDialog(), [individualToDelete]);
useEffect(() => individualToUndo && openUndoIndividualConfirmDialog(), [individualToUndo]);

useEffect(() => {
if (individualToDelete && confirmed) {
Expand All @@ -141,9 +155,21 @@ function IndividualSearcher({
);
setDeletedIndividualUuids([...deletedIndividualUuids, individualToDelete.id]);
}
if (individualToUndo && confirmed) {
undoDeleteIndividual(
individualToUndo,
formatMessageWithValues(intl, 'individual', 'individual.undo.mutationLabel', {
id: individualToUndo?.id,
}),
);
setUndoIndividualUuids([...undoIndividualUuids, individualToUndo.id]);
}
if (individualToDelete && confirmed !== null) {
setIndividualToDelete(null);
}
if (individualToUndo && confirmed !== null) {
setIndividualToUndo(null);
}
return () => confirmed && clearConfirm(false);
}, [confirmed]);

Expand All @@ -168,6 +194,9 @@ function IndividualSearcher({
if (rights.includes(RIGHT_INDIVIDUAL_UPDATE)) {
headers.push('emptyLabel');
}
if (rights.includes(RIGHT_INDIVIDUAL_DELETE)) {
headers.push('emptyLabel');
}
return headers;
};

Expand All @@ -191,16 +220,25 @@ function IndividualSearcher({
));
}
if (rights.includes(RIGHT_INDIVIDUAL_DELETE) && isModalEnrollment === false) {
formatters.push((individual) => (
<Tooltip title={formatMessage(intl, 'individual', 'deleteButtonTooltip')}>
formatters.push((individual) => (!individual?.isDeleted ? (
<Tooltip title={formatMessage(intl, INDIVIDUAL_MODULE_NAME, 'deleteButtonTooltip')}>
<IconButton
onClick={() => onDelete(individual)}
disabled={deletedIndividualUuids.includes(individual.id)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
));
) : (
<Tooltip title={formatMessage(intl, INDIVIDUAL_MODULE_NAME, 'undoButtonTooltip')}>
<IconButton
onClick={() => onUndo(individual)}
disabled={undoIndividualUuids.includes(individual.id)}
>
<UndoIcon />
</IconButton>
</Tooltip>
)));
}
return formatters;
};
Expand All @@ -213,7 +251,8 @@ function IndividualSearcher({
['dob', true],
];

const isRowDisabled = (_, individual) => deletedIndividualUuids.includes(individual.id);
const isRowDisabled = (_, individual) => deletedIndividualUuids.includes(individual.id)
|| undoIndividualUuids.includes(individual.id);

const [failedExport, setFailedExport] = useState(false);

Expand Down Expand Up @@ -355,6 +394,7 @@ const mapDispatchToProps = (dispatch) => bindActionCreators(
deleteIndividual,
downloadIndividuals,
clearIndividualExport,
undoDeleteIndividual,
coreConfirm,
clearConfirm,
journalize,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React from 'react';
import { FormattedMessage } from '@openimis/fe-core';

const IndividualUpdateTaskTableHeaders = () => [
const IndividualTaskTableHeaders = () => [
<FormattedMessage module="individual" id="individual.firstName" />,
<FormattedMessage module="individual" id="individual.lastName" />,
<FormattedMessage module="individual" id="individual.dob" />,
];

const IndividualUpdateTaskItemFormatters = () => [
const IndividualTaskItemFormatters = () => [
(individual) => individual?.first_name,
(individual) => individual?.last_name,
(individual) => individual?.dob,
];

export { IndividualUpdateTaskTableHeaders, IndividualUpdateTaskItemFormatters };
export { IndividualTaskTableHeaders, IndividualTaskItemFormatters };
12 changes: 6 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import GroupIndividualSearcher from './components/GroupIndividualSearcher';
import { clearIndividualExport, downloadIndividuals, fetchIndividuals } from './actions';
import IndividualHistorySearcher from './components/IndividualHistorySearcher';
import {
IndividualUpdateTaskItemFormatters,
IndividualUpdateTaskTableHeaders,
} from './components/tasks/IndividualUpdateTasks';
IndividualTaskItemFormatters,
IndividualTaskTableHeaders,
} from './components/tasks/IndividualTasks';
import GroupHistorySearcher from './components/GroupHistorySearcher';
import { GroupChangelogTabLabel, GroupChangelogTabPanel } from './components/GroupChangelogTab';
import { GroupTaskTabLabel, GroupTaskTabPanel } from './components/GroupTaskTab';
Expand Down Expand Up @@ -135,9 +135,9 @@ const DEFAULT_CONFIG = {
'individual.BenefitPlansListTabLabel': [BENEFIT_PLAN_TABS_LABEL_REF_KEY],
'individual.BenefitPlansListTabPanel': [BENEFIT_PLAN_TABS_PANEL_REF_KEY],
'tasksManagement.tasks': [{
text: <FormattedMessage module={INDIVIDUAL_MODULE_NAME} id="individual.tasks.update.title" />,
tableHeaders: IndividualUpdateTaskTableHeaders,
itemFormatters: IndividualUpdateTaskItemFormatters,
text: <FormattedMessage module={INDIVIDUAL_MODULE_NAME} id="individual.tasks.title" />,
tableHeaders: IndividualTaskTableHeaders,
itemFormatters: IndividualTaskItemFormatters,
taskSource: ['IndividualService'],
taskCode: INDIVIDUAL_LABEL,
},
Expand Down
Loading

0 comments on commit 6af5f69

Please sign in to comment.