Skip to content

Commit 4f8fa37

Browse files
zubeydecivelekkpsherva
authored andcommitted
feat: add overbooked confirm modal for librarians
1 parent e0c5350 commit 4f8fa37

File tree

5 files changed

+282
-54
lines changed

5 files changed

+282
-54
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import { Button, Modal } from 'semantic-ui-react';
4+
5+
const OverbookedConfirmModal = ({
6+
open,
7+
onClose,
8+
onConfirm,
9+
overbookedDocuments,
10+
}) => {
11+
const isMultiple = overbookedDocuments.length > 1;
12+
13+
return (
14+
<Modal size="small" open={open} onClose={onClose}>
15+
<Modal.Header>Item in high demand!</Modal.Header>
16+
17+
<Modal.Content>
18+
{overbookedDocuments.map((doc, index) => (
19+
<p key={doc.loanRequestId || doc.title || index}>
20+
There is another patron waiting for "<strong>{doc.title}</strong>"{' '}
21+
{doc.loanRequestId && (
22+
<>
23+
(
24+
<a href={`/backoffice/loans/${doc.loanRequestId}`}>
25+
See loan request
26+
</a>
27+
)
28+
</>
29+
)}
30+
.
31+
</p>
32+
))}
33+
34+
<p>
35+
<strong>
36+
Do you still want to extend your {isMultiple ? 'loans' : 'loan'}?
37+
</strong>
38+
</p>
39+
</Modal.Content>
40+
41+
<Modal.Actions>
42+
<Button secondary onClick={onClose}>
43+
Cancel
44+
</Button>
45+
<Button primary onClick={onConfirm}>
46+
Extend
47+
</Button>
48+
</Modal.Actions>
49+
</Modal>
50+
);
51+
};
52+
53+
OverbookedConfirmModal.propTypes = {
54+
open: PropTypes.bool.isRequired,
55+
onClose: PropTypes.func.isRequired,
56+
onConfirm: PropTypes.func.isRequired,
57+
overbookedDocuments: PropTypes.arrayOf(
58+
PropTypes.shape({
59+
title: PropTypes.string.isRequired,
60+
loanRequestId: PropTypes.string,
61+
})
62+
).isRequired,
63+
};
64+
65+
export default OverbookedConfirmModal;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as OverbookedConfirmModal } from './OverbookedConfirmModal';

src/lib/components/utils.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1+
import { documentApi } from '@api/documents';
2+
import { sessionManager } from '@authentication/services/SessionManager';
3+
import _get from 'lodash/get';
4+
15
export const prettyPrintBooleanValue = (value) => {
26
return value ? 'Yes' : 'No';
37
};
48

59
export const screenIsWiderThan = (pixels) => {
610
return window.matchMedia(`(max-width: ${pixels}px)`).matches ? false : true;
711
};
12+
13+
export const isPrivilegedUser = () => {
14+
const roles = _get(sessionManager, 'user.roles', []);
15+
return roles.includes('admin') || roles.includes('librarian');
16+
};
17+
18+
export const isDocumentOverbooked = async (documentPid) => {
19+
const response = await documentApi.get(documentPid);
20+
return _get(response, 'data.metadata.circulation.overbooked', false);
21+
};

src/lib/modules/Patron/PatronBulkExtendLoans/PatronBulkExtendLoans.js

Lines changed: 141 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,162 @@
1+
import { loanApi } from '@api/loans';
12
import { invenioConfig } from '@config/index';
3+
import { OverbookedConfirmModal } from '@components/OverbookedConfirmModal';
4+
import { isDocumentOverbooked, isPrivilegedUser } from '@components/utils';
25
import PropTypes from 'prop-types';
36
import React, { Component } from 'react';
47
import { Button, Icon, List, Modal } from 'semantic-ui-react';
8+
import _get from 'lodash/get';
59

610
export default class PatronBulkExtendLoans extends Component {
7-
state = { open: false };
11+
state = {
12+
open: false,
13+
showOverbookedConfirm: false,
14+
overbookedDocuments: [],
15+
isChecking: false,
16+
};
17+
18+
open = async () => {
19+
this.setState({ open: true });
20+
21+
if (!isPrivilegedUser()) return;
22+
23+
this.setState({ isChecking: true });
24+
25+
try {
26+
const { patronPid } = this.props;
27+
// Get all active loans for the patron
28+
const query = loanApi
29+
.query()
30+
.withPatronPid(patronPid)
31+
.withState(invenioConfig.CIRCULATION.loanActiveStates)
32+
.withSize(invenioConfig.APP.PATRON_PROFILE_MAX_RESULTS_SIZE)
33+
.sortByNewest()
34+
.qs();
35+
36+
const response = await loanApi.list(query);
37+
const loans = response.data.hits;
38+
39+
const checks = loans.map(async (loan) => {
40+
const documentPid = _get(loan, 'metadata.document.pid');
41+
const isOverbooked = await isDocumentOverbooked(documentPid);
42+
43+
if (isOverbooked) {
44+
return {
45+
title: loan.metadata.document.title,
46+
loanRequestId: loan.id,
47+
};
48+
}
49+
return null;
50+
});
51+
52+
const results = await Promise.all(checks);
53+
const overbookedDocuments = results.filter(Boolean);
54+
55+
this.setState({
56+
overbookedDocuments,
57+
isChecking: false,
58+
});
59+
} catch (error) {
60+
console.error('Failed to fetch overbooked documents', error);
61+
this.setState({ isChecking: false });
62+
}
63+
};
864

9-
open = () => this.setState({ open: true });
10-
close = () => this.setState({ open: false });
65+
close = () => {
66+
this.setState({
67+
open: false,
68+
showOverbookedConfirm: false,
69+
overbookedDocuments: [],
70+
});
71+
};
1172

1273
handleSubmitExtend = () => {
1374
const { bulkLoanExtension, patronPid } = this.props;
75+
const { overbookedDocuments } = this.state;
76+
77+
// If the user is privileged and there are overbooked docs
78+
if (isPrivilegedUser() && overbookedDocuments.length > 0) {
79+
this.setState({ showOverbookedConfirm: true });
80+
return;
81+
}
82+
// Otherwise extend directly
83+
bulkLoanExtension(patronPid);
84+
this.close();
85+
};
86+
87+
confirmBulkExtension = () => {
88+
const { bulkLoanExtension, patronPid } = this.props;
1489

1590
bulkLoanExtension(patronPid);
1691
this.close();
1792
};
1893

1994
render() {
20-
const { patronPid, bulkLoanExtension, isLoading, disabled, ...uiProps } =
95+
const { isLoading, disabled, patronPid, bulkLoanExtension, ...uiProps } =
2196
this.props;
22-
const { open } = this.state;
97+
const { open, showOverbookedConfirm, overbookedDocuments, isChecking } =
98+
this.state;
99+
23100
return (
24-
<Modal
25-
open={open}
26-
onClose={this.close}
27-
onOpen={this.open}
28-
trigger={
29-
<Button
30-
labelPosition="left"
31-
fluid
32-
icon
33-
primary
34-
loading={isLoading}
35-
disabled={disabled}
36-
{...uiProps}
37-
>
38-
<Icon name="refresh" />
39-
Extend all loans
40-
</Button>
41-
}
42-
>
43-
<Modal.Header>Confirm extension action</Modal.Header>
44-
<Modal.Content>
45-
<Modal.Description>
46-
The loan duration will be extended for:{' '}
47-
<List bulleted>
48-
<List.Item>
49-
loans that will have to be returned in next{' '}
50-
<b>{invenioConfig.CIRCULATION.loanWillExpireDays} days,</b>
51-
</List.Item>
52-
<List.Item>
53-
loans that do not involve third party libraries (Interlibrary
54-
Loans),
55-
</List.Item>
56-
<List.Item>
57-
loans for <b>literature not in high demand by other users.</b>
58-
</List.Item>
59-
</List>
60-
Do you want to continue?
61-
</Modal.Description>
62-
</Modal.Content>
63-
<Modal.Actions>
64-
<Button onClick={this.close}>Cancel</Button>
65-
<Button onClick={this.handleSubmitExtend} primary>
66-
Extend the loans
67-
</Button>
68-
</Modal.Actions>
69-
</Modal>
101+
<>
102+
<OverbookedConfirmModal
103+
open={showOverbookedConfirm}
104+
onClose={() => this.setState({ showOverbookedConfirm: false })}
105+
onConfirm={this.confirmBulkExtension}
106+
overbookedDocuments={overbookedDocuments}
107+
/>
108+
109+
<Modal
110+
open={open}
111+
onClose={this.close}
112+
onOpen={this.open}
113+
trigger={
114+
<Button
115+
labelPosition="left"
116+
fluid
117+
icon
118+
primary
119+
loading={isLoading}
120+
disabled={disabled}
121+
{...uiProps}
122+
>
123+
<Icon name="refresh" />
124+
Extend all loans
125+
</Button>
126+
}
127+
>
128+
<Modal.Header>Confirm extension action</Modal.Header>
129+
<Modal.Content>
130+
<Modal.Description>
131+
The loan duration will be extended for:{' '}
132+
<List bulleted>
133+
<List.Item>
134+
loans that will have to be returned in next{' '}
135+
<b>{invenioConfig.CIRCULATION.loanWillExpireDays} days,</b>
136+
</List.Item>
137+
<List.Item>
138+
loans that do not involve third party libraries (Interlibrary
139+
Loans),
140+
</List.Item>
141+
<List.Item>
142+
loans for <b>literature not in high demand by other users.</b>
143+
</List.Item>
144+
</List>
145+
Do you want to continue?
146+
</Modal.Description>
147+
</Modal.Content>
148+
<Modal.Actions>
149+
<Button onClick={this.close}>Cancel</Button>
150+
<Button
151+
onClick={this.handleSubmitExtend}
152+
primary
153+
loading={isChecking}
154+
>
155+
Extend the loans
156+
</Button>
157+
</Modal.Actions>
158+
</Modal>
159+
</>
70160
);
71161
}
72162
}

0 commit comments

Comments
 (0)