From 555855262e2fb71a7133a983518befdf1d64c64c Mon Sep 17 00:00:00 2001 From: Oliver Lewandowski <109145288+olewandowski1@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:22:17 +0200 Subject: [PATCH] OM-347: create a voucher public page (#74) * 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 --- .eslintrc | 3 +- package.json | 1 + src/actions.js | 21 ++ src/components/UploadWorkerModal.js | 1 - src/components/VoucherDetailsPanel.js | 2 + src/components/VoucherDetailsPrintTemplate.js | 29 +- src/components/VoucherDetailsVoucher.js | 90 ++++--- src/components/VoucherQRCode.js | 45 ++++ src/components/VoucherSearcher.js | 5 +- src/components/WorkerSearcher.js | 24 +- src/components/WorkerSearcherSelectActions.js | 14 +- src/index.js | 8 + src/pages/PublicVoucherDetailsPage.js | 247 ++++++++++++++++++ src/pages/WorkerDetailsPage.js | 23 +- src/translations/en.json | 12 +- src/utils/utils.js | 7 +- 16 files changed, 453 insertions(+), 79 deletions(-) create mode 100644 src/components/VoucherQRCode.js create mode 100644 src/pages/PublicVoucherDetailsPage.js diff --git a/.eslintrc b/.eslintrc index 1de0561..f86e202 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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" } } diff --git a/package.json b/package.json index 6e4a7f3..039e7b2 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "dist" ], "dependencies": { + "react-qr-code": "^2.0.15", "react-to-print": "^2.15.1" } } diff --git a/src/actions.js b/src/actions.js index 22d7fbc..3825d22 100644 --- a/src/actions.js +++ b/src/actions.js @@ -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 @@ -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 }, + ); +} diff --git a/src/components/UploadWorkerModal.js b/src/components/UploadWorkerModal.js index 6b5603c..1b902ad 100644 --- a/src/components/UploadWorkerModal.js +++ b/src/components/UploadWorkerModal.js @@ -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', diff --git a/src/components/VoucherDetailsPanel.js b/src/components/VoucherDetailsPanel.js index 70ef05a..77306c2 100644 --- a/src/components/VoucherDetailsPanel.js +++ b/src/components/VoucherDetailsPanel.js @@ -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'; @@ -67,6 +68,7 @@ function VoucherDetailsPanel({ variant="contained" color="primary" startIcon={} + disabled={!workerVoucher.billId || isTheVoucherExpired(workerVoucher)} onClick={(e) => { e.preventDefault(); handlePrint(null, () => voucherPrintTemplateRef.current); diff --git a/src/components/VoucherDetailsPrintTemplate.js b/src/components/VoucherDetailsPrintTemplate.js index 94c7169..b8b8059 100644 --- a/src/components/VoucherDetailsPrintTemplate.js +++ b/src/components/VoucherDetailsPrintTemplate.js @@ -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'; @@ -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': { @@ -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}`, @@ -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', }, @@ -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) => { @@ -114,7 +120,13 @@ const VoucherDetailsPrintTemplate = forwardRef(({ workerVoucher, logo }, ref) =>

-
+

{extractWorkerName(workerVoucher.insuree, isAssignedStatus)}

@@ -127,6 +139,7 @@ const VoucherDetailsPrintTemplate = forwardRef(({ workerVoucher, logo }, ref) =>

{formatMessage('workerVoucher.template.validOn')}

+

{`${voucherValue || 0} ${formatMessage('currency')}`}

@@ -138,7 +151,9 @@ const VoucherDetailsPrintTemplate = forwardRef(({ workerVoucher, logo }, ref) => alt="Logo of Ministerul Muncii și Protecţiei Sociale al Republicii Moldova" /> -

{`${voucherValue || 0} ${formatMessage('currency')}`}

+
+ +
); diff --git a/src/components/VoucherDetailsVoucher.js b/src/components/VoucherDetailsVoucher.js index 0a35910..0982249 100644 --- a/src/components/VoucherDetailsVoucher.js +++ b/src/components/VoucherDetailsVoucher.js @@ -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 ( <> - - - + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/src/components/VoucherQRCode.js b/src/components/VoucherQRCode.js new file mode 100644 index 0000000..843ec79 --- /dev/null +++ b/src/components/VoucherQRCode.js @@ -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 ( + +
+ +
+
+ ); +} diff --git a/src/components/VoucherSearcher.js b/src/components/VoucherSearcher.js index 8be2eb1..b3efcf8 100644 --- a/src/components/VoucherSearcher.js +++ b/src/components/VoucherSearcher.js @@ -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(); @@ -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) => (
diff --git a/src/components/WorkerSearcher.js b/src/components/WorkerSearcher.js index b9342fd..0ad3a07 100644 --- a/src/components/WorkerSearcher.js +++ b/src/components/WorkerSearcher.js @@ -17,8 +17,8 @@ import { useTranslations, downloadExport, SelectDialog, - journalize, EXPORT_FILE_FORMATS, + useToast, } from '@openimis/fe-core'; import { fetchWorkers, downloadWorkers, clearWorkersExport, deleteWorkerFromEconomicUnit, @@ -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'; @@ -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([]); @@ -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; diff --git a/src/components/WorkerSearcherSelectActions.js b/src/components/WorkerSearcherSelectActions.js index 87d761d..4c61611 100644 --- a/src/components/WorkerSearcherSelectActions.js +++ b/src/components/WorkerSearcherSelectActions.js @@ -7,7 +7,7 @@ import { import { makeStyles } from '@material-ui/styles'; import DeleteIcon from '@material-ui/icons/Delete'; -import { SelectDialog, journalize, useTranslations } from '@openimis/fe-core'; +import { SelectDialog, useTranslations } from '@openimis/fe-core'; import { deleteWorkersFromEconomicUnit } from '../actions'; import { MODULE_NAME } from '../constants'; @@ -27,11 +27,9 @@ function WorkerSearcherSelectActions({ withSelection, downloadWithIconButton = false, }) { - const prevSubmittingMutationRef = useRef(); const prevEconomicUnitRef = useRef(); const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false); const { economicUnit } = useSelector((state) => state.policyHolder); - const { mutation, submittingMutation } = useSelector((state) => state.workerVoucher); const dispatch = useDispatch(); const classes = useStyles(); const { formatMessage } = useTranslations(MODULE_NAME); @@ -61,16 +59,6 @@ function WorkerSearcherSelectActions({ prevEconomicUnitRef.current = economicUnit; }, [economicUnit, selectedWorkers, clearSelected]); - useEffect(() => { - if (prevSubmittingMutationRef.current && !submittingMutation) { - dispatch(journalize(mutation)); - } - }, [submittingMutation]); - - useEffect(() => { - prevSubmittingMutationRef.current = submittingMutation; - }); - if (!withSelection) { return null; } diff --git a/src/index.js b/src/index.js index c663dc6..4b029e7 100644 --- a/src/index.js +++ b/src/index.js @@ -43,7 +43,9 @@ import WorkerDetailsPage from './pages/WorkerDetailsPage'; import WorkerSearcherSelectActions from './components/WorkerSearcherSelectActions'; import GroupsPage from './pages/GroupsPage'; import GroupDetailsPage from './pages/GroupDetailsPage'; +import PublicVoucherDetailsPage from './pages/PublicVoucherDetailsPage'; +const ROUTE_PUBLIC_WORKER_VOUCHER_PAGE = 'voucher/check'; const ROUTE_WORKER_VOUCHERS_LIST = 'voucher/vouchers'; const ROUTE_WORKER_VOUCHER = 'voucher/vouchers/voucher'; const ROUTE_WORKER_VOUCHER_ACQUIREMENT = 'voucher/acquirement'; @@ -113,6 +115,12 @@ const DEFAULT_CONFIG = { filter: (rights) => [VOUCHER_PRICE_MANAGEMENT_RIGHT].some((right) => rights.includes(right)), }, ], + 'core.UnauthenticatedRouter': [ + { + path: `${ROUTE_PUBLIC_WORKER_VOUCHER_PAGE}/:voucher_uuid?`, + component: PublicVoucherDetailsPage, + }, + ], 'core.Router': [ { path: ROUTE_GROUP_LIST, diff --git a/src/pages/PublicVoucherDetailsPage.js b/src/pages/PublicVoucherDetailsPage.js new file mode 100644 index 0000000..b28fc3b --- /dev/null +++ b/src/pages/PublicVoucherDetailsPage.js @@ -0,0 +1,247 @@ +import React, { useEffect, useState } from 'react'; +import clsx from 'clsx'; +import { useDispatch } from 'react-redux'; +import { + Box, Button, Divider, Grid, Paper, Typography, CircularProgress, +} from '@material-ui/core'; +import ArrowBackIcon from '@material-ui/icons/ArrowBack'; +import CheckIcon from '@material-ui/icons/Check'; +import ErrorIcon from '@material-ui/icons/Error'; +import WarningIcon from '@material-ui/icons/Warning'; +import { makeStyles } from '@material-ui/styles'; +import { + useToast, useHistory, useModulesManager, useTranslations, +} from '@openimis/fe-core'; +import { fetchPublicVoucherDetails } from '../actions'; +import { EMPTY_STRING, MODULE_NAME } from '../constants'; +import { trimDate } from '../utils/utils'; + +const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: '100vh', + padding: theme.spacing(2), + [theme.breakpoints.down('sm')]: { + padding: theme.spacing(1), + }, + }, + paper: { + ...theme.paper.paper, + padding: theme.spacing(4), + textAlign: 'center', + maxWidth: 600, + width: '100%', + [theme.breakpoints.down('sm')]: { + padding: theme.spacing(2), + }, + }, + logo: { + maxWidth: 200, + marginBottom: theme.spacing(2), + [theme.breakpoints.down('xs')]: { + maxWidth: 120, + }, + }, + title: { + marginBottom: theme.spacing(2), + fontWeight: 600, + [theme.breakpoints.down('xs')]: { + fontSize: '1.5rem', + }, + }, + content: { + marginBottom: theme.spacing(3), + marginTop: theme.spacing(3), + [theme.breakpoints.down('xs')]: { + fontSize: '0.9rem', + }, + }, + box: { + display: 'flex', + alignItems: 'start', + justifyContent: 'center', + flexDirection: 'row', + gap: theme.spacing(1), + fontSize: '1.2rem', + padding: theme.spacing(2), + margin: theme.spacing(1), + borderRadius: theme.shape.borderRadius, + wordBreak: 'break-word', + [theme.breakpoints.down('xs')]: { + fontSize: '1rem', + alignItems: 'center', + flexDirection: 'column', + }, + }, + notFoundBox: { + backgroundColor: '#ffebee', + border: '1px solid #f44336', + color: '#c62828', + }, + warningBox: { + backgroundColor: '#fff3e0', + border: '1px solid #ff9800', + color: '#f57c00', + }, + foundBox: { + backgroundColor: '#e0f7fa', + border: '1px solid #009688', + color: '#00796b', + }, + button: theme.dialog.primaryButton, + spinnerContainer: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100vh', + }, +})); + +export default function PublicVoucherDetailsPage({ match, logo }) { + const modulesManager = useModulesManager(); + const dispatch = useDispatch(); + const classes = useStyles(); + const { formatMessage, formatMessageWithValues } = useTranslations(MODULE_NAME); + const { showError } = useToast(); + const voucherUuid = match.params.voucher_uuid; + const [voucherSearcher, setVoucherSearcher] = useState({ + isExisted: false, + isValid: false, + assignedDate: EMPTY_STRING, + employerCode: EMPTY_STRING, + employerName: EMPTY_STRING, + }); + const [isLoading, setIsLoading] = useState(false); + + useEffect(async () => { + const fetchVoucher = async () => { + const response = await dispatch(fetchPublicVoucherDetails(voucherUuid || EMPTY_STRING)); + + const { + assignedDate, employerCode, employerName, isExisted, isValid, + } = response.payload.data.voucherCheck; + + setVoucherSearcher({ + assignedDate, + employerCode, + employerName, + isExisted, + isValid, + }); + }; + + try { + setIsLoading(true); + await fetchVoucher(); + } catch { + showError('[PUBLIC_VOUCHER_DETAILS_PAGE]: Fetching voucher details failed.'); + } finally { + setIsLoading(false); + } + }, [voucherUuid, dispatch, modulesManager]); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (!voucherUuid) { + return ( + + } + message={formatMessage('PublicVoucherDetailsPage.noVoucherUuid')} + className={clsx(classes.box, classes.notFoundBox)} + /> + + ); + } + + const { + isExisted, isValid, assignedDate, employerCode, employerName, + } = voucherSearcher; + + const renderMessage = () => { + if (!isExisted) { + return formatMessage('PublicVoucherDetailsPage.voucherNotFound'); + } + + return formatMessageWithValues( + isValid ? 'PublicVoucherDetailsPage.voucherFound' : 'PublicVoucherDetailsPage.invalidVoucherFound', + { + assignedDate: {trimDate(assignedDate)}, + employer: {`${employerCode} - ${employerName}`}, + }, + ); + }; + + const renderIcon = () => { + if (!isExisted) { + return ; + } + + return isValid ? : ; + }; + + return ( + + + + ); +} + +function InfoBox({ icon, message, className }) { + return ( + + {icon} + {message} + + ); +} + +function RootLayout({ children, logo }) { + const classes = useStyles(); + const history = useHistory(); + const { formatMessage } = useTranslations(MODULE_NAME); + + return ( +
+ + + + + Logo + + {formatMessage('PublicVoucherDetailsPage.title')} + + +
{children}
+
+
+
+
+ ); +} diff --git a/src/pages/WorkerDetailsPage.js b/src/pages/WorkerDetailsPage.js index 4835df9..40e091c 100644 --- a/src/pages/WorkerDetailsPage.js +++ b/src/pages/WorkerDetailsPage.js @@ -4,7 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { makeStyles } from '@material-ui/styles'; import { - Form, Helmet, useHistory, useModulesManager, useTranslations, parseData, journalize, + Form, Helmet, useHistory, useModulesManager, useTranslations, parseData, useToast, } from '@openimis/fe-core'; import { appendWorkerToEconomicUnit, clearWorker, fetchWorker, fetchWorkerVoucherCount, @@ -14,6 +14,7 @@ import { } from '../constants'; import WorkerMasterPanel from '../components/WorkerMasterPanel'; import WorkerMConnectMasterPanel from '../components/WorkerMConnectMasterPanel'; +import { getLastMutationLog } from '../utils/utils'; const useStyles = makeStyles((theme) => ({ page: theme.page, @@ -35,6 +36,7 @@ function WorkerDetailsPage({ match }) { const [reset, setReset] = useState(0); const [workerVoucherCount, setWorkerVoucherCount] = useState(0); const { mutation, submittingMutation } = useSelector((state) => state.workerVoucher); + const { showSuccess, showError } = useToast(); const titleParams = (worker) => ({ chfId: worker?.chfId ?? EMPTY_STRING, @@ -79,20 +81,27 @@ function WorkerDetailsPage({ match }) { code: 'M', }; - dispatch(appendWorkerToEconomicUnit(economicUnit.code, data, 'Append Worker to Economic Unit')).then( - () => history.goBack(), - ); + dispatch(appendWorkerToEconomicUnit(economicUnit.code, data, 'Append Worker to Economic Unit')) + .then(() => history.goBack()); } catch (error) { setReset((prevReset) => prevReset + 1); throw new Error(`[WORKER_DETAILS_PAGE]: Saving worker failed. ${error}`); } }; - useEffect(() => { + useEffect(async () => { if (prevSubmittingMutationRef.current && !submittingMutation) { - dispatch(journalize(mutation)); + const mutationLog = await getLastMutationLog(dispatch, mutation?.clientMutationId || EMPTY_STRING); + + if (mutationLog?.error) { + showError(formatMessageWithValues('saveWorker.error')); + setReset((prevReset) => prevReset + 1); + return; + } + + showSuccess(formatMessage('saveWorker.success')); } - }, [submittingMutation]); + }, [submittingMutation, mutation]); useEffect(() => { prevSubmittingMutationRef.current = submittingMutation; diff --git a/src/translations/en.json b/src/translations/en.json index 35cc0b3..51c14fd 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -76,6 +76,10 @@ "workerVoucher.WorkerSearcher.dialog.message": "You are about to delete the worker. This action cannot be undone. Please confirm if you wish to proceed with this action.", "workerVoucher.WorkerSearcher.dialog.confirm": "Delete", "workerVoucher.WorkerSearcher.dialog.abandon": "Cancel", + "workerVoucher.deleteWorker.success": "The worker(s) has been deleted successfully.", + "workerVoucher.deleteWorker.error": "Something went wrong while deleting the worker(s). {detail}", + "workerVoucher.saveWorker.success": "The worker has been saved successfully.", + "workerVoucher.saveWorker.error": "Something went wrong while saving the worker.", "workerVoucher.priceManagement": "Confirm Voucher Price", "workerVoucher.mobileAppPassword": "Submit Change", "workerVoucher.password.mustMatch": "The new password and confirmation password must match", @@ -195,5 +199,11 @@ "workerVoucher.WorkerDateRangePicker.selectDate.moreInfo": "Define the validity period for the selected workers' vouchers. You can add multiple validity periods, but they must not overlap. First, set the period, and then confirm it using the button below.", "workerVoucher.VoucherAssignmentForm.form.moreInfo": "Define the workers to whom you want to assign the non-personal vouchers. You can assign vouchers to multiple workers at once. First, select the workers, and then define periods in which the vouchers will be valid. Confirm the assignment using the top right corner button.", "workerVoucher.WorkerImportDialog.moreInfo": "By using this feature, you can import your workers immediately. You can choose to import all workers, workers you've worked with, workers from the previous day only or workers from the particular group. Once you've selected the option, click the Import button to proceed.", - "workerVoucher.GroupPicker.label": "Group" + "workerVoucher.GroupPicker.label": "Group", + "workerVoucher.PublicVoucherDetailsPage.title": "Public Voucher Check", + "workerVoucher.PublicVoucherDetailsPage.backButton": "Back", + "workerVoucher.PublicVoucherDetailsPage.noVoucherUuid": " Voucher UUID not provided.", + "workerVoucher.PublicVoucherDetailsPage.voucherNotFound": "Voucher not found.", + "workerVoucher.PublicVoucherDetailsPage.invalidVoucherFound": "Voucher is no longer valid. It was valid on the day {assignedDate} at the {employer} employer.", + "workerVoucher.PublicVoucherDetailsPage.voucherFound": "Voucher found. It is valid on the day {assignedDate} at the {employer} employer." } diff --git a/src/utils/utils.js b/src/utils/utils.js index feba8dc..fde2828 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,7 +1,7 @@ /* eslint-disable import/prefer-default-export */ import { baseApiUrl, parseData } from '@openimis/fe-core'; -import { MPAY_BILL_URL } from '../constants'; +import { MPAY_BILL_URL, WORKER_VOUCHER_STATUS } from '../constants'; import { fetchMutation } from '../actions'; const fetchMPayArgs = async (url) => { @@ -70,3 +70,8 @@ export const getLastMutationLog = async (dispatch, mutationId) => { return parseData(mutation.payload.data.mutationLogs)?.[0]; }; + +export const isTheVoucherExpired = (voucher) => voucher.status === WORKER_VOUCHER_STATUS.EXPIRED +|| new Date(voucher.expiryDate) < new Date(); + +export const trimDate = (date) => date.split('T')[0];