@inveniosoftware/react-invenio-app-ils 2.0.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -739,7 +739,7 @@ const RECORDS_CONFIG = {
739
739
  }
740
740
  },
741
741
  ITEMS: {
742
- identifiersToDisplayInFrontside: [],
742
+ identifiersToDisplay: [],
743
743
  circulationRestrictions: [{
744
744
  value: 'NO_RESTRICTION',
745
745
  text: 'No restriction (4 weeks)'
@@ -4287,6 +4287,26 @@ const vocabularyApi = {
4287
4287
  serializer: serializer$1
4288
4288
  };
4289
4289
 
4290
+ /**
4291
+ * Resolves identifier scheme keys (e.g. "CALL_NUMBER") to their
4292
+ * human-readable titles from the vocabulary API.
4293
+ *
4294
+ * @param {Array<string>} identifiers
4295
+ * @param {string} identifierScheme
4296
+ * @returns {Promise<Array<{key: string, text: string}>>}
4297
+ */
4298
+ const fetchIdentifierTitles = async (identifiers, identifierScheme) => {
4299
+ const query = vocabularyApi.query().withType(identifierScheme);
4300
+ const response = await vocabularyApi.list(query.qs());
4301
+ return identifiers.map(scheme => {
4302
+ const vocabEntry = response.data.hits.find(entry => entry.metadata.key === scheme);
4303
+ return {
4304
+ key: scheme,
4305
+ text: vocabEntry ? vocabEntry.metadata.text : scheme
4306
+ };
4307
+ });
4308
+ };
4309
+
4290
4310
  class ScrollingMenuItem$1 extends React.Component {
4291
4311
  constructor(props) {
4292
4312
  super(props);
@@ -4359,6 +4379,14 @@ const prettyPrintBooleanValue = value => {
4359
4379
  const screenIsWiderThan = pixels => {
4360
4380
  return window.matchMedia(`(max-width: ${pixels}px)`).matches ? false : true;
4361
4381
  };
4382
+ const isPrivilegedUser = () => {
4383
+ const roles = _get__default["default"](sessionManager, 'user.roles', []);
4384
+ return roles.includes('admin') || roles.includes('librarian');
4385
+ };
4386
+ const isDocumentOverbooked = async documentPid => {
4387
+ const response = await documentApi.get(documentPid);
4388
+ return _get__default["default"](response, 'data.metadata.circulation.overbooked', false);
4389
+ };
4362
4390
 
4363
4391
  class MetadataTable extends React.Component {
4364
4392
  renderRows() {
@@ -6224,32 +6252,36 @@ class DocumentItemBody extends React.Component {
6224
6252
  items,
6225
6253
  shelfLink,
6226
6254
  documentDetails,
6227
- identifiersToDisplayInFrontside
6228
- } = this.props;
6229
- return items.map(item => /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Row, {
6230
- key: item.pid
6231
- }, /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6232
- "data-label": "Barcode",
6233
- className: "document-item-table-itemCell"
6234
- }, item.barcode), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6235
- "data-label": "Shelf",
6236
- className: "document-item-table-itemCell"
6237
- }, shelfLink !== null ? shelfLink(item, documentDetails) : _get__default["default"](item, 'shelf')), identifiersToDisplayInFrontside.map(identifier => {
6238
- var _item$identifiers, _item$identifiers$fin;
6239
- return /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6240
- key: identifier,
6241
- "data-label": identifier.text,
6255
+ identifiersToDisplay
6256
+ } = this.props;
6257
+ return items.map(item => {
6258
+ const itemIsAccessible = !_get__default["default"](item, 'internal_location.restricted');
6259
+ const shelfValue = shelfLink ? shelfLink(item, documentDetails) : _get__default["default"](item, 'shelf');
6260
+ return /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Row, {
6261
+ key: item.pid
6262
+ }, /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6263
+ "data-label": "Barcode",
6264
+ className: "document-item-table-itemCell"
6265
+ }, item.barcode), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6266
+ "data-label": "Shelf",
6242
6267
  className: "document-item-table-itemCell"
6243
- }, (_item$identifiers = item.identifiers) === null || _item$identifiers === void 0 ? void 0 : (_item$identifiers$fin = _item$identifiers.find(entry => {
6244
- return entry.scheme === identifier.key;
6245
- })) === null || _item$identifiers$fin === void 0 ? void 0 : _item$identifiers$fin.value);
6246
- }), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6247
- "data-label": "Status"
6248
- }, this.statusLabel(item)), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6249
- "data-label": "Medium"
6250
- }, getDisplayVal('ITEMS.mediums', item.medium)), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6251
- "data-label": "Restrictions"
6252
- }, getDisplayVal('ITEMS.circulationRestrictions', item.circulation_restriction))));
6268
+ }, itemIsAccessible ? shelfValue : /*#__PURE__*/React__default["default"].createElement("p", null, "Available on Request")), identifiersToDisplay.map(identifier => {
6269
+ var _item$identifiers, _item$identifiers$fin;
6270
+ return /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6271
+ key: identifier,
6272
+ "data-label": identifier.text,
6273
+ className: "document-item-table-itemCell"
6274
+ }, itemIsAccessible && ((_item$identifiers = item.identifiers) === null || _item$identifiers === void 0 ? void 0 : (_item$identifiers$fin = _item$identifiers.find(entry => {
6275
+ return entry.scheme === identifier.key;
6276
+ })) === null || _item$identifiers$fin === void 0 ? void 0 : _item$identifiers$fin.value));
6277
+ }), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6278
+ "data-label": "Status"
6279
+ }, this.statusLabel(item)), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6280
+ "data-label": "Medium"
6281
+ }, getDisplayVal('ITEMS.mediums', item.medium)), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Cell, {
6282
+ "data-label": "Restrictions"
6283
+ }, getDisplayVal('ITEMS.circulationRestrictions', item.circulation_restriction)));
6284
+ });
6253
6285
  }
6254
6286
  }
6255
6287
  DocumentItemBody.defaultProps = {
@@ -27050,7 +27082,11 @@ class AvailableItems$1 extends React.Component {
27050
27082
  query: '',
27051
27083
  filteredData: null,
27052
27084
  modalOpen: false,
27053
- checkoutItem: null
27085
+ checkoutItem: null,
27086
+ identifiersToDisplay: invenioConfig.ITEMS.identifiersToDisplay.map(s => ({
27087
+ key: s,
27088
+ text: s
27089
+ }))
27054
27090
  };
27055
27091
  }
27056
27092
  componentDidMount() {
@@ -27059,6 +27095,25 @@ class AvailableItems$1 extends React.Component {
27059
27095
  loan
27060
27096
  } = this.props;
27061
27097
  fetchAvailableItems(loan.metadata.document_pid);
27098
+ this.loadIdentifierTitles();
27099
+ }
27100
+ componentWillUnmount() {
27101
+ if (this.cancellableFetchIdentifierTitles) {
27102
+ this.cancellableFetchIdentifierTitles.cancel();
27103
+ }
27104
+ }
27105
+ async loadIdentifierTitles() {
27106
+ this.cancellableFetchIdentifierTitles = withCancel(fetchIdentifierTitles(invenioConfig.ITEMS.identifiersToDisplay, invenioConfig.VOCABULARIES.item.identifier.scheme));
27107
+ try {
27108
+ let result = await this.cancellableFetchIdentifierTitles.promise;
27109
+ this.setState({
27110
+ identifiersToDisplay: result
27111
+ });
27112
+ } catch (error) {
27113
+ if (error !== 'UNMOUNTED') {
27114
+ console.error('Error fetching identifier titles for items.', error);
27115
+ }
27116
+ }
27062
27117
  }
27063
27118
  assignItemButton(item) {
27064
27119
  const {
@@ -27129,18 +27184,31 @@ class AvailableItems$1 extends React.Component {
27129
27184
  data
27130
27185
  } = this.props;
27131
27186
  const {
27132
- filteredData
27187
+ filteredData,
27188
+ identifiersToDisplay
27133
27189
  } = this.state;
27190
+ const identifierColumns = identifiersToDisplay.map(identifier => ({
27191
+ title: identifier.text,
27192
+ field: `metadata.identifiers`,
27193
+ formatter: _ref2 => {
27194
+ var _row$metadata$identif;
27195
+ let {
27196
+ row
27197
+ } = _ref2;
27198
+ const entry = (_row$metadata$identif = row.metadata.identifiers) === null || _row$metadata$identif === void 0 ? void 0 : _row$metadata$identif.find(id => id.scheme === identifier.key);
27199
+ return entry === null || entry === void 0 ? void 0 : entry.value;
27200
+ }
27201
+ }));
27134
27202
  const columns = [{
27135
27203
  title: 'PID',
27136
27204
  field: 'metadata.pid'
27137
27205
  }, {
27138
27206
  title: 'Barcode',
27139
27207
  field: 'metadata.barcode',
27140
- formatter: _ref2 => {
27208
+ formatter: _ref3 => {
27141
27209
  let {
27142
27210
  row
27143
- } = _ref2;
27211
+ } = _ref3;
27144
27212
  return /*#__PURE__*/React__default["default"].createElement(reactRouterDom.Link, {
27145
27213
  to: BackOfficeRoutes.itemDetailsFor(row.metadata.pid)
27146
27214
  }, row.metadata.barcode);
@@ -27157,7 +27225,7 @@ class AvailableItems$1 extends React.Component {
27157
27225
  }, {
27158
27226
  title: 'Shelf',
27159
27227
  field: 'metadata.shelf'
27160
- }, {
27228
+ }, ...identifierColumns, {
27161
27229
  title: 'Actions',
27162
27230
  field: '',
27163
27231
  formatter: this.rowActionButton
@@ -28146,15 +28214,20 @@ const LoanDetails = reactRedux.connect(mapStateToProps$K, mapDispatchToProps$z)(
28146
28214
  class _SearchDateRange extends React.Component {
28147
28215
  constructor() {
28148
28216
  super(...arguments);
28149
- this.onDateChange = newFilter => {
28217
+ this.onDateChange = (field, value) => {
28150
28218
  const {
28151
28219
  currentQueryState,
28152
28220
  updateQueryState
28153
28221
  } = this.props;
28154
- const filters = currentQueryState.filters;
28155
- filters.push(newFilter);
28222
+ const updatedFilters = currentQueryState.filters.filter(_ref => {
28223
+ let [name] = _ref;
28224
+ return name !== field;
28225
+ });
28226
+ if (value !== '') {
28227
+ updatedFilters.push([field, value]);
28228
+ }
28156
28229
  return updateQueryState({
28157
- filters: filters
28230
+ filters: updatedFilters
28158
28231
  });
28159
28232
  };
28160
28233
  }
@@ -28166,8 +28239,8 @@ class _SearchDateRange extends React.Component {
28166
28239
  } = this.props;
28167
28240
  let fromDate = '';
28168
28241
  let toDate = '';
28169
- filters.forEach(_ref => {
28170
- let [name, value] = _ref;
28242
+ filters.forEach(_ref2 => {
28243
+ let [name, value] = _ref2;
28171
28244
  if (name === 'loans_from_date') fromDate = value;
28172
28245
  if (name === 'loans_to_date') toDate = value;
28173
28246
  });
@@ -28179,12 +28252,12 @@ class _SearchDateRange extends React.Component {
28179
28252
  maxDate: toDate,
28180
28253
  defaultValue: fromDate,
28181
28254
  placeholder: "From",
28182
- handleDateChange: value => this.onDateChange(['loans_from_date', value])
28255
+ handleDateChange: value => this.onDateChange('loans_from_date', value)
28183
28256
  })), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Card.Content, null, /*#__PURE__*/React__default["default"].createElement(DatePicker$1, {
28184
28257
  minDate: fromDate,
28185
28258
  defaultValue: toDate,
28186
28259
  placeholder: "To",
28187
- handleDateChange: value => this.onDateChange(['loans_to_date', value])
28260
+ handleDateChange: value => this.onDateChange('loans_to_date', value)
28188
28261
  })));
28189
28262
  }
28190
28263
  }
@@ -28295,6 +28368,11 @@ const schema$3 = () => {
28295
28368
  physical_location: {
28296
28369
  type: 'string',
28297
28370
  title: 'Physical location'
28371
+ },
28372
+ restricted: {
28373
+ type: 'boolean',
28374
+ title: 'Restricted access (not accessible to patrons)',
28375
+ default: false
28298
28376
  }
28299
28377
  }
28300
28378
  };
@@ -28315,6 +28393,8 @@ const uiSchema$3 = title => {
28315
28393
  }, {
28316
28394
  physical_location: 8,
28317
28395
  notes: 8
28396
+ }, {
28397
+ restricted: 8
28318
28398
  }],
28319
28399
  'custom:root': {
28320
28400
  'custom:formTitle': title
@@ -29384,6 +29464,16 @@ class InternalLocationList$1 extends React.Component {
29384
29464
  }, {
29385
29465
  title: 'Location e-mail',
29386
29466
  field: 'metadata.location.email'
29467
+ }, {
29468
+ title: 'Restricted access',
29469
+ formatter: _ref2 => {
29470
+ let {
29471
+ row
29472
+ } = _ref2;
29473
+ return row.metadata.restricted ? /*#__PURE__*/React__default["default"].createElement("i", {
29474
+ className: "check icon"
29475
+ }) : null;
29476
+ }
29387
29477
  }, {
29388
29478
  title: 'Actions',
29389
29479
  field: '',
@@ -29547,19 +29637,108 @@ const mapDispatchToProps$v = dispatch => ({
29547
29637
  });
29548
29638
  const LocationList = reactRedux.connect(mapStateToProps$G, mapDispatchToProps$v)(LocationList$1);
29549
29639
 
29640
+ const OverbookedConfirmModal = _ref => {
29641
+ let {
29642
+ open,
29643
+ onClose,
29644
+ onConfirm,
29645
+ overbookedDocuments
29646
+ } = _ref;
29647
+ const isMultiple = overbookedDocuments.length > 1;
29648
+ return /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Modal, {
29649
+ size: "small",
29650
+ open: open,
29651
+ onClose: onClose
29652
+ }, /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Modal.Header, null, "Item in high demand!"), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Modal.Content, null, overbookedDocuments.map((doc, index) => /*#__PURE__*/React__default["default"].createElement("p", {
29653
+ key: doc.loanRequestId || doc.title || index
29654
+ }, "There is another patron waiting for \"", /*#__PURE__*/React__default["default"].createElement("strong", null, doc.title), "\"", ' ', doc.loanRequestId && /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, "(", /*#__PURE__*/React__default["default"].createElement("a", {
29655
+ href: `/backoffice/loans/${doc.loanRequestId}`
29656
+ }, "See loan request"), ")"), ".")), /*#__PURE__*/React__default["default"].createElement("p", null, /*#__PURE__*/React__default["default"].createElement("strong", null, "Do you still want to extend your ", isMultiple ? 'loans' : 'loan', "?"))), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Modal.Actions, null, /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Button, {
29657
+ secondary: true,
29658
+ onClick: onClose
29659
+ }, "Cancel"), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Button, {
29660
+ primary: true,
29661
+ onClick: onConfirm
29662
+ }, "Extend")));
29663
+ };
29664
+
29550
29665
  class PatronBulkExtendLoans$1 extends React.Component {
29551
29666
  constructor() {
29552
29667
  super(...arguments);
29553
29668
  this.state = {
29554
- open: false
29669
+ open: false,
29670
+ showOverbookedConfirm: false,
29671
+ overbookedDocuments: [],
29672
+ isChecking: false
29673
+ };
29674
+ this.open = async () => {
29675
+ this.setState({
29676
+ open: true
29677
+ });
29678
+ if (!isPrivilegedUser()) return;
29679
+ this.setState({
29680
+ isChecking: true
29681
+ });
29682
+ try {
29683
+ const {
29684
+ patronPid
29685
+ } = this.props;
29686
+ // Get all active loans for the patron
29687
+ const query = loanApi.query().withPatronPid(patronPid).withState(invenioConfig.CIRCULATION.loanActiveStates).withSize(invenioConfig.APP.PATRON_PROFILE_MAX_RESULTS_SIZE).sortByNewest().qs();
29688
+ const response = await loanApi.list(query);
29689
+ const loans = response.data.hits;
29690
+ const checks = loans.map(async loan => {
29691
+ const documentPid = _get__default["default"](loan, 'metadata.document.pid');
29692
+ const isOverbooked = await isDocumentOverbooked(documentPid);
29693
+ if (isOverbooked) {
29694
+ return {
29695
+ title: loan.metadata.document.title,
29696
+ loanRequestId: loan.id
29697
+ };
29698
+ }
29699
+ return null;
29700
+ });
29701
+ const results = await Promise.all(checks);
29702
+ const overbookedDocuments = results.filter(Boolean);
29703
+ this.setState({
29704
+ overbookedDocuments,
29705
+ isChecking: false
29706
+ });
29707
+ } catch (error) {
29708
+ console.error('Failed to fetch overbooked documents', error);
29709
+ this.setState({
29710
+ isChecking: false
29711
+ });
29712
+ }
29713
+ };
29714
+ this.close = () => {
29715
+ this.setState({
29716
+ open: false,
29717
+ showOverbookedConfirm: false,
29718
+ overbookedDocuments: []
29719
+ });
29555
29720
  };
29556
- this.open = () => this.setState({
29557
- open: true
29558
- });
29559
- this.close = () => this.setState({
29560
- open: false
29561
- });
29562
29721
  this.handleSubmitExtend = () => {
29722
+ const {
29723
+ bulkLoanExtension,
29724
+ patronPid
29725
+ } = this.props;
29726
+ const {
29727
+ overbookedDocuments
29728
+ } = this.state;
29729
+
29730
+ // If the user is privileged and there are overbooked docs
29731
+ if (isPrivilegedUser() && overbookedDocuments.length > 0) {
29732
+ this.setState({
29733
+ showOverbookedConfirm: true
29734
+ });
29735
+ return;
29736
+ }
29737
+ // Otherwise extend directly
29738
+ bulkLoanExtension(patronPid);
29739
+ this.close();
29740
+ };
29741
+ this.confirmBulkExtension = () => {
29563
29742
  const {
29564
29743
  bulkLoanExtension,
29565
29744
  patronPid
@@ -29570,16 +29749,26 @@ class PatronBulkExtendLoans$1 extends React.Component {
29570
29749
  }
29571
29750
  render() {
29572
29751
  const {
29573
- patronPid,
29574
- bulkLoanExtension,
29575
29752
  isLoading,
29576
29753
  disabled,
29754
+ patronPid,
29755
+ bulkLoanExtension,
29577
29756
  ...uiProps
29578
29757
  } = this.props;
29579
29758
  const {
29580
- open
29759
+ open,
29760
+ showOverbookedConfirm,
29761
+ overbookedDocuments,
29762
+ isChecking
29581
29763
  } = this.state;
29582
- return /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Modal, {
29764
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(OverbookedConfirmModal, {
29765
+ open: showOverbookedConfirm,
29766
+ onClose: () => this.setState({
29767
+ showOverbookedConfirm: false
29768
+ }),
29769
+ onConfirm: this.confirmBulkExtension,
29770
+ overbookedDocuments: overbookedDocuments
29771
+ }), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Modal, {
29583
29772
  open: open,
29584
29773
  onClose: this.close,
29585
29774
  onOpen: this.open,
@@ -29599,8 +29788,9 @@ class PatronBulkExtendLoans$1 extends React.Component {
29599
29788
  onClick: this.close
29600
29789
  }, "Cancel"), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Button, {
29601
29790
  onClick: this.handleSubmitExtend,
29602
- primary: true
29603
- }, "Extend the loans")));
29791
+ primary: true,
29792
+ loading: isChecking
29793
+ }, "Extend the loans"))));
29604
29794
  }
29605
29795
  }
29606
29796
  PatronBulkExtendLoans$1.defaultProps = {
@@ -35320,21 +35510,6 @@ class DocumentStats extends React.Component {
35320
35510
  class DocumentItem extends React.Component {
35321
35511
  constructor(props) {
35322
35512
  super(props);
35323
- this.fetchIdentifiersToDisplayInFrontsideTitles = () => {
35324
- const query = vocabularyApi.query().withType(invenioConfig.VOCABULARIES.item.identifier.scheme);
35325
- vocabularyApi.list(query.qs()).then(response => {
35326
- const identifiersToDisplayInFrontside = this.state.identifiersToDisplayInFrontside.map(identifier => {
35327
- const vocabEntry = response.data.hits.find(entry => entry.metadata.key === identifier.key);
35328
- return {
35329
- ...identifier,
35330
- text: vocabEntry ? vocabEntry.metadata.text : identifier.text
35331
- };
35332
- });
35333
- this.setState({
35334
- identifiersToDisplayInFrontside
35335
- });
35336
- });
35337
- };
35338
35513
  this.toggleItems = () => {
35339
35514
  const {
35340
35515
  isShowingAll
@@ -35343,18 +35518,10 @@ class DocumentItem extends React.Component {
35343
35518
  isShowingAll: !isShowingAll
35344
35519
  });
35345
35520
  };
35346
- const _identifiersToDisplayInFrontside = invenioConfig.ITEMS.identifiersToDisplayInFrontside.map(identifier => ({
35347
- key: identifier,
35348
- text: identifier
35349
- }));
35350
35521
  this.state = {
35351
35522
  isShowingAll: false,
35352
- itemAmountLimit: 5,
35353
- identifiersToDisplayInFrontside: _identifiersToDisplayInFrontside
35523
+ itemAmountLimit: 5
35354
35524
  };
35355
- if (_identifiersToDisplayInFrontside.length > 0) {
35356
- this.fetchIdentifiersToDisplayInFrontsideTitles();
35357
- }
35358
35525
  }
35359
35526
  get moreItemsToLoad() {
35360
35527
  const {
@@ -35370,12 +35537,12 @@ class DocumentItem extends React.Component {
35370
35537
  internalLocationName,
35371
35538
  items,
35372
35539
  documentDetails,
35373
- showTitle
35540
+ showTitle,
35541
+ identifiersToDisplay
35374
35542
  } = this.props;
35375
35543
  const {
35376
35544
  isShowingAll,
35377
- itemAmountLimit,
35378
- identifiersToDisplayInFrontside
35545
+ itemAmountLimit
35379
35546
  } = this.state;
35380
35547
  const previewArrayOfItems = items.slice(0, itemAmountLimit);
35381
35548
  const completeArrayOfItems = items;
@@ -35393,14 +35560,14 @@ class DocumentItem extends React.Component {
35393
35560
  id: "DocumentDetails.DocumentItem.TableHeader"
35394
35561
  }, /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Row, {
35395
35562
  "data-test": "header"
35396
- }, /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, null, "Barcode"), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, null, "Shelf"), identifiersToDisplayInFrontside.map(identifier => /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, {
35563
+ }, /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, null, "Barcode"), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, null, "Shelf"), identifiersToDisplay.map(identifier => /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, {
35397
35564
  key: identifier.key
35398
35565
  }, identifier.text)), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, null, "Status"), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, null, "Medium"), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.HeaderCell, null, "Loan restriction")))), /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Body, null, /*#__PURE__*/React__default["default"].createElement(Overridable__default["default"], {
35399
35566
  id: "DocumentDetails.DocumentItemTableBody"
35400
35567
  }, /*#__PURE__*/React__default["default"].createElement(DocumentItemBody, {
35401
35568
  items: itemsToShow,
35402
35569
  documentDetails: documentDetails,
35403
- identifiersToDisplayInFrontside: identifiersToDisplayInFrontside
35570
+ identifiersToDisplay: identifiersToDisplay
35404
35571
  }))), this.moreItemsToLoad && /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Table.Footer, {
35405
35572
  fullWidth: true,
35406
35573
  "data-test": "footer",
@@ -35492,7 +35659,8 @@ class DocumentTabs extends React.Component {
35492
35659
  };
35493
35660
  this.createInternalLocationTables = locationsObject => {
35494
35661
  const {
35495
- activeInternalLocation
35662
+ activeInternalLocation,
35663
+ identifiersToDisplay
35496
35664
  } = this.state;
35497
35665
  const sortedInternalLocationEntries = this.sortedLocationEntries(Object.entries(locationsObject));
35498
35666
  if (activeInternalLocation) {
@@ -35506,7 +35674,8 @@ class DocumentTabs extends React.Component {
35506
35674
  internalLocationName: internalLocationName,
35507
35675
  items: items,
35508
35676
  documentDetails: this.documentDetails,
35509
- showTitle: activeInternalLocation !== internalLocationName
35677
+ showTitle: activeInternalLocation !== internalLocationName,
35678
+ identifiersToDisplay: identifiersToDisplay
35510
35679
  });
35511
35680
  }
35512
35681
  return sortedInternalLocationEntries.map(_ref6 => {
@@ -35517,7 +35686,8 @@ class DocumentTabs extends React.Component {
35517
35686
  internalLocationName: internalLocationName,
35518
35687
  items: items,
35519
35688
  documentDetails: this.documentDetails,
35520
- showTitle: activeInternalLocation !== internalLocationName
35689
+ showTitle: activeInternalLocation !== internalLocationName,
35690
+ identifiersToDisplay: identifiersToDisplay
35521
35691
  });
35522
35692
  });
35523
35693
  };
@@ -35541,9 +35711,34 @@ class DocumentTabs extends React.Component {
35541
35711
  const [firstLocationName] = this.locationEntries[0];
35542
35712
  const [_firstInternalLocationName] = this.firstInternalLocationEntry(firstLocationName);
35543
35713
  this.state = {
35544
- activeInternalLocation: _firstInternalLocationName
35714
+ activeInternalLocation: _firstInternalLocationName,
35715
+ identifiersToDisplay: invenioConfig.ITEMS.identifiersToDisplay.map(s => ({
35716
+ key: s,
35717
+ text: s
35718
+ }))
35545
35719
  };
35546
35720
  }
35721
+ componentDidMount() {
35722
+ this.loadIdentifierTitles();
35723
+ }
35724
+ componentWillUnmount() {
35725
+ if (this.cancellableFetchIdentifierTitles) {
35726
+ this.cancellableFetchIdentifierTitles.cancel();
35727
+ }
35728
+ }
35729
+ async loadIdentifierTitles() {
35730
+ this.cancellableFetchIdentifierTitles = withCancel(fetchIdentifierTitles(invenioConfig.ITEMS.identifiersToDisplay, invenioConfig.VOCABULARIES.item.identifier.scheme));
35731
+ try {
35732
+ let result = await this.cancellableFetchIdentifierTitles.promise;
35733
+ this.setState({
35734
+ identifiersToDisplay: result
35735
+ });
35736
+ } catch (error) {
35737
+ if (error !== 'UNMOUNTED') {
35738
+ console.error('Error fetching identifier titles for items.', error);
35739
+ }
35740
+ }
35741
+ }
35547
35742
  get locations() {
35548
35743
  const {
35549
35744
  locationsObject
@@ -38948,6 +39143,36 @@ class LoanExtendButton extends React.Component {
38948
39143
  constructor(props) {
38949
39144
  super(props);
38950
39145
  this.triggerExtension = async () => {
39146
+ const {
39147
+ loan,
39148
+ onError
39149
+ } = this.props;
39150
+ const isUserPrivileged = isPrivilegedUser();
39151
+ if (isUserPrivileged) {
39152
+ this.setState({
39153
+ isLoading: true
39154
+ });
39155
+ try {
39156
+ const documentPid = _get__default["default"](loan, 'metadata.document.pid');
39157
+ const isOverbooked = await isDocumentOverbooked(documentPid);
39158
+ if (isOverbooked) {
39159
+ this.setState({
39160
+ isLoading: false,
39161
+ showOverbookedConfirm: true
39162
+ });
39163
+ return;
39164
+ }
39165
+ } catch (error) {
39166
+ onError('Something went wrong while checking if the literature is overbooked.', error);
39167
+ this.setState({
39168
+ isLoading: false
39169
+ });
39170
+ return;
39171
+ }
39172
+ }
39173
+ await this.performExtension();
39174
+ };
39175
+ this.performExtension = async () => {
38951
39176
  const {
38952
39177
  loan,
38953
39178
  onSuccess,
@@ -38965,7 +39190,8 @@ class LoanExtendButton extends React.Component {
38965
39190
  this.cancellableExtendLoan = withCancel(this.extendLoan(extendUrl, document.pid, patronPid));
38966
39191
  const response = await this.cancellableExtendLoan.promise;
38967
39192
  this.setState({
38968
- isLoading: false
39193
+ isLoading: false,
39194
+ showOverbookedConfirm: false
38969
39195
  });
38970
39196
  const documentTitle = document.title;
38971
39197
  onSuccess(INFO_MESSAGES.SUCCESS(documentTitle, luxon.DateTime.fromISO(response.data.metadata.end_date)));
@@ -38976,6 +39202,11 @@ class LoanExtendButton extends React.Component {
38976
39202
  onError(error.response.data.message);
38977
39203
  }
38978
39204
  };
39205
+ this.hideOverbookedConfirm = () => {
39206
+ this.setState({
39207
+ showOverbookedConfirm: false
39208
+ });
39209
+ };
38979
39210
  this.validate = loan => {
38980
39211
  const hasExtendAction = _has__default["default"](loan, 'availableActions.extend');
38981
39212
  if (!hasExtendAction) return {
@@ -39003,7 +39234,8 @@ class LoanExtendButton extends React.Component {
39003
39234
  };
39004
39235
  };
39005
39236
  this.state = {
39006
- isLoading: false
39237
+ isLoading: false,
39238
+ showOverbookedConfirm: false
39007
39239
  };
39008
39240
  }
39009
39241
  componentWillUnmount() {
@@ -39022,11 +39254,21 @@ class LoanExtendButton extends React.Component {
39022
39254
  loan
39023
39255
  } = this.props;
39024
39256
  const {
39025
- isLoading
39257
+ isLoading,
39258
+ showOverbookedConfirm
39026
39259
  } = this.state;
39027
39260
  const validation = this.validate(loan);
39028
39261
  const isDisabled = !validation.isValid;
39029
- return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, isDisabled && /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Popup, {
39262
+ const overbookedDocuments = [{
39263
+ title: _get__default["default"](loan, 'metadata.document.title', 'Unknown title'),
39264
+ loanRequestId: loan.id
39265
+ }];
39266
+ return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, showOverbookedConfirm && /*#__PURE__*/React__default["default"].createElement(OverbookedConfirmModal, {
39267
+ open: showOverbookedConfirm,
39268
+ onClose: this.hideOverbookedConfirm,
39269
+ onConfirm: this.performExtension,
39270
+ overbookedDocuments: overbookedDocuments
39271
+ }), isDisabled && /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Popup, {
39030
39272
  content: validation.msg,
39031
39273
  trigger: /*#__PURE__*/React__default["default"].createElement(semanticUiReact.Icon, {
39032
39274
  name: "info"