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

OM-325: add assignment progress tracker #83

Merged
merged 5 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
99 changes: 87 additions & 12 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ACTION_TYPE } from './reducer';
import {
CLEAR, ERROR, REQUEST, SUCCESS,
} from './utils/action-type';
import { EMPTY_STRING } from './constants';
import { DRAFT_FORM_TYPE, EMPTY_STRING } from './constants';

const WORKER_VOUCHER_PROJECTION = (modulesManager) => [
'id',
Expand Down Expand Up @@ -56,12 +56,16 @@ export const GROUP_PROJECTION = (modulesManager, withWorkers = true) => [
`policyholder ${modulesManager.getProjection('policyHolder.PolicyHolderPicker.projection')}`,
`groupWorkers {
totalCount
${withWorkers ? `edges {
${
withWorkers
? `edges {
node {
isDeleted,
insuree ${modulesManager.getProjection('insuree.InsureePicker.projection')},
}
}` : ''}
}`
: ''
}
}`,
];

Expand Down Expand Up @@ -336,9 +340,7 @@ export async function fetchAllPages(dispatch, query, variables, categories) {
while (hasNextPage) {
try {
// eslint-disable-next-line no-await-in-loop
const response = await dispatch(
graphqlWithVariables(query, { ...variables, after }),
);
const response = await dispatch(graphqlWithVariables(query, { ...variables, after }));
const data = response?.payload?.data || {};

const pageInfos = categories.map((category) => processCategoryData(category, data, allData));
Expand Down Expand Up @@ -414,12 +416,11 @@ export async function fetchAllAvailableWorkers(dispatch, economicUnitCode, dateR
}
}
`;
const response = await fetchAllPages(
dispatch,
query,
{ economicUnitCode, dateRange },
['allAvailableWorkers', 'previousWorkers', 'previousDayWorkers'],
);
const response = await fetchAllPages(dispatch, query, { economicUnitCode, dateRange }, [
'allAvailableWorkers',
'previousWorkers',
'previousDayWorkers',
]);

return response;
}
Expand Down Expand Up @@ -679,3 +680,77 @@ export function fetchPublicVoucherDetails(voucherUuid) {
{ voucherUuid },
);
}

export function fetchVoucherDraftForm(modulesManager, economicUnitCode) {
return graphqlWithVariables(
`
query getCurrentDraft($economicUnitCode: String) {
voucherFormDraft(policyholder_Code: $economicUnitCode) {
edges {
node {
policyholder ${modulesManager.getProjection('policyHolder.PolicyHolderPicker.projection')}
workers ${modulesManager.getProjection('insuree.InsureePicker.projection')}
dateRanges { startDate endDate }
}
}
}
}
`,
{ economicUnitCode },
);
}

export function createOrUpdateVoucherDraftForm(
voucherAssignment,
clientMutationLabel,
typeOfForm = DRAFT_FORM_TYPE.ASSIGNMENT,
) {
const { employer, workers, dateRanges } = voucherAssignment;
const workerIds = workers ? workers.map((worker) => decodeId(worker.id)).join(', ') : EMPTY_STRING;

const mutationInput = `
typeOfForm: "${typeOfForm}"
economicUnitCode: "${employer.code}"
workers: [${workerIds}]
${dateRanges ? `dateRanges: ${formatGraphQLDateRanges(dateRanges)}` : 'dateRanges: []'}
`;

const mutation = formatMutation('createOrUpdateVoucherDraftForm', mutationInput, clientMutationLabel);
const requestedDateTime = new Date();

return graphql(
mutation.payload,
[
REQUEST(ACTION_TYPE.MUTATION),
SUCCESS(ACTION_TYPE.CREATE_OR_UPDATE_ASSIGNMENT_DRAFT),
ERROR(ACTION_TYPE.MUTATION),
],
{
actionType: ACTION_TYPE.CREATE_OR_UPDATE_ASSIGNMENT_DRAFT,
clientMutationId: mutation.clientMutationId,
clientMutationLabel,
requestedDateTime,
},
);
}

export function deleteVoucherDraftForm(economicUnit, clientMutationLabel, typeOfForm = DRAFT_FORM_TYPE.ASSIGNMENT) {
const mutationInput = `
${`economicUnitCode: "${economicUnit.code}"`}
${`typeOfForm: "${typeOfForm}"`}
`;

const mutation = formatMutation('deleteVoucherDraftForm', mutationInput, clientMutationLabel);
const requestedDateTime = new Date();

return graphql(
mutation.payload,
[REQUEST(ACTION_TYPE.MUTATION), SUCCESS(ACTION_TYPE.DELETE_ASSIGNMENT_DRAFT), ERROR(ACTION_TYPE.MUTATION)],
{
actionType: ACTION_TYPE.DELETE_ASSIGNMENT_DRAFT,
clientMutationId: mutation.clientMutationId,
clientMutationLabel,
requestedDateTime,
},
);
}
2 changes: 1 addition & 1 deletion src/components/VoucherAcquirementGenericVoucher.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function VoucherAcquirementGenericVoucher() {
historyPush(modulesManager, history, REF_ROUTE_BILL, [billId]);
dispatch(
coreAlert(
formatMessage('menu.voucherAcquirementSuccess'),
formatMessage('menu.voucherAcquirement'),
formatMessageWithValues('workerVoucher.VoucherAcquirementForm.genericVoucherConfirmation', {
quantity: voucherAcquirement?.quantity,
}),
Expand Down
2 changes: 1 addition & 1 deletion src/components/VoucherAcquirementSpecificWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function VoucherAcquirementSpecificWorker() {
historyPush(modulesManager, history, REF_ROUTE_BILL, [billId]);
dispatch(
coreAlert(
formatMessage('menu.voucherAcquirementSuccess'),
formatMessage('menu.voucherAcquirement'),
formatMessage('workerVoucher.VoucherAcquirementForm.specificVoucherConfirmation'),
),
);
Expand Down
76 changes: 47 additions & 29 deletions src/components/VoucherAssignmentForm.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'lodash';

import {
Divider, Grid, Paper, Typography, Button, Tooltip,
Button, Divider, Grid, Paper, Tooltip, Typography,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/styles';
import AssignmentIndIcon from '@material-ui/icons/AssignmentInd';
import { makeStyles } from '@material-ui/styles';

import {
InfoButton,
coreAlert,
useModulesManager,
useTranslations,
journalize,
historyPush,
useHistory,
InfoButton,
useModulesManager,
useTranslations,
parseData,
} from '@openimis/fe-core';
import { assignVouchers, voucherAssignmentValidation } from '../actions';
import { MODULE_NAME, REF_ROUTE_WORKER_VOUCHERS, USER_ECONOMIC_UNIT_STORAGE_KEY } from '../constants';
import {
assignVouchers, deleteVoucherDraftForm, fetchVoucherDraftForm, voucherAssignmentValidation,
} from '../actions';
import { MODULE_NAME, REF_ROUTE_WORKER_VOUCHERS } from '../constants';
import AssignmentVoucherForm from './AssignmentVoucherForm';
import VoucherAssignmentConfirmModal from './VoucherAssignmentConfirmModal';
import VoucherAssignmentProgressTracker from './VoucherAssignmentProgressTracker';

export const useStyles = makeStyles((theme) => ({
paper: { ...theme.paper.paper, margin: '10px 0 0 0' },
Expand All @@ -46,7 +50,6 @@ export const useStyles = makeStyles((theme) => ({
}));

function VoucherAssignmentForm() {
const prevSubmittingMutationRef = useRef();
const modulesManager = useModulesManager();
const dispatch = useDispatch();
const classes = useStyles();
Expand All @@ -57,8 +60,8 @@ function VoucherAssignmentForm() {
const [assignmentSummaryLoading, setAssignmentSummaryLoading] = useState(false);
const [isAssignmentLoading, setIsAssignmentLoading] = useState(false);
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
const { mutation, submittingMutation } = useSelector((state) => state.workerVoucher);
const { economicUnit } = useSelector((state) => state.policyHolder);
const prevEconomicUnitRef = useRef();

const assignmentBlocked = (voucherAssignment) => !voucherAssignment?.workers?.length
|| !voucherAssignment?.dateRanges?.length;
Expand Down Expand Up @@ -93,6 +96,7 @@ function VoucherAssignmentForm() {
'Assign Vouchers',
),
);
await dispatch(deleteVoucherDraftForm(economicUnit, 'Delete Voucher Draft'));
historyPush(modulesManager, history, REF_ROUTE_WORKER_VOUCHERS);
dispatch(
coreAlert(
Expand All @@ -109,26 +113,38 @@ function VoucherAssignmentForm() {
setIsConfirmationModalOpen((prevState) => !prevState);
};

useEffect(() => {
if (prevSubmittingMutationRef.current && !submittingMutation) {
dispatch(journalize(mutation));
useEffect(async () => {
if (_.isEqual(economicUnit, prevEconomicUnitRef.current)) {
return;
}
}, [submittingMutation]);

useEffect(() => {
prevSubmittingMutationRef.current = submittingMutation;
});

useEffect(() => {
const storedUserEconomicUnit = localStorage.getItem(USER_ECONOMIC_UNIT_STORAGE_KEY);
if (storedUserEconomicUnit) {
const userEconomicUnit = JSON.parse(storedUserEconomicUnit);
setVoucherAssignment((prevState) => ({
...prevState,
employer: userEconomicUnit,
workers: [],
dateRanges: [],

try {
const response = await dispatch(fetchVoucherDraftForm(modulesManager, economicUnit.code));

if (response.error) {
// eslint-disable-next-line no-console
console.error(`[ERROR]: Error while fetching voucher draft form. ${response.error}`);
}

const voucherFormDraft = parseData(response.payload.data.voucherFormDraft)?.[0];

if (!voucherFormDraft) {
setVoucherAssignment(() => ({
employer: economicUnit,
workers: [],
dateRanges: [],
}));
return;
}

setVoucherAssignment(() => ({
employer: voucherFormDraft.policyholder,
workers: voucherFormDraft.workers,
dateRanges: voucherFormDraft.dateRanges,
}));
} catch (error) {
// eslint-disable-next-line no-console
console.error(`[ERROR]: Error during assignment init. ${error}`);
}
}, [setVoucherAssignment, economicUnit]);

Expand Down Expand Up @@ -168,6 +184,8 @@ function VoucherAssignmentForm() {
</Grid>
</Grid>
<Divider />
<VoucherAssignmentProgressTracker voucherAssignment={voucherAssignment} />
<Divider />
<AssignmentVoucherForm
edited={voucherAssignment}
onEditedChange={setVoucherAssignment}
Expand Down
77 changes: 77 additions & 0 deletions src/components/VoucherAssignmentProgressTracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, {
useEffect, useRef, useState, useCallback,
} from 'react';
import { useDispatch } from 'react-redux';
import _ from 'lodash';
import _debounce from 'lodash/debounce';

import { CircularProgress, Grid, Typography } from '@material-ui/core';
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import { makeStyles } from '@material-ui/styles';

import { useToast, useTranslations } from '@openimis/fe-core';
import { createOrUpdateVoucherDraftForm } from '../actions';
import { DEFAULT_DEBOUNCE_TIME, MODULE_NAME } from '../constants';

const useStyles = makeStyles((theme) => ({
container: {
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
gap: '4px',
padding: theme.spacing(1),
},
}));

function VoucherAssignmentProgressTracker({ voucherAssignment }) {
const prevVoucherAssignment = useRef();
const dispatch = useDispatch();
const classes = useStyles();
const { showError } = useToast();
const { formatMessage } = useTranslations(MODULE_NAME);
const [isSaving, setIsSaving] = useState(false);

const saveAssignmentDraft = useCallback(
_debounce(async (voucherAssignment) => {
try {
await dispatch(createOrUpdateVoucherDraftForm(voucherAssignment, 'Save Assignment Draft'));
} catch (error) {
showError('[ERROR]: Error while saving the progress.');
// eslint-disable-next-line no-console
console.error(error);
} finally {
setIsSaving(false);
}
}, DEFAULT_DEBOUNCE_TIME * 2),
[dispatch, showError],
);

useEffect(() => {
if (
!_.isEqual(voucherAssignment, prevVoucherAssignment.current)
&& !_.isEmpty(voucherAssignment)
) {
setIsSaving(true);
prevVoucherAssignment.current = voucherAssignment;
saveAssignmentDraft(voucherAssignment);
}
}, [voucherAssignment, saveAssignmentDraft]);

return (
<Grid className={classes.container}>
{isSaving ? (
<>
<CircularProgress size={16} thickness={5} />
<Typography variant="body2">{formatMessage('VoucherAssignmentProgressTracker.saving')}</Typography>
</>
) : (
<>
<CheckCircleIcon color="primary" fontSize="small" />
<Typography variant="body2">{formatMessage('VoucherAssignmentProgressTracker.upToDate')}</Typography>
</>
)}
</Grid>
);
}

export default VoucherAssignmentProgressTracker;
4 changes: 4 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,7 @@ export const UPLOAD_STAGE = {
FILE_UPLOAD: 'FILE_UPLOAD',
WORKER_UPLOAD: 'WORKER_UPLOAD',
};

export const DRAFT_FORM_TYPE = {
ASSIGNMENT: 'ASSIGNMENT',
};
1 change: 1 addition & 0 deletions src/pickers/WorkerMultiplePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ function WorkerMultiplePicker({
flexDirection: 'column',
gap: '8px',
alignItems: 'end',
marginTop: '8px',
}}
>
<Autocomplete
Expand Down
2 changes: 2 additions & 0 deletions src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export const ACTION_TYPE = {
DELETE_GROUP: 'WORKER_VOUCHER_DELETE_GROUP',
CREATE_GROUP: 'WORKER_VOUCHER_CREATE_GROUP',
UPDATE_GROUP: 'WORKER_VOUCHER_UPDATE_GROUP',
CREATE_OR_UPDATE_ASSIGNMENT_DRAFT: 'WORKER_VOUCHER_CREATE_OR_UPDATE_ASSIGNMENT_DRAFT',
DELETE_ASSIGNMENT_DRAFT: 'WORKER_VOUCHER_DELETE_ASSIGNMENT_DRAFT',
};

const STORE_STATE = {
Expand Down
Loading
Loading