diff --git a/CHANGELOG.md b/CHANGELOG.md index 2963e7f..e7f3fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Implement functionality for editing mediated request. Refs UIREQMED-22. * *BREAKING* Migrate to new `mod-circulation-bff` endpoints. Refs UIREQMED-39. * Add circulation-storage.staff-slips.collection.get for get staff-slips on Send item in transit. Refs UIREQMED-55. +* Add Decline Action for Mediated request with status of New - Awaiting confirmation. Refs UIREQMED-42. ## [1.1.0](https://github.com/folio-org/ui-requests-mediated/tree/v1.1.0) (2024-10-30) * Update github actions. Refs UIREQMED-14. diff --git a/package.json b/package.json index 7239fad..e6d0acf 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ ], "subPermissions": [ "ui-requests-mediated.view", - "requests-mediated.mediated-request.item.delete" + "requests-mediated.decline-mediated-request.execute" ], "visible": true }, diff --git a/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/MediatedRequestsDetail.js b/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/MediatedRequestsDetail.js index 8c4ac08..2ccb532 100644 --- a/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/MediatedRequestsDetail.js +++ b/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/MediatedRequestsDetail.js @@ -1,3 +1,6 @@ +import { + useState, +} from 'react'; import PropTypes from 'prop-types'; import { useHistory, @@ -14,6 +17,7 @@ import { import { IfPermission, TitleManager, + useOkapiKy, } from '@folio/stripes/core'; import { Button, @@ -31,6 +35,7 @@ import TitleInformation from '../TitleInformation'; import MediatedRequestInformation from '../MediatedRequestInformation'; import ItemDetail from '../ItemDetail'; import UserDetail from '../UserDetail'; +import DeclineModal from './components/DeclineModal'; import { useMediatedRequestById, useUserById, @@ -52,6 +57,7 @@ import { getPatronGroup, getUserPreferences, getReferredRecordData, + confirmDeclineModal, } from '../../../../utils'; const DETAIL_PANE_WIDTH = '44%'; @@ -64,15 +70,19 @@ const MediatedRequestsDetail = ({ stripes, patronGroups, setRequest, + updateMediatedRequestList, }) => { const history = useHistory(); const location = useLocation(); + const ky = useOkapiKy(); const mediatedRequestIdFromPathname = location.pathname.substring(location.pathname.lastIndexOf('/') + 1); const { formatMessage } = useIntl(); const { mediatedRequest, isFetching, + shouldUpdateMediatedRequestById, + setShouldUpdateMediatedRequestById, } = useMediatedRequestById(mediatedRequestIdFromPathname); const { userData } = useUserById(mediatedRequest?.requesterId, isFetching); const { servicePoints } = useServicePoints(); @@ -80,6 +90,20 @@ const MediatedRequestsDetail = ({ setRequest(mediatedRequest); + const [declineModalOpen, setDeclineModalOpen] = useState(false); + const onOpenDeclineModal = () => setDeclineModalOpen(true); + const declineModalState = { + shouldUpdateMediatedRequestById, + setShouldUpdateMediatedRequestById, + setDeclineModalOpen, + }; + const declineModalProps = { + ky, + url: `requests-mediated/mediated-requests/${mediatedRequestIdFromPathname}/decline`, + updateMediatedRequestList, + }; + const onConfirmDeclineModal = () => confirmDeclineModal(declineModalState, declineModalProps); + const onCloseDeclineModal = () => setDeclineModalOpen(false); const isActionMenuVisible = () => ( get(mediatedRequest, MEDIATED_REQUESTS_RECORD_FIELD_PATH[MEDIATED_REQUESTS_RECORD_FIELD_NAME.STATUS], DEFAULT_VIEW_VALUE) === MEDIATED_REQUEST_STATUS.NEW_AWAITING_CONFIRMATION ? stripes.hasPerm('ui-requests-mediated.requests-mediated.view-confirm.execute') || stripes.hasPerm('ui-requests-mediated.requests-mediated.view-create-edit.execute') || stripes.hasPerm('ui-requests-mediated.requests-mediated.view-decline.execute') @@ -96,6 +120,10 @@ const MediatedRequestsDetail = ({ onToggle(); history.push(`${mediatedRequestsActivitiesUrl}/edit/${mediatedRequestIdFromPathname}`); }; + const handleDecline = () => { + onOpenDeclineModal(); + onToggle(); + }; return ( <> @@ -114,7 +142,7 @@ const MediatedRequestsDetail = ({ + + + ); + + return ( + } + open={open} + size="small" + footer={footer} + onClose={onClose} + > + + + ); +}; + +DeclineModal.propTypes = { + open: PropTypes.bool.isRequired, + title: PropTypes.string.isRequired, + onConfirm: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, +}; + +export default DeclineModal; diff --git a/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/components/DeclineModal/DeclineModal.test.js b/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/components/DeclineModal/DeclineModal.test.js new file mode 100644 index 0000000..113b513 --- /dev/null +++ b/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/components/DeclineModal/DeclineModal.test.js @@ -0,0 +1,76 @@ +import { + render, + screen, + fireEvent, +} from '@folio/jest-config-stripes/testing-library/react'; + +import DeclineModal from './DeclineModal'; + +const testIds = { + confirmButton: 'confirmButton', + backButton: 'backButton', +}; +const messageIds = { + title: 'ui-requests-mediated.declineModal.title', + message: 'ui-requests-mediated.declineModal.message', + confirmButton: 'ui-requests-mediated.declineModal.confirm', + backButton: 'ui-requests-mediated.declineModal.back', +}; +const defaultProps = { + open: true, + title: 'Title', + onConfirm: jest.fn(), + onClose: jest.fn(), +}; + +describe('DeclineModal', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + beforeEach(() => { + render( + + ); + }); + + it('should render title', () => { + expect(screen.getByText(messageIds.title)).toBeVisible(); + }); + + it('should render message', () => { + expect(screen.getByText(messageIds.message)).toBeVisible(); + }); + + describe('Confirm button', () => { + it('should render confirm button', () => { + expect(screen.getByTestId(testIds.confirmButton)).toBeVisible(); + }); + + it('should render confirm button text', () => { + expect(screen.getByText(messageIds.confirmButton)).toBeVisible(); + }); + + it('should call onConfirm', () => { + fireEvent.click(screen.getByTestId(testIds.confirmButton)); + + expect(defaultProps.onConfirm).toHaveBeenCalled(); + }); + }); + + describe('Back button', () => { + it('should render back button', () => { + expect(screen.getByTestId(testIds.backButton)).toBeVisible(); + }); + + it('should render back button text', () => { + expect(screen.getByText(messageIds.backButton)).toBeVisible(); + }); + + it('should call onClose', () => { + fireEvent.click(screen.getByTestId(testIds.backButton)); + + expect(defaultProps.onClose).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/components/DeclineModal/index.js b/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/components/DeclineModal/index.js new file mode 100644 index 0000000..9faba9e --- /dev/null +++ b/src/components/MediatedRequestsActivities/components/MediatedRequestsDetail/components/DeclineModal/index.js @@ -0,0 +1 @@ +export { default } from './DeclineModal'; diff --git a/src/hooks/useMediatedRequestById/useMediatedRequestById.js b/src/hooks/useMediatedRequestById/useMediatedRequestById.js index 16e4e03..8fe42a5 100644 --- a/src/hooks/useMediatedRequestById/useMediatedRequestById.js +++ b/src/hooks/useMediatedRequestById/useMediatedRequestById.js @@ -1,3 +1,7 @@ +import { + useState, +} from 'react'; + import { useQuery } from 'react-query'; import { @@ -6,13 +10,15 @@ import { } from '@folio/stripes/core'; const useMediatedRequestById = (mediatedRequestId) => { + const [shouldUpdateMediatedRequestById, setShouldUpdateMediatedRequestById] = useState(0); + const ky = useOkapiKy(); const [namespace] = useNamespace({ key: 'mediatedRequest' }); const { data, isFetching, } = useQuery( - [namespace, mediatedRequestId], + [namespace, mediatedRequestId, shouldUpdateMediatedRequestById], () => ky.get(`requests-mediated/mediated-requests/${mediatedRequestId}`).json(), { enabled: Boolean(mediatedRequestId) }, ); @@ -20,6 +26,8 @@ const useMediatedRequestById = (mediatedRequestId) => { return { isFetching, mediatedRequest: data, + shouldUpdateMediatedRequestById, + setShouldUpdateMediatedRequestById, }; }; diff --git a/src/routes/MediatedRequestsActivitiesContainer.js b/src/routes/MediatedRequestsActivitiesContainer.js index b983827..91b4f17 100644 --- a/src/routes/MediatedRequestsActivitiesContainer.js +++ b/src/routes/MediatedRequestsActivitiesContainer.js @@ -39,6 +39,21 @@ export const buildQuery = (queryParams, pathComponents, resourceData, logger, pr return getCql(queryParams, pathComponents, resourceData, logger, props); }; +export const updateMediatedRequestList = (source, props) => { + const { + location, + history, + } = props; + const { + resources: { + query, + }, + } = source; + const url = buildUrl(location, query); + + history.push(url); +}; + class MediatedRequestsActivitiesContainer extends React.Component { static manifest = Object.freeze({ query: { @@ -62,6 +77,7 @@ class MediatedRequestsActivitiesContainer extends React.Component { query: buildQuery, }, }, + resourceShouldRefresh: true, throwErrors: false, }, reportRecords: { @@ -161,6 +177,10 @@ class MediatedRequestsActivitiesContainer extends React.Component { } }; + updateMediatedRequestList = () => { + updateMediatedRequestList(this.source, this.props); + }; + render() { if (this.source) { this.source.update(this.props, MEDIATED_REQUESTS_RECORDS_NAME); @@ -176,7 +196,13 @@ class MediatedRequestsActivitiesContainer extends React.Component { mutator={this.props.mutator} settings={this.props.settings} > - {this.props.children} + { + React.Children.map( + this.props.children, child => React.cloneElement(child, { + updateMediatedRequestList: this.updateMediatedRequestList, + }) + ) + } ); } diff --git a/src/routes/MediatedRequestsActivitiesContainer.test.js b/src/routes/MediatedRequestsActivitiesContainer.test.js index 1bc9ee5..eea5c67 100644 --- a/src/routes/MediatedRequestsActivitiesContainer.test.js +++ b/src/routes/MediatedRequestsActivitiesContainer.test.js @@ -6,6 +6,7 @@ import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import MediatedRequestsActivitiesContainer, { buildQuery, + updateMediatedRequestList, } from './MediatedRequestsActivitiesContainer'; import MediatedRequestsActivities from '../components/MediatedRequestsActivities'; @@ -70,6 +71,9 @@ const props = { pathname: '', }, }, + history: { + push: jest.fn(), + }, stripes: { logger, }, @@ -118,3 +122,19 @@ describe('buildQuery', () => { )); }); }); + +describe('updateMediatedRequestList', () => { + const source = { + resources: { + query: ['query=Test'], + }, + }; + + it('should trigger history push', () => { + updateMediatedRequestList(source, props); + + expect(props.history.push).toHaveBeenCalledWith(expect.stringContaining( + 'query%3DTest' + )); + }); +}); diff --git a/src/utils.js b/src/utils.js index 593b537..b148b7f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -378,6 +378,29 @@ export const handleConfirmItemSubmit = async (itemBarcode, confirmItemState, con }); }; +export const confirmDeclineModal = (declineModalState, declineModalProps) => { + const { + shouldUpdateMediatedRequestById, + setShouldUpdateMediatedRequestById, + setDeclineModalOpen, + } = declineModalState; + const { + ky, + url, + updateMediatedRequestList, + } = declineModalProps; + + ky.post(url) + .then(() => { + updateMediatedRequestList(); + setShouldUpdateMediatedRequestById(shouldUpdateMediatedRequestById + 1); + setDeclineModalOpen(false); + }) + .catch(() => { + setDeclineModalOpen(false); + }); +}; + export const getStaffSlipsTemplateByType = (staffSlips = [], slipType = STAFF_SLIPS_TYPE.TRANSIT_MEDIATED_REQUESTS) => { const slipTypeInLowerCase = slipType.toLowerCase(); const slipTemplate = staffSlips.find(slip => slip.name.toLowerCase() === slipTypeInLowerCase); diff --git a/src/utils.test.js b/src/utils.test.js index c1bae09..a2ee397 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -48,6 +48,7 @@ import { getDeliveryAddressForCsvRecords, modifyRecordsToExport, handleConfirmItemSubmit, + confirmDeclineModal, getStaffSlipsTemplateByType, escapeValue, buildTemplate, @@ -1009,6 +1010,59 @@ describe('handleConfirmItemSubmit', () => { }); }); +describe('confirmDeclineModal', () => { + const ky = { + post: jest.fn().mockImplementation(() => Promise.resolve()), + }; + const declineModalState = { + shouldUpdateMediatedRequestById: 0, + setShouldUpdateMediatedRequestById: jest.fn(), + setDeclineModalOpen: jest.fn(), + }; + const declineModalProps = { + ky, + url: 'requests-mediated/mediated-requests/111/decline', + updateMediatedRequestList: jest.fn(), + }; + + it('should trigger "ky.post" with correct props', () => { + confirmDeclineModal(declineModalState, declineModalProps); + + expect(ky.post).toHaveBeenCalledWith(declineModalProps.url); + }); + + it('should trigger "updateMediatedRequestList" on success', () => { + confirmDeclineModal(declineModalState, declineModalProps); + + expect(declineModalProps.updateMediatedRequestList).toHaveBeenCalled(); + }); + + it('should trigger "setShouldUpdateMediatedRequestById" on success', () => { + confirmDeclineModal(declineModalState, declineModalProps); + + expect(declineModalState.setShouldUpdateMediatedRequestById).toHaveBeenCalledWith(1); + }); + + it('should trigger "setDeclineModalOpen" on success', () => { + confirmDeclineModal(declineModalState, declineModalProps); + + expect(declineModalState.setDeclineModalOpen).toHaveBeenCalledWith(false); + }); + + it('should trigger "setDeclineModalOpen" on errors', () => { + const declineModalPropsWithErrors = { + ...declineModalProps, + ky: { + post: jest.fn().mockImplementation(() => Promise.reject()), + }, + }; + + confirmDeclineModal(declineModalState, declineModalPropsWithErrors); + + expect(declineModalState.setDeclineModalOpen).toHaveBeenCalledWith(false); + }); +}); + describe('getStaffSlipsTemplateByType', () => { const slipTypeName = STAFF_SLIPS_TYPE.TRANSIT_MEDIATED_REQUESTS; const slipTypeTemplate =

${slipTypeName}

; diff --git a/translations/ui-requests-mediated/en.json b/translations/ui-requests-mediated/en.json index c18d55a..5e328b3 100644 --- a/translations/ui-requests-mediated/en.json +++ b/translations/ui-requests-mediated/en.json @@ -34,6 +34,11 @@ "confirmItem.errorModal.sendItemInTransit.message": "No \"Send item in transit\" transaction could be found for this item", "confirmItem.errorModal.close": "Close", + "declineModal.title": "Confirm Mediated request decline", + "declineModal.message": "{title} will be declined", + "declineModal.confirm": "Confirm", + "declineModal.back": "Back", + "confirmItemList.columnName.arrivalDate": "Arrival date", "confirmItemList.columnName.inTransitDate": "In transit date", "confirmItemList.columnName.title": "Title",