From 7da08171d6e34a8682dd9f827e967854d511879c Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 9 Oct 2024 13:50:27 +0200 Subject: [PATCH 1/2] OM-320: add bulk delete of workers --- src/actions.js | 26 +++++- src/components/WorkerSearcher.js | 18 ++++ src/components/WorkerSearcherSelectActions.js | 93 +++++++++++++++++++ src/index.js | 2 + src/reducer.js | 6 +- src/translations/en.json | 9 +- 6 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 src/components/WorkerSearcherSelectActions.js diff --git a/src/actions.js b/src/actions.js index 5ee4295..eab2b0c 100644 --- a/src/actions.js +++ b/src/actions.js @@ -284,10 +284,10 @@ export function clearWorkerVoucherExport() { }; } -export function fetchWorkers(modulesManager, params) { +export function fetchWorkers(modulesManager, params, actionType = ACTION_TYPE.GET_WORKERS) { const queryParams = [...params]; const payload = formatPageQueryWithCount('worker', queryParams, WORKER_PROJECTION(modulesManager)); - return graphql(payload, ACTION_TYPE.GET_WORKERS); + return graphql(payload, actionType); } export function fetchWorker(modulesManager, params) { @@ -497,6 +497,28 @@ export function deleteWorkerFromEconomicUnit(economicUnit, workerToDelete, clien ); } +export function deleteWorkersFromEconomicUnit(economicUnit, workersToDelete, clientMutationLabel) { + const workersUuids = workersToDelete.map((worker) => worker.uuid); + const mutationInput = ` + ${economicUnit.code ? `economicUnitCode: "${economicUnit.code}"` : ''} + ${workersUuids?.length ? `uuids: [${workersUuids.map((uuid) => `"${uuid}"`).join(', ')}]` : ''} + `; + + const mutation = formatMutation('deleteWorker', mutationInput, clientMutationLabel); + const requestedDateTime = new Date(); + + return graphql( + mutation.payload, + [REQUEST(ACTION_TYPE.MUTATION), SUCCESS(ACTION_TYPE.DELETE_WORKERS), ERROR(ACTION_TYPE.MUTATION)], + { + actionType: ACTION_TYPE.DELETE_WORKERS, + clientMutationId: mutation.clientMutationId, + clientMutationLabel, + requestedDateTime, + }, + ); +} + export function validateMConnectWorker(nationalId, economicUnitCode) { return graphqlWithVariables( ` diff --git a/src/components/WorkerSearcher.js b/src/components/WorkerSearcher.js index c2ec57e..8441cd6 100644 --- a/src/components/WorkerSearcher.js +++ b/src/components/WorkerSearcher.js @@ -27,6 +27,7 @@ import { ADMIN_RIGHT, DEFAULT_PAGE_SIZE, EMPTY_OBJECT, + EMPTY_STRING, INSPECTOR_RIGHT, MODULE_NAME, RIGHT_WORKER_DELETE, @@ -34,6 +35,9 @@ import { ROWS_PER_PAGE_OPTIONS, } from '../constants'; import WorkerFilter from './WorkerFilter'; +import { ACTION_TYPE } from '../reducer'; + +const WORKER_SEARCHER_ACTION_CONTRIBUTION_KEY = 'workerVoucher.WorkerSearcherAction.select'; function WorkerSearcher({ downloadWorkers, fetchWorkers: fetchWorkersAction, clearWorkersExport }) { const history = useHistory(); @@ -56,6 +60,9 @@ function WorkerSearcher({ downloadWorkers, fetchWorkers: fetchWorkersAction, cle } = useSelector((state) => state.workerVoucher); const { economicUnit } = useSelector((state) => state.policyHolder); const isAdminOrInspector = rights.includes(INSPECTOR_RIGHT) || rights.includes(ADMIN_RIGHT); + const isAuthorized = rights.includes(RIGHT_WORKER_DELETE) + && (rights.includes(ADMIN_RIGHT) + || !rights.includes(INSPECTOR_RIGHT)); const { formatMessage, formatMessageWithValues } = useTranslations(MODULE_NAME, modulesManager); @@ -95,6 +102,12 @@ function WorkerSearcher({ downloadWorkers, fetchWorkers: fetchWorkersAction, cle [economicUnit], ); + const fetchAllAvailableWorkers = () => fetchWorkersAction( + modulesManager, + [`economicUnitCode:"${economicUnit.code}"`], + ACTION_TYPE.REQUEST, + ); + const headers = () => [ 'workerVoucher.worker.chfId', 'workerVoucher.worker.lastName', @@ -230,6 +243,11 @@ function WorkerSearcher({ downloadWorkers, fetchWorkers: fetchWorkersAction, cle return ( <> ({ + uppercase: { + textTransform: 'uppercase', + }, +})); + +function WorkerSearcherSelectActions({ + selection: selectedWorkers, + refetch: refetchWorkers, + clearSelected, + withSelection, +}) { + const prevSubmittingMutationRef = useRef(); + const prevEconomicUnitRef = useRef(); + const [isBulkDeleteDialogOpen, setBulkDeleteDialogOpen] = 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); + const isWorkerSelected = !!selectedWorkers.length; + + const onBulkDeleteConfirm = async () => { + try { + await dispatch(deleteWorkersFromEconomicUnit(economicUnit, selectedWorkers, 'Bulk Delete Workers')); + refetchWorkers(); + } catch (error) { + // eslint-disable-next-line no-console + console.log('[WORKER_SEARCHER_SELECT_ACTIONS]: Bulk delete failed.', error); + } finally { + setBulkDeleteDialogOpen(false); + } + }; + + const onBulkDeleteClose = () => setBulkDeleteDialogOpen(false); + + useEffect(() => { + if (prevEconomicUnitRef.current !== undefined && prevEconomicUnitRef.current !== economicUnit) { + if (selectedWorkers.length) { + clearSelected(); + } + } + + prevEconomicUnitRef.current = economicUnit; + }, [economicUnit, selectedWorkers, clearSelected]); + + useEffect(() => { + if (prevSubmittingMutationRef.current && !submittingMutation) { + dispatch(journalize(mutation)); + } + }, [submittingMutation]); + + useEffect(() => { + prevSubmittingMutationRef.current = submittingMutation; + }); + + if (!withSelection) { + return null; + } + + return ( + <> + + + {formatMessage('workerVoucher.WorkerSearcherSelectActions.delete')} + + + + + ); +} + +export default WorkerSearcherSelectActions; diff --git a/src/index.js b/src/index.js index 7258acf..fd5d300 100644 --- a/src/index.js +++ b/src/index.js @@ -37,6 +37,7 @@ import MobileAppPasswordManagement from './pages/MobileAppPasswordManagement'; import BillVoucherHeadPanel from './components/BillVoucherHeadPanel'; import WorkersPage from './pages/WorkersPage'; import WorkerDetailsPage from './pages/WorkerDetailsPage'; +import WorkerSearcherSelectActions from './components/WorkerSearcherSelectActions'; const ROUTE_WORKER_VOUCHERS_LIST = 'voucher/vouchers'; const ROUTE_WORKER_VOUCHER = 'voucher/vouchers/voucher'; @@ -148,6 +149,7 @@ const DEFAULT_CONFIG = { }, ], 'workerVoucher.VoucherHeadPanel': [BillVoucherHeadPanel], + 'workerVoucher.WorkerSearcherAction.select': WorkerSearcherSelectActions, }; export const WorkerVoucherModule = (cfg) => ({ ...DEFAULT_CONFIG, ...cfg }); diff --git a/src/reducer.js b/src/reducer.js index 73df484..992fa7b 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -16,6 +16,7 @@ import { export const ACTION_TYPE = { MUTATION: 'WORKER_VOUCHER_MUTATION', + REQUEST: 'WORKER_VOUCHER_REQUEST', ACQUIRE_GENERIC_VOUCHER: 'WORKER_VOUCHER_ACQUIRE_GENERIC_VOUCHER', ACQUIRE_SPECIFIC_VOUCHER: 'WORKER_VOUCHER_ACQUIRE_SPECIFIC_VOUCHER', ASSIGN_VOUCHERS: 'WORKER_VOUCHER_ASSIGN_VOUCHERS', @@ -30,7 +31,8 @@ export const ACTION_TYPE = { GET_WORKER: 'WORKER_VOUCHER_GET_WORKER', WORKERS_EXPORT: 'WORKER_VOUCHER_WORKERS_EXPORT', VOUCHER_COUNT: 'WORKER_VOUCHER_VOUCHER_COUNT', - DELETE_WORKER: 'WORKER_VOUCHER_DELETE_WORKER' + DELETE_WORKER: 'WORKER_VOUCHER_DELETE_WORKER', + DELETE_WORKERS: 'WORKER_VOUCHER_DELETE_WORKERS', }; const STORE_STATE = { @@ -314,6 +316,8 @@ function reducer(state = STORE_STATE, action) { return dispatchMutationResp(state, 'createWorker', action); case SUCCESS(ACTION_TYPE.DELETE_WORKER): return dispatchMutationResp(state, 'deleteWorker', action); + case SUCCESS(ACTION_TYPE.DELETE_WORKERS): + return dispatchMutationResp(state, 'deleteWorker', action); case SUCCESS(ACTION_TYPE.MANAGE_VOUCHER_PRICE): return dispatchMutationResp(state, 'createBusinessConfig', action); case SUCCESS(ACTION_TYPE.DELETE_VOUCHER_PRICE): diff --git a/src/translations/en.json b/src/translations/en.json index 8272149..c0c8da2 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -33,6 +33,7 @@ "workerVoucher.createdDate": "Created Date", "workerVoucher.tooltip.details": "View details", "workerVoucher.tooltip.delete": "Delete Worker", + "workerVoucher.tooltip.bulkDelete": "Delete Worker(s)", "workerVoucher.voucherPrice.delete": "Delete", "workerVoucher.placeholder.any": "Any", "workerVoucher.status.AWAITING_PAYMENT": "AWAITING PAYMENT", @@ -138,5 +139,11 @@ "workerVoucher.WorkerMConnectAddForm.search": "Search", "workerVoucher.WorkerMConnectAddForm.notFound": "Worker not found. Ensure that the National ID is correct and try again.", "workerVoucher.WorkerMConnectAddForm.tip": "As an Employer, you can add a worker to the system by searching for the worker's National ID. Once the worker is found, you can save the form and add the worker to your list of workers.", - "workerVoucher.WorkerMConnectAddForm.detail": "Type the worker's National ID and get the worker's details. Ensure that the National ID is correct." + "workerVoucher.WorkerMConnectAddForm.detail": "Type the worker's National ID and get the worker's details. Ensure that the National ID is correct.", + "workerVoucher.WorkerSearcher.selection": "Selected {count} workers", + "workerVoucher.WorkerSearcherSelectActions.delete": "Delete Selected Workers", + "workerVoucher.WorkerSearcherSelectActions.dialog.title": "Are you sure you want to proceed?", + "workerVoucher.WorkerSearcherSelectActions.dialog.message": "You are about to delete the selected workers ({count}). This action cannot be undone. Please confirm if you wish to proceed with this action.", + "workerVoucher.WorkerSearcherSelectActions.dialog.confirm": "Delete", + "workerVoucher.WorkerSearcherSelectActions.dialog.abandon": "Cancel" } From c55ba6002a9f084fb7c7a8c0a29ad4397c01c579 Mon Sep 17 00:00:00 2001 From: olewandowski1 Date: Wed, 9 Oct 2024 14:01:49 +0200 Subject: [PATCH 2/2] OM-320: fix sonar issues --- src/actions.js | 3 ++- src/components/WorkerSearcherSelectActions.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/actions.js b/src/actions.js index eab2b0c..356a378 100644 --- a/src/actions.js +++ b/src/actions.js @@ -499,9 +499,10 @@ export function deleteWorkerFromEconomicUnit(economicUnit, workerToDelete, clien export function deleteWorkersFromEconomicUnit(economicUnit, workersToDelete, clientMutationLabel) { const workersUuids = workersToDelete.map((worker) => worker.uuid); + const workerUuidsString = workersUuids.map((uuid) => `"${uuid}"`).join(', '); const mutationInput = ` ${economicUnit.code ? `economicUnitCode: "${economicUnit.code}"` : ''} - ${workersUuids?.length ? `uuids: [${workersUuids.map((uuid) => `"${uuid}"`).join(', ')}]` : ''} + ${workersUuids.length ? `uuids: [${workerUuidsString}]` : ''} `; const mutation = formatMutation('deleteWorker', mutationInput, clientMutationLabel); diff --git a/src/components/WorkerSearcherSelectActions.js b/src/components/WorkerSearcherSelectActions.js index 6be3e33..1ebdb06 100644 --- a/src/components/WorkerSearcherSelectActions.js +++ b/src/components/WorkerSearcherSelectActions.js @@ -22,7 +22,7 @@ function WorkerSearcherSelectActions({ }) { const prevSubmittingMutationRef = useRef(); const prevEconomicUnitRef = useRef(); - const [isBulkDeleteDialogOpen, setBulkDeleteDialogOpen] = useState(false); + const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false); const { economicUnit } = useSelector((state) => state.policyHolder); const { mutation, submittingMutation } = useSelector((state) => state.workerVoucher); const dispatch = useDispatch(); @@ -38,11 +38,11 @@ function WorkerSearcherSelectActions({ // eslint-disable-next-line no-console console.log('[WORKER_SEARCHER_SELECT_ACTIONS]: Bulk delete failed.', error); } finally { - setBulkDeleteDialogOpen(false); + setIsBulkDeleteDialogOpen(false); } }; - const onBulkDeleteClose = () => setBulkDeleteDialogOpen(false); + const onBulkDeleteClose = () => setIsBulkDeleteDialogOpen(false); useEffect(() => { if (prevEconomicUnitRef.current !== undefined && prevEconomicUnitRef.current !== economicUnit) { @@ -71,7 +71,7 @@ function WorkerSearcherSelectActions({ return ( <> - + {formatMessage('workerVoucher.WorkerSearcherSelectActions.delete')}