Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Button, Modal } from 'semantic-ui-react';

const OverbookedConfirmModal = ({
open,
onClose,
onConfirm,
overbookedDocuments,
}) => {
const isMultiple = overbookedDocuments.length > 1;

return (
<Modal size="small" open={open} onClose={onClose}>
<Modal.Header>Item in high demand!</Modal.Header>

<Modal.Content>
{overbookedDocuments.map((doc, index) => (
<p key={doc.loanRequestId || doc.title || index}>
There is another patron waiting for "<strong>{doc.title}</strong>"{' '}
{doc.loanRequestId && (
<>
(
<a href={`/backoffice/loans/${doc.loanRequestId}`}>
See loan request
</a>
)
</>
)}
.
</p>
))}

<p>
<strong>
Do you still want to extend your {isMultiple ? 'loans' : 'loan'}?
</strong>
</p>
</Modal.Content>

<Modal.Actions>
<Button secondary onClick={onClose}>
Cancel
</Button>
<Button primary onClick={onConfirm}>
Extend
</Button>
</Modal.Actions>
</Modal>
);
};

OverbookedConfirmModal.propTypes = {
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
overbookedDocuments: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string.isRequired,
loanRequestId: PropTypes.string,
})
).isRequired,
};

export default OverbookedConfirmModal;
1 change: 1 addition & 0 deletions src/lib/components/OverbookedConfirmModal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as OverbookedConfirmModal } from './OverbookedConfirmModal';
14 changes: 14 additions & 0 deletions src/lib/components/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { documentApi } from '@api/documents';
import { sessionManager } from '@authentication/services/SessionManager';
import _get from 'lodash/get';

export const prettyPrintBooleanValue = (value) => {
return value ? 'Yes' : 'No';
};

export const screenIsWiderThan = (pixels) => {
return window.matchMedia(`(max-width: ${pixels}px)`).matches ? false : true;
};

export const isPrivilegedUser = () => {
const roles = _get(sessionManager, 'user.roles', []);
return roles.includes('admin') || roles.includes('librarian');
};

export const isDocumentOverbooked = async (documentPid) => {
const response = await documentApi.get(documentPid);
return _get(response, 'data.metadata.circulation.overbooked', false);
};
192 changes: 141 additions & 51 deletions src/lib/modules/Patron/PatronBulkExtendLoans/PatronBulkExtendLoans.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,162 @@
import { loanApi } from '@api/loans';
import { invenioConfig } from '@config/index';
import { OverbookedConfirmModal } from '@components/OverbookedConfirmModal';
import { isDocumentOverbooked, isPrivilegedUser } from '@components/utils';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Button, Icon, List, Modal } from 'semantic-ui-react';
import _get from 'lodash/get';

export default class PatronBulkExtendLoans extends Component {
state = { open: false };
state = {
open: false,
showOverbookedConfirm: false,
overbookedDocuments: [],
isChecking: false,
};

open = async () => {
this.setState({ open: true });

if (!isPrivilegedUser()) return;

this.setState({ isChecking: true });

try {
const { patronPid } = this.props;
// Get all active loans for the patron
const query = loanApi
.query()
.withPatronPid(patronPid)
.withState(invenioConfig.CIRCULATION.loanActiveStates)
.withSize(invenioConfig.APP.PATRON_PROFILE_MAX_RESULTS_SIZE)
.sortByNewest()
.qs();

const response = await loanApi.list(query);
const loans = response.data.hits;

const checks = loans.map(async (loan) => {
const documentPid = _get(loan, 'metadata.document.pid');
const isOverbooked = await isDocumentOverbooked(documentPid);

if (isOverbooked) {
return {
title: loan.metadata.document.title,
loanRequestId: loan.id,
};
}
return null;
});

const results = await Promise.all(checks);
const overbookedDocuments = results.filter(Boolean);

this.setState({
overbookedDocuments,
isChecking: false,
});
} catch (error) {
console.error('Failed to fetch overbooked documents', error);
this.setState({ isChecking: false });
}
};

open = () => this.setState({ open: true });
close = () => this.setState({ open: false });
close = () => {
this.setState({
open: false,
showOverbookedConfirm: false,
overbookedDocuments: [],
});
};

handleSubmitExtend = () => {
const { bulkLoanExtension, patronPid } = this.props;
const { overbookedDocuments } = this.state;

// If the user is privileged and there are overbooked docs
if (isPrivilegedUser() && overbookedDocuments.length > 0) {
this.setState({ showOverbookedConfirm: true });
return;
}
// Otherwise extend directly
bulkLoanExtension(patronPid);
this.close();
};

confirmBulkExtension = () => {
const { bulkLoanExtension, patronPid } = this.props;

bulkLoanExtension(patronPid);
this.close();
};

render() {
const { patronPid, bulkLoanExtension, isLoading, disabled, ...uiProps } =
const { isLoading, disabled, patronPid, bulkLoanExtension, ...uiProps } =
this.props;
const { open } = this.state;
const { open, showOverbookedConfirm, overbookedDocuments, isChecking } =
this.state;

return (
<Modal
open={open}
onClose={this.close}
onOpen={this.open}
trigger={
<Button
labelPosition="left"
fluid
icon
primary
loading={isLoading}
disabled={disabled}
{...uiProps}
>
<Icon name="refresh" />
Extend all loans
</Button>
}
>
<Modal.Header>Confirm extension action</Modal.Header>
<Modal.Content>
<Modal.Description>
The loan duration will be extended for:{' '}
<List bulleted>
<List.Item>
loans that will have to be returned in next{' '}
<b>{invenioConfig.CIRCULATION.loanWillExpireDays} days,</b>
</List.Item>
<List.Item>
loans that do not involve third party libraries (Interlibrary
Loans),
</List.Item>
<List.Item>
loans for <b>literature not in high demand by other users.</b>
</List.Item>
</List>
Do you want to continue?
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={this.close}>Cancel</Button>
<Button onClick={this.handleSubmitExtend} primary>
Extend the loans
</Button>
</Modal.Actions>
</Modal>
<>
<OverbookedConfirmModal
open={showOverbookedConfirm}
onClose={() => this.setState({ showOverbookedConfirm: false })}
onConfirm={this.confirmBulkExtension}
overbookedDocuments={overbookedDocuments}
/>

<Modal
open={open}
onClose={this.close}
onOpen={this.open}
trigger={
<Button
labelPosition="left"
fluid
icon
primary
loading={isLoading}
disabled={disabled}
{...uiProps}
>
<Icon name="refresh" />
Extend all loans
</Button>
}
>
<Modal.Header>Confirm extension action</Modal.Header>
<Modal.Content>
<Modal.Description>
The loan duration will be extended for:{' '}
<List bulleted>
<List.Item>
loans that will have to be returned in next{' '}
<b>{invenioConfig.CIRCULATION.loanWillExpireDays} days,</b>
</List.Item>
<List.Item>
loans that do not involve third party libraries (Interlibrary
Loans),
</List.Item>
<List.Item>
loans for <b>literature not in high demand by other users.</b>
</List.Item>
</List>
Do you want to continue?
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={this.close}>Cancel</Button>
<Button
onClick={this.handleSubmitExtend}
primary
loading={isChecking}
>
Extend the loans
</Button>
</Modal.Actions>
</Modal>
</>
);
}
}
Expand Down
Loading
Loading