diff --git a/src/actions.js b/src/actions.js index 4f2a278..76340b4 100644 --- a/src/actions.js +++ b/src/actions.js @@ -18,6 +18,16 @@ const WORKER_VOUCHER_PROJECTION = (modulesManager) => [ `policyholder ${modulesManager.getProjection('policyHolder.PolicyHolderPicker.projection')}`, ]; +const VOUCHER_PRICE_PROJECTION = () => [ + 'id', + 'uuid', + 'key', + 'value', + 'dateValidFrom', + 'dateValidTo', + 'isDeleted', +]; + function formatGraphQLDateRanges(dateRanges) { const rangeStrings = dateRanges.map((range) => `{ startDate: "${range.startDate}", endDate: "${range.endDate}" }`); return `[${rangeStrings.join(', ')}]`; @@ -213,3 +223,25 @@ export function fetchMutation(clientMutationId) { { clientMutationId }, ); } + +export function fetchVoucherPrices(params) { + const payload = formatPageQueryWithCount('businessConfig', params, VOUCHER_PRICE_PROJECTION()); + return graphql(payload, ACTION_TYPE.SEARCH_VOUCHER_PRICES); +} + +export function deleteVoucherPrice(voucherPriceUuid, clientMutationLabel) { + const voucherPricesUuids = `ids: ["${voucherPriceUuid}"]`; + const mutation = formatMutation('deleteBusinessConfig', voucherPricesUuids, clientMutationLabel); + const requestedDateTime = new Date(); + + return graphql( + mutation.payload, + [REQUEST(ACTION_TYPE.MUTATION), SUCCESS(ACTION_TYPE.DELETE_VOUCHER_PRICE), ERROR(ACTION_TYPE.MUTATION)], + { + actionType: ACTION_TYPE.DELETE_VOUCHER_PRICE, + clientMutationId: mutation.clientMutationId, + clientMutationLabel, + requestedDateTime, + }, + ); +} diff --git a/src/components/VoucherPriceFilter.js b/src/components/VoucherPriceFilter.js index 723fbb6..13633b6 100644 --- a/src/components/VoucherPriceFilter.js +++ b/src/components/VoucherPriceFilter.js @@ -1,46 +1,73 @@ import React from 'react'; +import _debounce from 'lodash/debounce'; -import { Grid, Typography, Divider } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { makeStyles } from '@material-ui/styles'; -import { PublishedComponent } from '@openimis/fe-core'; +import { PublishedComponent, TextInput } from '@openimis/fe-core'; +import { + DATE_TIME_SUFFIX, DEFAULT_DEBOUNCE_TIME, EMPTY_STRING, STARTS_WITH_LOOKUP, +} from '../constants'; export const useStyles = makeStyles((theme) => ({ item: theme.paper.item, })); -function VoucherPriceFilter({ filters, onChangeFilters, formatMessage }) { +function VoucherPriceFilter({ filters, onChangeFilters }) { const classes = useStyles(); + const debouncedOnChangeFilters = _debounce(onChangeFilters, DEFAULT_DEBOUNCE_TIME); + const filterValue = (filterName) => filters?.[filterName]?.value; + const filterTextFieldValue = (filterName) => filters?.[filterName]?.value ?? EMPTY_STRING; return ( - <> - - - {formatMessage('workerVoucher.priceManagement.tip')} - - + + + debouncedOnChangeFilters([ + { + id: 'value', + value: price, + filter: `value_${STARTS_WITH_LOOKUP}: "${price}"`, + }, + ])} + /> + + + onChangeFilters([ + { + id: 'dateValidFrom_Gte', + value: validFrom, + filter: `dateValidFrom_Gte: "${validFrom}${DATE_TIME_SUFFIX}"`, + }, + ])} + /> - - - onChangeFilters([ - { - id: 'date', - value: date, - filter: `date: "${date}"`, - }, - ])} - /> - + + onChangeFilters([ + { + id: 'dateValidTo_Lte', + value: validTo, + filter: `dateValidTo_Lte: "${validTo}${DATE_TIME_SUFFIX}"`, + }, + ])} + /> - + ); } diff --git a/src/components/VoucherPriceManagementForm.js b/src/components/VoucherPriceManagementForm.js index 2f42940..faf3e77 100644 --- a/src/components/VoucherPriceManagementForm.js +++ b/src/components/VoucherPriceManagementForm.js @@ -11,8 +11,8 @@ import { } from '@openimis/fe-core'; import PriceManagementForm from './PriceManagementForm'; import VoucherPriceSearcher from './VoucherPriceSearcher'; -import { fetchMutation, manageVoucherPrice } from '../actions'; -import { MODULE_NAME, VOUCHER_PRICE_MANAGEMENT_BUSINESS_KEY } from '../constants'; +import { fetchMutation, fetchVoucherPrices, manageVoucherPrice } from '../actions'; +import { DEFAULT_VOUCHER_PRICE_FILTERS, MODULE_NAME, VOUCHER_PRICE_MANAGEMENT_BUSINESS_KEY } from '../constants'; export const useStyles = makeStyles((theme) => ({ paper: { ...theme.paper.paper, margin: '10px 0 0 0' }, @@ -33,25 +33,29 @@ function VoucherPriceManagementForm() { const classes = useStyles(); const { formatMessage, formatMessageWithValues } = useTranslations(MODULE_NAME, modulesManager); const [priceManagement, setPriceManagement] = useState({}); - const [priceManagementLoading, setPriceManagementLoading] = useState(false); const { mutation, submittingMutation } = useSelector((state) => state.workerVoucher); + const { + voucherPrices, + voucherPricesTotalCount, + voucherPricesPageInfo, + fetchedVoucherPrices, + errorVoucherPrices, + } = useSelector((state) => state.workerVoucher); const priceManagementBlocked = (priceManagement) => !priceManagement?.price || !priceManagement?.validFrom - || !priceManagement?.validTo - || priceManagementLoading; + || !priceManagement?.validTo; - const fetchVoucherPrices = async (params) => { + const fetchPrices = async (params) => { try { - // TODO: Fetch vouchers - console.log(params); + const queryParams = [...params, `key: "${VOUCHER_PRICE_MANAGEMENT_BUSINESS_KEY}"`]; + dispatch(fetchVoucherPrices(queryParams)); } catch (error) { throw new Error(`[VOUCHER_PRICE_SEARCHER]: Fetching voucher prices failed. ${error}`); } }; const onPriceManagementChange = async () => { - setPriceManagementLoading(true); try { const { payload } = await dispatch( manageVoucherPrice( @@ -70,26 +74,30 @@ function VoucherPriceManagementForm() { if (currentMutation.error) { const errorDetails = JSON.parse(currentMutation.error); - dispatch(coreAlert( - formatMessage('workerVoucher.menu.priceManagement'), - formatMessage(errorDetails?.detail || 'NOT_FOUND'), - )); + dispatch( + coreAlert( + formatMessage('menu.priceManagement'), + formatMessage(errorDetails?.detail || 'NOT_FOUND'), + ), + ); return; } - dispatch(coreAlert( - formatMessage('workerVoucher.menu.priceManagement'), - formatMessageWithValues('workerVoucher.priceManagement.success', { - price: priceManagement.price, - dateFrom: priceManagement.validFrom, - dateTo: priceManagement.validTo, - }), - )); + dispatch( + coreAlert( + formatMessage('menu.priceManagement'), + formatMessageWithValues('priceManagement.success', { + price: priceManagement.price, + dateFrom: priceManagement.validFrom, + dateTo: priceManagement.validTo, + }), + ), + ); + setPriceManagement({}); + await fetchPrices(DEFAULT_VOUCHER_PRICE_FILTERS); } catch (error) { throw new Error(`[VOUCHER_PRICE_MANAGEMENT]: Price change failed. ${error}`); - } finally { - setPriceManagementLoading(false); } }; @@ -110,12 +118,12 @@ function VoucherPriceManagementForm() { - {formatMessage('workerVoucher.menu.priceManagement')} + {formatMessage('menu.priceManagement')} @@ -125,7 +133,7 @@ function VoucherPriceManagementForm() { onClick={onPriceManagementChange} disabled={priceManagementBlocked(priceManagement)} > - {formatMessage('workerVoucher.priceManagement')} + {formatMessage('priceManagement')} @@ -134,7 +142,7 @@ function VoucherPriceManagementForm() { - {formatMessage('workerVoucher.priceManagement.subtitle')} + {formatMessage('priceManagement.subtitle')} @@ -148,12 +156,12 @@ function VoucherPriceManagementForm() { ); diff --git a/src/components/VoucherPriceSearcher.js b/src/components/VoucherPriceSearcher.js index 38b7527..f3d0e11 100644 --- a/src/components/VoucherPriceSearcher.js +++ b/src/components/VoucherPriceSearcher.js @@ -1,50 +1,109 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { useDispatch } from 'react-redux'; -import { Searcher, useModulesManager, useTranslations } from '@openimis/fe-core'; -import { MODULE_NAME, VOUCHER_PRICE_DEFAULT_PAGE_SIZE, VOUCHER_PRICE_ROWS_PER_PAGE } from '../constants'; +import { IconButton, Tooltip } from '@material-ui/core'; +import DeleteIcon from '@material-ui/icons/Delete'; + +import { + Searcher, useModulesManager, useTranslations, AmountInput, toISODate, SelectDialog, +} from '@openimis/fe-core'; import VoucherPriceFilter from './VoucherPriceFilter'; +import { deleteVoucherPrice } from '../actions'; +import { MODULE_NAME, VOUCHER_PRICE_DEFAULT_PAGE_SIZE, VOUCHER_PRICE_ROWS_PER_PAGE } from '../constants'; function VoucherPriceSearcher({ fetch, items, fetchedItems, errorItems, itemsPageInfo, voucherPricesTotalCount, }) { + const dispatch = useDispatch(); const modulesManager = useModulesManager(); const { formatMessage, formatMessageWithValues } = useTranslations(MODULE_NAME, modulesManager); + const [dialogOpen, setDialogOpen] = useState(false); + const [priceToDelete, setPriceToDelete] = useState(null); + + const onDialogOpen = (voucherPriceId) => { + setDialogOpen((prevState) => !prevState); + setPriceToDelete(voucherPriceId); + }; + + const onDialogClose = () => { + setDialogOpen((prevState) => !prevState); + setPriceToDelete(null); + }; - const headers = () => ['workerVoucher.searcher.price', 'workerVoucher.validFrom', 'workerVoucher.validTo']; + const onDialogConfirm = async () => { + try { + await dispatch(deleteVoucherPrice(priceToDelete, 'Delete Voucher Price')); + } catch (error) { + throw new Error(`[VOUCHER_PRICE_SEARCHER]: Deletion failed. ${error}`); + } finally { + setDialogOpen((prevState) => !prevState); + } + }; + + const isRowDisabled = (_, row) => !!row.isDeleted; + + const headers = () => ['searcher.price', 'validFrom', 'validTo']; const itemFormatters = () => [ - (voucherPrice) => voucherPrice.price, - (voucherPrice) => voucherPrice.validFrom, - (voucherPrice) => voucherPrice.validTo, + (voucherPrice) => ( + + ), + (voucherPrice) => toISODate(voucherPrice.dateValidFrom), + (voucherPrice) => toISODate(voucherPrice.dateValidTo), + (voucherPrice) => ( + + onDialogOpen(voucherPrice.uuid)} disabled={voucherPrice.isDeleted}> + + + + ), ]; const sorts = () => [ - ['price', true], - ['validFrom', true], - ['validTo', true], + ['value', true], + ['date_valid_from', true], + ['date_valid_to', true], ]; const voucherPriceFilter = ({ filters, onChangeFilters }) => ( - + ); return ( - + <> + + onDialogConfirm()} + onClose={() => onDialogClose()} + module="workerVoucher" + confirmTitle="priceManagement.dialog.title" + confirmMessage="priceManagement.dialog.message" + confirmationButton="priceManagement.dialog.confirm" + rejectionButton="priceManagement.dialog.abandon" + /> + ); } diff --git a/src/constants.js b/src/constants.js index a7ca34f..036b152 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,6 +1,6 @@ export const VOUCHER_RIGHT_SEARCH = 204001; export const EMPLOYER_RIGHT_SEARCH = 204001; -export const VOUCHER_PRICE_MANAGEMENT_RIGHT = 204001; +export const VOUCHER_PRICE_MANAGEMENT_RIGHT = 205001; export const MODULE_NAME = 'workerVoucher'; export const REF_ROUTE_WORKER_VOUCHER = 'workerVoucher.route.workerVoucher'; @@ -12,10 +12,17 @@ export const ROWS_PER_PAGE_OPTIONS = [10, 20, 50, 100]; export const VOUCHER_PRICE_ROWS_PER_PAGE = [5, 10, 20, 50]; export const EMPTY_STRING = ''; export const CONTAINS_LOOKUP = 'Icontains'; +export const STARTS_WITH_LOOKUP = 'Istartswith'; export const WORKER_THRESHOLD = 3; export const VOUCHER_QUANTITY_THRESHOLD = 1000; export const USER_ECONOMIC_UNIT_STORAGE_KEY = 'userEconomicUnit'; export const VOUCHER_PRICE_MANAGEMENT_BUSINESS_KEY = 'VOUCHER_PRICE_MANAGEMENT_KEY'; +export const DATE_TIME_SUFFIX = 'T00:00:00'; +export const DEFAULT_VOUCHER_PRICE_FILTERS = [ + 'first: 5', + 'orderBy: ["value"]', + `key: ${VOUCHER_PRICE_MANAGEMENT_BUSINESS_KEY}`, +]; const WORKER_VOUCHER_STATUS = { AWAITING_PAYMENT: 'AWAITING_PAYMENT', diff --git a/src/reducer.js b/src/reducer.js index 6f4ebea..7bc4ca4 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -24,6 +24,7 @@ export const ACTION_TYPE = { GET_WORKER_VOUCHER: 'WORKER_VOUCHER_GET_WORKER_VOUCHER', SEARCH_VOUCHER_PRICES: 'WORKER_VOUCHER_VOUCHER_PRICES', MANAGE_VOUCHER_PRICE: 'WORKER_VOUCHER_MANAGE_VOUCHER_PRICE', + DELETE_VOUCHER_PRICE: 'WORKER_VOUCHER_DELETE_VOUCHER_PRICE', }; const STORE_STATE = { @@ -130,9 +131,9 @@ function reducer( fetchingVoucherPrices: false, fetchedVoucherPrices: true, errorVoucherPrices: formatGraphQLError(action.payload), - voucherPrices: parseData(action.payload.data.voucherPrices), - voucherPricesPageInfo: pageInfo(action.payload.data.voucherPrices), - voucherPricesTotalCount: action.payload.data.voucherPrices?.totalCount ?? 0, + voucherPrices: parseData(action.payload.data.businessConfig), + voucherPricesPageInfo: pageInfo(action.payload.data.businessConfig), + voucherPricesTotalCount: action.payload.data.businessConfig?.totalCount ?? 0, }; case ERROR(ACTION_TYPE.SEARCH_VOUCHER_PRICES): return { @@ -162,6 +163,8 @@ function reducer( return dispatchMutationResp(state, 'assignVouchers', action); case SUCCESS(ACTION_TYPE.MANAGE_VOUCHER_PRICE): return dispatchMutationResp(state, 'createBusinessConfig', action); + case SUCCESS(ACTION_TYPE.DELETE_VOUCHER_PRICE): + return dispatchMutationResp(state, 'deleteBusinessConfig', action); default: return state; } diff --git a/src/translations/en.json b/src/translations/en.json index e76cb24..ff19d05 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -19,6 +19,7 @@ "workerVoucher.expiryDate": "Expiry Date", "workerVoucher.createdDate": "Created Date", "workerVoucher.tooltip.details": "View details", + "workerVoucher.voucherPrice.delete": "Delete", "workerVoucher.placeholder.any": "Any", "workerVoucher.status.AWAITING_PAYMENT": "AWAITING PAYMENT", "workerVoucher.status.UNASSIGNED": "UNASSIGNED", @@ -38,9 +39,13 @@ "workerVoucher.vouchersQuantity": "Quantity of Vouchers", "workerVoucher.pricePerVoucher": "Voucher Price", "workerVoucher.toBePaid": "Total Cost", + "workerVoucher.priceManagement.dialog.title": "Delete Voucher Price Confirmation ", + "workerVoucher.priceManagement.dialog.message": "You are about to delete the voucher price. This action cannot be undone. Please confirm if you wish to proceed with this action.", + "workerVoucher.priceManagement.dialog.confirm": "Confirm", + "workerVoucher.priceManagement.dialog.abandon": "Cancel", "workerVoucher.priceManagement": "Confirm Voucher Price", - "workerVoucher.priceManagement.subtitle": "Please fill out the price and validity range to confirm the voucher's price.", - "workerVoucher.priceManagement.tip": "Enter a date to view the voucher price valid on that day.", + "workerVoucher.priceManagement.subtitle": "Please fill out the price and validity range to confirm the voucher price.", + "workerVoucher.priceManagement.success": "The voucher price has been successfully set at {price}$ for the period from {dateFrom} to {dateTo}.", "workerVoucher.validFrom": "Valid From", "workerVoucher.validTo": "Valid To", "workerVoucher.acquire.vouchers": "Acquire Vouchers", @@ -72,6 +77,5 @@ "workerVoucher.searcher.title": "{count} Voucher Prices", "workerVoucher.searcher.price": "Price", "workerVoucher.filter.date": "Date", - "business_config.validation.date_range_overlap": "Unable to create the voucher price for the specified period. There is an existing voucher price defined within the selected date range.", - "workerVoucher.priceManagement.success": "The voucher price has been successfully set at {price}$ for the period from {dateFrom} to {dateTo}." + "business_config.validation.date_range_overlap": "Unable to create the voucher price for the specified period. There is an existing voucher price defined within the selected date range." }