Skip to content

Commit

Permalink
OM-347: create a voucher public page (#74)
Browse files Browse the repository at this point in the history
* OM-347: create a voucher public page

* OM-347: add warning state

* OM-330: add qr codes to vouchers (#81)

* OM-347: adjust public page, add toasts to worker page
  • Loading branch information
olewandowski1 authored Oct 24, 2024
1 parent c838669 commit 5558552
Show file tree
Hide file tree
Showing 16 changed files with 453 additions and 79 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"no-shadow": "off", // disabled due to use of bindActionCreators
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], // disabled due to naming consistency with other modules
"import/no-unresolved": "off", // disable due to module architecture. For modules most references are marked as unresolved
"max-len": ["error", { "code": 120 }]
"max-len": ["error", { "code": 120 }],
"import/no-extraneous-dependencies": "off"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"dist"
],
"dependencies": {
"react-qr-code": "^2.0.15",
"react-to-print": "^2.15.1"
}
}
21 changes: 21 additions & 0 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ const WORKER_VOUCHER_PROJECTION = (modulesManager) => [
`policyholder ${modulesManager.getProjection('policyHolder.PolicyHolderPicker.projection')}`,
];

const WORKER_VOUCHER_CHECK_PROJECTION = [
'isExisted',
'isValid',
'assignedDate',
'employerCode',
'employerName',
];

const VOUCHER_PRICE_PROJECTION = () => ['id', 'uuid', 'key', 'value', 'dateValidFrom', 'dateValidTo', 'isDeleted'];

// eslint-disable-next-line no-unused-vars
Expand Down Expand Up @@ -645,3 +653,16 @@ export function deleteGroup(economicUnit, groupsToDelete, clientMutationLabel) {
},
);
}

export function fetchPublicVoucherDetails(voucherUuid) {
return graphqlWithVariables(
`
query checkVoucherValidity($voucherUuid: String!) {
voucherCheck(code: $voucherUuid) {
${WORKER_VOUCHER_CHECK_PROJECTION.join('\n')}
}
}
`,
{ voucherUuid },
);
}
1 change: 0 additions & 1 deletion src/components/UploadWorkerModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const useStyles = makeStyles((theme) => ({
uploadBox: {
width: '100%',
minHeight: '140px',
backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
display: 'flex',
Expand Down
2 changes: 2 additions & 0 deletions src/components/VoucherDetailsPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
FormattedMessage, useModulesManager, useHistory, historyPush,
} from '@openimis/fe-core';
import { PRINTABLE, REF_ROUTE_BILL, VOUCHER_RIGHT_SEARCH } from '../constants';
import { isTheVoucherExpired } from '../utils/utils';
import VoucherDetailsEmployer from './VoucherDetailsEmployer';
import VoucherDetailsVoucher from './VoucherDetailsVoucher';
import VoucherDetailsWorker from './VoucherDetailsWorker';
Expand Down Expand Up @@ -67,6 +68,7 @@ function VoucherDetailsPanel({
variant="contained"
color="primary"
startIcon={<PrintIcon />}
disabled={!workerVoucher.billId || isTheVoucherExpired(workerVoucher)}
onClick={(e) => {
e.preventDefault();
handlePrint(null, () => voucherPrintTemplateRef.current);
Expand Down
29 changes: 22 additions & 7 deletions src/components/VoucherDetailsPrintTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {
forwardRef, useState, useEffect, useMemo,
} from 'react';
import { useDispatch } from 'react-redux';
import clsx from 'clsx';

import { Divider } from '@material-ui/core';
import { makeStyles } from '@material-ui/styles';
Expand All @@ -11,6 +12,7 @@ import {
EMPTY_STRING, MODULE_NAME, REF_GET_BILL_LINE_ITEM, WORKER_VOUCHER_STATUS,
} from '../constants';
import { extractEmployerName, extractWorkerName } from '../utils/utils';
import VoucherQRCode from './VoucherQRCode';

const useStyles = makeStyles((theme) => ({
'@global': {
Expand All @@ -22,10 +24,9 @@ const useStyles = makeStyles((theme) => ({
},
container: {
display: 'flex',
backgroundColor: theme.palette.background.default,
flexDirection: 'row',
height: '320px',
padding: '24px',
padding: '12px',
justifyContent: 'space-between',
borderLeft: `10px solid ${theme.palette.primary.main}`,
borderRight: `10px solid ${theme.palette.primary.main}`,
Expand All @@ -39,14 +40,14 @@ const useStyles = makeStyles((theme) => ({
fontSize: '48px',
fontWeight: 900,
letterSpacing: '-2px',
textAlign: 'right',
textAlign: 'left',
},
annotation: {
fontSize: '12px',
fontStyle: 'italic',
},
voucherTitle: {
fontSize: '32px',
fontSize: '28px',
fontWeight: 900,
textTransform: 'uppercase',
},
Expand All @@ -58,11 +59,16 @@ const useStyles = makeStyles((theme) => ({
fontSize: '16px',
fontWeight: 500,
},
manualFill: {
fields: {
display: 'flex',
flexDirection: 'column',
},
manualFill: {
gap: '16px',
},
assignedFields: {
gap: '4px',
},
}));

const VoucherDetailsPrintTemplate = forwardRef(({ workerVoucher, logo }, ref) => {
Expand Down Expand Up @@ -114,7 +120,13 @@ const VoucherDetailsPrintTemplate = forwardRef(({ workerVoucher, logo }, ref) =>
</p>
</div>

<div className={classes.manualFill}>
<div
className={clsx({
[classes.fields]: true,
[classes.manualFill]: !isAssignedStatus,
[classes.assignedFields]: isAssignedStatus,
})}
>
<div>
<p className={classes.workerInfo}>{extractWorkerName(workerVoucher.insuree, isAssignedStatus)}</p>
<Divider />
Expand All @@ -127,6 +139,7 @@ const VoucherDetailsPrintTemplate = forwardRef(({ workerVoucher, logo }, ref) =>
<Divider />
<p className={classes.annotation}>{formatMessage('workerVoucher.template.validOn')}</p>
</div>
<p className={classes.voucherValue}>{`${voucherValue || 0} ${formatMessage('currency')}`}</p>
</div>
</div>

Expand All @@ -138,7 +151,9 @@ const VoucherDetailsPrintTemplate = forwardRef(({ workerVoucher, logo }, ref) =>
alt="Logo of Ministerul Muncii și Protecţiei Sociale al Republicii Moldova"
/>
</div>
<p className={classes.voucherValue}>{`${voucherValue || 0} ${formatMessage('currency')}`}</p>
<div>
<VoucherQRCode voucher={workerVoucher} bgColor="#FFFFFF" size={256} />
</div>
</div>
</div>
);
Expand Down
90 changes: 48 additions & 42 deletions src/components/VoucherDetailsVoucher.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,61 @@ import { Grid, Divider } from '@material-ui/core';

import { PublishedComponent, TextInput } from '@openimis/fe-core';
import WorkerVoucherStatusPicker from '../pickers/WorkerVoucherStatusPicker';
import VoucherQRCode from './VoucherQRCode';

function VoucherDetailsVoucher({
workerVoucher, classes, readOnly, formatMessage,
}) {
return (
<>
<Grid container>
<Grid item xs={3} className={classes.item}>
<TextInput
module="workerVoucher"
label="workerVoucher.code"
value={workerVoucher?.code}
readOnly={readOnly}
/>
<Grid container style={{ marginTop: '12px' }}>
<Grid xs={3}>
<VoucherQRCode voucher={workerVoucher} />
</Grid>
<Grid item xs={3} className={classes.item}>
<WorkerVoucherStatusPicker
nullLabel={formatMessage('workerVoucher.placeholder.any')}
withLabel
value={workerVoucher?.status}
readOnly={readOnly}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module="workerVoucher"
label="workerVoucher.assignedDate"
value={workerVoucher?.assignedDate}
readOnly={readOnly}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module="workerVoucher"
label="workerVoucher.expiryDate"
value={workerVoucher?.expiryDate}
readOnly={readOnly}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module="workerVoucher"
label="workerVoucher.createdDate"
value={workerVoucher?.dateCreated}
readOnly={readOnly}
/>
<Grid container xs={9}>
<Grid item xs={4} className={classes.item}>
<TextInput
module="workerVoucher"
label="workerVoucher.code"
value={workerVoucher?.code}
readOnly={readOnly}
/>
</Grid>
<Grid item xs={4} className={classes.item}>
<WorkerVoucherStatusPicker
nullLabel={formatMessage('workerVoucher.placeholder.any')}
withLabel
value={workerVoucher?.status}
readOnly={readOnly}
/>
</Grid>
<Grid item xs={4} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module="workerVoucher"
label="workerVoucher.assignedDate"
value={workerVoucher?.assignedDate}
readOnly={readOnly}
/>
</Grid>
<Grid item xs={4} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module="workerVoucher"
label="workerVoucher.expiryDate"
value={workerVoucher?.expiryDate}
readOnly={readOnly}
/>
</Grid>
<Grid item xs={4} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module="workerVoucher"
label="workerVoucher.createdDate"
value={workerVoucher?.dateCreated}
readOnly={readOnly}
/>
</Grid>
</Grid>
</Grid>
<Divider style={{ margin: '12px 0' }} />
Expand Down
45 changes: 45 additions & 0 deletions src/components/VoucherQRCode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import QRCode from 'react-qr-code';
import { makeStyles } from '@material-ui/styles';

import { Grid } from '@material-ui/core';

const useStyles = makeStyles(() => ({
qrContainer: {
height: 'auto',
margin: '12px auto',
maxWidth: 128,
width: '100%',
},
qrCode: {
height: 'auto',
maxWidth: '100%',
width: '100%',
},
}));

export default function VoucherQRCode({ voucher, bgColor = '#e4f2ff' }) {
const classes = useStyles();

if (!voucher) {
return null;
}

const voucherUrl = new URL(`${window.location.origin}${process.env.PUBLIC_URL}/voucher/check/${voucher.code}`);

return (
<Grid item xs={12}>
<div className={classes.qrContainer}>
<QRCode
size={128}
className={classes.qrCode}
value={voucherUrl.toString()}
viewBox="0 0 256 256"
level="H"
bgColor={bgColor}
fgColor="#000000"
/>
</div>
</Grid>
);
}
5 changes: 3 additions & 2 deletions src/components/VoucherSearcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
VOUCHER_RIGHT_SEARCH,
} from '../constants';
import VoucherFilter from './VoucherFilter';
import { trimDate } from '../utils/utils';

function VoucherSearcher({ downloadWorkerVoucher, fetchWorkerVouchers, clearWorkerVoucherExport }) {
const history = useHistory();
Expand Down Expand Up @@ -116,8 +117,8 @@ function VoucherSearcher({ downloadWorkerVoucher, fetchWorkerVouchers, clearWork
? `${workerVoucher.insuree?.chfId} ${workerVoucher.insuree?.lastName}`
: formatMessage('workerVoucher.unassigned')),
(workerVoucher) => formatMessage(`workerVoucher.status.${workerVoucher.status}`),
(workerVoucher) => workerVoucher.assignedDate,
(workerVoucher) => workerVoucher.expiryDate,
(workerVoucher) => trimDate(workerVoucher.assignedDate),
(workerVoucher) => trimDate(workerVoucher.expiryDate),
(workerVoucher) => (
<div style={{ textAlign: 'right' }}>
<Tooltip title={formatMessage('workerVoucher.tooltip.details')}>
Expand Down
24 changes: 20 additions & 4 deletions src/components/WorkerSearcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
useTranslations,
downloadExport,
SelectDialog,
journalize,
EXPORT_FILE_FORMATS,
useToast,
} from '@openimis/fe-core';
import {
fetchWorkers, downloadWorkers, clearWorkersExport, deleteWorkerFromEconomicUnit,
Expand All @@ -37,6 +37,7 @@ import {
import WorkerFilter from './WorkerFilter';
import { ACTION_TYPE } from '../reducer';
import { useUploadWorkerContext } from '../context/UploadWorkerContext';
import { getLastMutationLog } from '../utils/utils';

const WORKER_SEARCHER_ACTION_CONTRIBUTION_KEY = 'workerVoucher.WorkerSearcherAction.select';

Expand Down Expand Up @@ -68,6 +69,7 @@ function WorkerSearcher({
|| !rights.includes(INSPECTOR_RIGHT));

const { formatMessage, formatMessageWithValues } = useTranslations(MODULE_NAME, modulesManager);
const { showError, showSuccess } = useToast();

const [failedExport, setFailedExport] = useState(false);
const [queryParams, setQueryParams] = useState([]);
Expand Down Expand Up @@ -241,11 +243,25 @@ function WorkerSearcher({
}
}, [validationSuccess, validationWarning]);

useEffect(() => {
useEffect(async () => {
if (prevSubmittingMutationRef.current && !submittingMutation) {
dispatch(journalize(mutation));
const mutationLog = await getLastMutationLog(dispatch, mutation?.clientMutationId || EMPTY_STRING);

if (mutationLog?.error) {
const parsedMutationError = JSON.parse(mutationLog.error);

showError(
formatMessageWithValues('deleteWorker.error', {
detail: parsedMutationError[0]?.detail || EMPTY_STRING,
}),
);
return;
}

showSuccess(formatMessage('deleteWorker.success'));
fetchWorkers(queryParams);
}
}, [submittingMutation]);
}, [submittingMutation, mutation]);

useEffect(() => {
prevSubmittingMutationRef.current = submittingMutation;
Expand Down
Loading

0 comments on commit 5558552

Please sign in to comment.