diff --git a/CHANGELOG.md b/CHANGELOG.md
index ada3de9f2..1de2d36da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,11 @@
# Change history for ui-inventory
+## [11.0.0] IN PROGRESS
-## [10.1.0] IN PROGRESS
-
+* *BREAKING* Replace imports from quick-marc with stripes-marc-components. Refs UIIN-2636.
* Make Inventory search and browse query boxes expandable. Refs UIIN-2493.
+* Added support for `containsAny` match option in Advanced search. Refs UIIN-2486.
+* Inventory search/browse: Do not retain checkbox selections when toggling search segment. Refs UIIN-2477.
## [10.0.1] IN PROGRESS
diff --git a/package.json b/package.json
index 4dc9374e4..1690b7162 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@folio/inventory",
- "version": "10.1.0",
+ "version": "11.0.0",
"description": "Inventory manager",
"repository": "folio-org/ui-inventory",
"publishConfig": {
@@ -862,6 +862,7 @@
"@folio/stripes-components": "^12.0.0",
"@folio/stripes-connect": "^9.0.0",
"@folio/stripes-core": "^10.0.0",
+ "@folio/stripes-marc-components": "^1.0.0",
"@folio/stripes-smart-components": "^9.0.0",
"@folio/stripes-testing": "^4.6.0",
"@folio/stripes-util": "^6.0.0",
@@ -882,7 +883,6 @@
"zustand": "^4.1.1"
},
"dependencies": {
- "@folio/quick-marc": "^7.0.0",
"@folio/stripes-acq-components": "^5.0.0",
"classnames": "^2.3.2",
"file-saver": "^2.0.0",
@@ -904,6 +904,7 @@
},
"peerDependencies": {
"@folio/stripes": "^9.0.0",
+ "@folio/stripes-marc-components": "^1.0.0",
"react": "^18.2.0",
"react-intl": "^6.4.4",
"react-query": "^3.6.0",
diff --git a/src/ViewHoldingsRecord.js b/src/ViewHoldingsRecord.js
index 81d06a913..218794b9e 100644
--- a/src/ViewHoldingsRecord.js
+++ b/src/ViewHoldingsRecord.js
@@ -81,7 +81,7 @@ class ViewHoldingsRecord extends React.Component {
path: 'holdings-storage/holdings/:{holdingsrecordid}',
resourceShouldRefresh: false,
accumulate: true,
- tenant: '!{location.state.tenantTo}'
+ tenant: '!{tenantTo}',
},
items: {
type: 'okapi',
@@ -97,7 +97,7 @@ class ViewHoldingsRecord extends React.Component {
type: 'okapi',
path: 'inventory/instances/:{id}',
accumulate: true,
- tenant: '!{location.state.tenantTo}'
+ tenant: '!{tenantTo}',
},
tagSettings: {
type: 'okapi',
diff --git a/src/components/InstancesList/InstancesList.js b/src/components/InstancesList/InstancesList.js
index 2626f3d83..83dce4f6b 100644
--- a/src/components/InstancesList/InstancesList.js
+++ b/src/components/InstancesList/InstancesList.js
@@ -393,6 +393,11 @@ class InstancesList extends React.Component {
document.getElementById('input-inventory-search').focus();
}
+ handleSearchSegmentChange = (segment) => {
+ this.refocusOnInputSearch(segment);
+ this.setState({ selectedRows: {} });
+ }
+
onSearchModeSwitch = () => {
const {
namespace,
@@ -415,7 +420,7 @@ class InstancesList extends React.Component {
/>
>
);
diff --git a/src/components/InstancesList/InstancesList.test.js b/src/components/InstancesList/InstancesList.test.js
index 2680d5c2f..cb1324a32 100644
--- a/src/components/InstancesList/InstancesList.test.js
+++ b/src/components/InstancesList/InstancesList.test.js
@@ -221,6 +221,22 @@ describe('InstancesList', () => {
});
});
+ describe('when search segment is changed', () => {
+ it('should clear selected rows', () => {
+ const {
+ getAllByLabelText,
+ getByText,
+ } = renderInstancesList({
+ segment: 'instances',
+ });
+
+ fireEvent.click(getAllByLabelText('Select instance')[0]);
+ fireEvent.click(getByText('Holdings'));
+
+ expect(getAllByLabelText('Select instance')[0].checked).toBeFalsy();
+ });
+ });
+
describe('when a user performs a search and clicks the `Next` button in the list of records', () => {
describe('then clicks on the `Browse` lookup tab and then clicks `Search` lookup tab', () => {
it('should avoid infinity loading by resetting the records on unmounting', () => {
diff --git a/src/components/ViewSource/ViewSource.js b/src/components/ViewSource/ViewSource.js
index ce8fa5cae..57fab5307 100644
--- a/src/components/ViewSource/ViewSource.js
+++ b/src/components/ViewSource/ViewSource.js
@@ -10,9 +10,11 @@ import {
LoadingView,
} from '@folio/stripes/components';
import { useStripes } from '@folio/stripes/core';
-import MarcView from '@folio/quick-marc/src/QuickMarcView/QuickMarcView';
-import PrintPopup from '@folio/quick-marc/src/QuickMarcView/PrintPopup';
-import { getHeaders } from '@folio/quick-marc/src/QuickMarcEditor/utils';
+import {
+ MarcView,
+ PrintPopup,
+ getHeaders,
+} from '@folio/stripes-marc-components';
import { useGoBack } from '../../common/hooks';
diff --git a/src/components/ViewSource/ViewSource.test.js b/src/components/ViewSource/ViewSource.test.js
index a3777ab79..a719b9381 100644
--- a/src/components/ViewSource/ViewSource.test.js
+++ b/src/components/ViewSource/ViewSource.test.js
@@ -89,18 +89,18 @@ describe('ViewSource', () => {
});
});
- it('should render QuickMarcView', () => {
- expect(screen.getByText('QuickMarcView')).toBeInTheDocument();
+ it('should render MarcView', () => {
+ expect(screen.getByText('MarcView')).toBeInTheDocument();
});
it('should initiate useGoBack with correct path', () => {
expect(useGoBack).toBeCalledWith('/inventory/view/instance-id');
});
- describe('when QuickMarcView is closed', () => {
+ describe('when MarcView is closed', () => {
it('should call onClose with correct url', async () => {
- await waitFor(() => expect(screen.getByText('QuickMarcView')).toBeInTheDocument());
- act(() => fireEvent.click(screen.getByText('QuickMarcView')));
+ await waitFor(() => expect(screen.getByText('MarcView')).toBeInTheDocument());
+ act(() => fireEvent.click(screen.getByText('MarcView')));
expect(mockGoBack).toBeCalledTimes(1);
});
});
diff --git a/src/constants.js b/src/constants.js
index 534d4d280..2aaa0852c 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -466,146 +466,175 @@ export const fieldSearchConfigurations = {
exactPhrase: 'keyword=="%{query.query}"',
containsAll: 'keyword all "%{query.query}"',
startsWith: 'keyword all "%{query.query}*"',
+ containsAny: 'keyword any "%{query.query}"',
},
contributor: {
exactPhrase: 'contributors.name=="%{query.query}"',
containsAll: 'contributors.name="*%{query.query}*"',
startsWith: 'contributors.name="%{query.query}*"',
+ containsAny: 'contributors.name any "*%{query.query}*"',
},
title: {
exactPhrase: 'title=="%{query.query}"',
containsAll: 'title all "%{query.query}"',
startsWith: 'title all "%{query.query}*"',
+ containsAny: 'title any "%{query.query}"',
},
isbn: {
exactPhrase: 'isbn=="%{query.query}"',
containsAll: 'isbn="*%{query.query}*"',
startsWith: 'isbn="%{query.query}*"',
+ containsAny: 'isbn any "*%{query.query}*"',
},
issn: {
exactPhrase: 'issn=="%{query.query}"',
containsAll: 'issn="*%{query.query}*"',
startsWith: 'issn="%{query.query}*"',
+ containsAny: 'issn any "*%{query.query}*"',
},
identifier: {
exactPhrase: 'identifiers.value=="%{query.query}"',
containsAll: 'identifiers.value="*%{query.query}*"',
startsWith: 'identifiers.value="%{query.query}*"',
+ containsAny: 'identifiers.value any "*%{query.query}*"',
},
oclc: {
exactPhrase: 'oclc=="%{query.query}"',
containsAll: 'oclc="*%{query.query}*"',
startsWith: 'oclc="%{query.query}*"',
+ containsAny: 'oclc any "*%{query.query}*"',
},
instanceNotes: {
exactPhrase: 'notes.note=="%{query.query}" or administrativeNotes=="%{query.query}"',
containsAll: 'notes.note all "%{query.query}" or administrativeNotes all "%{query.query}"',
startsWith: 'notes.note all "%{query.query}*" or administrativeNotes all "%{query.query}*"',
+ containsAny: 'notes.note any "%{query.query}" or administrativeNotes any "%{query.query}"',
},
instanceAdministrativeNotes: {
exactPhrase: 'administrativeNotes=="%{query.query}"',
containsAll: 'administrativeNotes all "%{query.query}"',
startsWith: 'administrativeNotes all "%{query.query}*"',
+ containsAny: 'administrativeNotes any "%{query.query}"',
},
subject: {
exactPhrase: 'subjects.value=="%{query.query}"',
containsAll: 'subjects.value all "%{query.query}"',
startsWith: 'subjects.value=="%{query.query}*"',
+ containsAny: 'subjects.value any "%{query.query}"',
},
callNumber: {
exactPhrase: 'itemEffectiveShelvingOrder=="%{query.query}"',
containsAll: 'itemEffectiveShelvingOrder="*%{query.query}*"',
startsWith: 'itemEffectiveShelvingOrder=="%{query.query}*"',
+ containsAny: 'itemEffectiveShelvingOrder any "*%{query.query}*"',
},
hrid: {
exactPhrase: 'hrid=="%{query.query}"',
containsAll: 'hrid=="*%{query.query}*"',
startsWith: 'hrid=="%{query.query}*"',
+ containsAny: 'hrid any "*%{query.query}*"',
},
id: {
exactPhrase: 'id=="%{query.query}"',
containsAll: 'id="*%{query.query}*"',
startsWith: 'id="%{query.query}*"',
+ containsAny: 'id any "*%{query.query}*"',
},
authorityId: {
exactPhrase: 'authorityId == %{query.query}',
containsAll: 'authorityId=="*%{query.query}*"',
startsWith: 'authorityId=="%{query.query}*"',
+ containsAny: 'authorityId any "*%{query.query}*"',
},
allFields: {
exactPhrase: 'cql.all=="%{query.query}"',
containsAll: 'cql.all all "%{query.query}"',
startsWith: 'cql.all all "%{query.query}*"',
+ containsAny: 'cql.all any "%{query.query}"',
},
holdingsFullCallNumbers: {
exactPhrase: 'holdingsFullCallNumbers=="%{query.query}"',
containsAll: 'holdingsFullCallNumbers="*%{query.query}*"',
startsWith: 'holdingsFullCallNumbers="%{query.query}*"',
+ containsAny: 'holdingsFullCallNumbers any "*%{query.query}*"',
},
holdingsNormalizedCallNumbers: {
exactPhrase: 'holdingsNormalizedCallNumbers=="%{query.query}"',
containsAll: 'holdingsNormalizedCallNumbers="*%{query.query}*"',
startsWith: 'holdingsNormalizedCallNumbers="%{query.query}*"',
+ containsAny: 'holdingsNormalizedCallNumbers any "*%{query.query}*"',
},
holdingsNotes: {
exactPhrase: 'holdings.notes.note=="%{query.query}" or holdings.administrativeNotes=="%{query.query}"',
containsAll: 'holdings.notes.note all "%{query.query}" or holdings.administrativeNotes all "%{query.query}"',
startsWith: 'holdings.notes.note all "%{query.query}*" or holdings.administrativeNotes all "%{query.query}*"',
+ containsAny: 'holdings.notes.note any "%{query.query}" or holdings.administrativeNotes any "%{query.query}"',
},
holdingsAdministrativeNotes: {
exactPhrase: 'holdings.administrativeNotes=="%{query.query}"',
containsAll: 'holdings.administrativeNotes all "%{query.query}"',
startsWith: 'holdings.administrativeNotes all "%{query.query}*"',
+ containsAny: 'holdings.administrativeNotes any "%{query.query}"',
},
holdingsHrid: {
exactPhrase: 'holdings.hrid=="%{query.query}"',
containsAll: 'holdings.hrid=="*%{query.query}*"',
startsWith: 'holdings.hrid=="%{query.query}*"',
+ containsAny: 'holdings.hrid any "*%{query.query}*"',
},
hid: {
exactPhrase: 'holdings.id=="%{query.query}"',
containsAll: 'holdings.id="*%{query.query}*"',
startsWith: 'holdings.id="%{query.query}*"',
+ containsAny: 'holdings.id any "*%{query.query}*"',
},
barcode: {
exactPhrase: 'items.barcode=="%{query.query}"',
containsAll: 'items.barcode="*%{query.query}*"',
startsWith: 'items.barcode="%{query.query}*"',
+ containsAny: 'items.barcode any "*%{query.query}*"',
},
itemFullCallNumbers: {
exactPhrase: 'itemFullCallNumbers=="%{query.query}"',
containsAll: 'itemFullCallNumbers="*%{query.query}*"',
startsWith: 'itemFullCallNumbers="%{query.query}*"',
+ containsAny: 'itemFullCallNumbers any "*%{query.query}*"',
},
itemNormalizedCallNumbers: {
exactPhrase: 'itemNormalizedCallNumbers=="%{query.query}"',
containsAll: 'itemNormalizedCallNumbers="*%{query.query}*"',
startsWith: 'itemNormalizedCallNumbers="%{query.query}*"',
+ containsAny: 'itemNormalizedCallNumbers any "*%{query.query}*"',
},
itemNotes: {
exactPhrase: 'item.notes.note=="%{query.query}" or item.administrativeNotes=="%{query.query}"',
containsAll: 'item.notes.note all "%{query.query}" or item.administrativeNotes all "%{query.query}"',
startsWith: 'item.notes.note all "%{query.query}*" or item.administrativeNotes all "%{query.query}*"',
+ containsAny: 'item.notes.note any "%{query.query}" or item.administrativeNotes any "%{query.query}"',
},
itemAdministrativeNotes: {
exactPhrase: 'item.administrativeNotes=="%{query.query}"',
containsAll: 'item.administrativeNotes all "%{query.query}"',
startsWith: 'item.administrativeNotes all "%{query.query}*"',
+ containsAny: 'item.administrativeNotes any "%{query.query}"',
},
itemCirculationNotes: {
exactPhrase: 'item.circulationNotes.note=="%{query.query}"',
containsAll: 'item.circulationNotes.note all "%{query.query}"',
startsWith: 'item.circulationNotes.note all "%{query.query}*"',
+ containsAny: 'item.circulationNotes.note any "%{query.query}"',
},
itemHrid: {
exactPhrase: 'items.hrid=="%{query.query}"',
containsAll: 'items.hrid="*%{query.query}*"',
startsWith: 'items.hrid="%{query.query}*"',
+ containsAny: 'items.hrid any "*%{query.query}*"',
},
iid: {
exactPhrase: 'item.id=="%{query.query}"',
containsAll: 'item.id="*%{query.query}*"',
startsWith: 'item.id="%{query.query}*"',
+ containsAny: 'item.id any "*%{query.query}*"',
},
};
diff --git a/src/routes/ItemRoute.js b/src/routes/ItemRoute.js
index 729e04133..d9a14fd2e 100644
--- a/src/routes/ItemRoute.js
+++ b/src/routes/ItemRoute.js
@@ -1,190 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
-import {
- flowRight,
- get,
-} from 'lodash';
+import { flowRight } from 'lodash';
import { stripesConnect } from '@folio/stripes/core';
-import { requestsStatusString } from '../Instance/ViewRequests/utils';
-
import withLocation from '../withLocation';
import { ItemView } from '../views';
-import { PaneLoading } from '../components';
import { DataContext } from '../contexts';
-const getRequestsPath = `circulation/requests?query=(itemId==:{itemid}) and status==(${requestsStatusString}) sortby requestDate desc&limit=1`;
-
class ItemRoute extends React.Component {
- static manifest = Object.freeze({
- query: {},
- itemsResource: {
- type: 'okapi',
- path: 'inventory/items/:{itemid}',
- POST: { path: 'inventory/items' },
- resourceShouldRefresh: true,
- tenant: '!{location.state.tenantTo}',
- },
- markItemAsWithdrawn: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-withdrawn',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markItemAsMissing: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-missing',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markAsInProcess: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-in-process',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markAsInProcessNonRequestable: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-in-process-non-requestable',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markAsIntellectualItem: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-intellectual-item',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markAsLongMissing: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-long-missing',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markAsRestricted: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-restricted',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markAsUnavailable: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-unavailable',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- markAsUnknown: {
- type: 'okapi',
- POST: {
- path: 'inventory/items/:{itemid}/mark-unknown',
- },
- clientGeneratePk: false,
- fetch: false,
- },
- holdingsRecords: {
- type: 'okapi',
- path: 'holdings-storage/holdings/:{holdingsrecordid}',
- tenant: '!{location.state.tenantTo}',
- },
- instanceRecords: {
- type: 'okapi',
- path: 'inventory/instances/:{id}',
- resourceShouldRefresh: true,
- },
- servicePoints: {
- type: 'okapi',
- path: 'service-points',
- records: 'servicepoints',
- params: (_q, _p, _r, _l, props) => {
- // Only one service point is of interest here: the SP used for the item's last check-in
- // (if the item has a check-in). Iff that service point ID is found, add a query param
- // to filter down to that one service point in the records returned.
- const servicePointId = get(props.resources, 'itemsResource.records[0].lastCheckIn.servicePointId', '');
- const query = servicePointId && `id==${servicePointId}`;
- return query ? { query } : {};
- },
- resourceShouldRefresh: true,
- },
- staffMembers: {
- type: 'okapi',
- path: 'users',
- records: 'users',
- params: (_q, _p, _r, _l, props) => {
- const staffMemberId = get(props.resources, 'itemsResource.records[0].lastCheckIn.staffMemberId', '');
- const query = staffMemberId && `id==${staffMemberId}`;
-
- return query ? { query } : null;
- },
- resourceShouldRefresh: true,
- },
- // return a count of the requests matching the given item and status
- requests: {
- type: 'okapi',
- path: getRequestsPath,
- records: 'requests',
- PUT: { path: 'circulation/requests/%{requestOnItem.id}' },
- },
- openLoans: {
- type: 'okapi',
- path: 'circulation/loans',
- params: {
- query: 'status.name=="Open" and itemId==:{itemid}',
- },
- records: 'loans',
- },
- requestOnItem: {},
- tagSettings: {
- type: 'okapi',
- records: 'configs',
- path: 'configurations/entries?query=(module==TAGS and configName==tags_enabled)',
- },
- });
-
- isLoading = () => {
+ render() {
const {
- resources: {
- itemsResource,
- holdingsRecords,
- instanceRecords,
- },
+ stripes: { okapi },
+ location: { state },
} = this.props;
- if (!itemsResource?.hasLoaded ||
- !instanceRecords?.hasLoaded ||
- !holdingsRecords?.hasLoaded) {
- return true;
- }
-
- return false;
- }
-
- render() {
- if (this.isLoading()) {
- return ;
- }
-
return (
{data => (
)}
diff --git a/src/routes/ViewHoldingRoute.js b/src/routes/ViewHoldingRoute.js
index 7ad407a93..ff1b74871 100644
--- a/src/routes/ViewHoldingRoute.js
+++ b/src/routes/ViewHoldingRoute.js
@@ -1,5 +1,10 @@
import { useContext } from 'react';
-import { useParams } from 'react-router-dom';
+import {
+ useParams,
+ useLocation,
+} from 'react-router-dom';
+
+import { useStripes } from '@folio/stripes/core';
import { DataContext } from '../contexts';
import ViewHoldingsRecord from '../ViewHoldingsRecord';
@@ -7,10 +12,13 @@ import ViewHoldingsRecord from '../ViewHoldingsRecord';
const ViewHoldingRoute = () => {
const { id: instanceId, holdingsrecordid } = useParams();
const referenceTables = useContext(DataContext);
+ const { okapi } = useStripes();
+ const { state } = useLocation();
return (
diff --git a/src/routes/buildManifestObject.js b/src/routes/buildManifestObject.js
index 291da5195..724325d77 100644
--- a/src/routes/buildManifestObject.js
+++ b/src/routes/buildManifestObject.js
@@ -2,7 +2,10 @@ import {
get,
} from 'lodash';
-import { makeQueryFunction } from '@folio/stripes/smart-components';
+import {
+ makeQueryFunction,
+ advancedSearchQueryToRows,
+} from '@folio/stripes/smart-components';
import {
CQL_FIND_ALL,
fieldSearchConfigurations,
@@ -21,38 +24,7 @@ const getQueryTemplateContributor = (queryValue) => `contributors.name==/string
const getAdvancedSearchQueryTemplate = (queryIndex, matchOption) => fieldSearchConfigurations[queryIndex]?.[matchOption];
export const getAdvancedSearchTemplate = (queryValue) => {
- const splitIntoRowsRegex = /(?=\sor\s|\sand\s|\snot\s)/g;
-
- // split will return array of strings:
- // ['keyword==test', 'or issn=123', ...]
- const rows = queryValue.split(splitIntoRowsRegex).map(i => i.trim());
-
- return rows.map((match, index) => {
- let bool = '';
- let query = match;
-
- // first row doesn't have a bool operator
- if (index !== 0) {
- bool = match.substr(0, match.indexOf(' '));
- query = match.substr(bool.length);
- }
-
- const splitIndexAndQueryRegex = /([^=]+)(exactPhrase|containsAll|startsWith)(.+)/g;
-
-
- const rowParts = [...query.matchAll(splitIndexAndQueryRegex)]?.[0] || [];
- // eslint-disable-next-line no-unused-vars
- const [, option, _match, value] = rowParts
- .map(i => i.trim())
- .map(i => i.replaceAll('"', ''));
-
- return {
- query: value,
- bool,
- searchOption: option,
- match: _match,
- };
- }).reduce((acc, row) => {
+ return advancedSearchQueryToRows(queryValue).reduce((acc, row) => {
const rowTemplate = getAdvancedSearchQueryTemplate(row.searchOption, row.match);
if (!rowTemplate) {
diff --git a/src/views/ItemView.js b/src/views/ItemView.js
index 30f224f74..d018c961d 100644
--- a/src/views/ItemView.js
+++ b/src/views/ItemView.js
@@ -5,6 +5,7 @@ import {
isEmpty,
values,
sortBy,
+ flowRight,
} from 'lodash';
import { parameterize } from 'inflected';
@@ -51,8 +52,11 @@ import {
IntlConsumer,
CalloutContext,
checkIfUserInCentralTenant,
+ stripesConnect,
} from '@folio/stripes/core';
+import { requestsStatusString } from '../Instance/ViewRequests/utils';
+
import ModalContent from '../components/ModalContent';
import { ItemAcquisition } from '../Item/ViewItem/ItemAcquisition';
import {
@@ -86,11 +90,152 @@ import {
WarningMessage,
AdministrativeNoteList,
ItemViewSubheader,
+ PaneLoading,
} from '../components';
export const requestStatusFiltersString = map(REQUEST_OPEN_STATUSES, requestStatus => `requestStatus.${requestStatus}`).join(',');
class ItemView extends React.Component {
+ static manifest = Object.freeze({
+ query: {},
+ itemsResource: {
+ type: 'okapi',
+ path: 'inventory/items/:{itemid}',
+ POST: { path: 'inventory/items' },
+ resourceShouldRefresh: true,
+ tenant: '!{tenantTo}',
+ },
+ markItemAsWithdrawn: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-withdrawn',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markItemAsMissing: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-missing',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markAsInProcess: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-in-process',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markAsInProcessNonRequestable: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-in-process-non-requestable',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markAsIntellectualItem: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-intellectual-item',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markAsLongMissing: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-long-missing',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markAsRestricted: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-restricted',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markAsUnavailable: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-unavailable',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ markAsUnknown: {
+ type: 'okapi',
+ POST: {
+ path: 'inventory/items/:{itemid}/mark-unknown',
+ },
+ clientGeneratePk: false,
+ fetch: false,
+ },
+ holdingsRecords: {
+ type: 'okapi',
+ path: 'holdings-storage/holdings/:{holdingsrecordid}',
+ tenant: '!{tenantTo}',
+ },
+ instanceRecords: {
+ type: 'okapi',
+ path: 'inventory/instances/:{id}',
+ resourceShouldRefresh: true,
+ },
+ servicePoints: {
+ type: 'okapi',
+ path: 'service-points',
+ records: 'servicepoints',
+ params: (_q, _p, _r, _l, props) => {
+ // Only one service point is of interest here: the SP used for the item's last check-in
+ // (if the item has a check-in). Iff that service point ID is found, add a query param
+ // to filter down to that one service point in the records returned.
+ const servicePointId = get(props.resources, 'itemsResource.records[0].lastCheckIn.servicePointId', '');
+ const query = servicePointId && `id==${servicePointId}`;
+ return query ? { query } : {};
+ },
+ resourceShouldRefresh: true,
+ },
+ staffMembers: {
+ type: 'okapi',
+ path: 'users',
+ records: 'users',
+ params: (_q, _p, _r, _l, props) => {
+ const staffMemberId = get(props.resources, 'itemsResource.records[0].lastCheckIn.staffMemberId', '');
+ const query = staffMemberId && `id==${staffMemberId}`;
+
+ return query ? { query } : null;
+ },
+ resourceShouldRefresh: true,
+ },
+ // return a count of the requests matching the given item and status
+ requests: {
+ type: 'okapi',
+ path: `circulation/requests?query=(itemId==:{itemid}) and status==(${requestsStatusString}) sortby requestDate desc&limit=1`,
+ records: 'requests',
+ PUT: { path: 'circulation/requests/%{requestOnItem.id}' },
+ },
+ openLoans: {
+ type: 'okapi',
+ path: 'circulation/loans',
+ params: {
+ query: 'status.name=="Open" and itemId==:{itemid}',
+ },
+ records: 'loans',
+ },
+ requestOnItem: {},
+ tagSettings: {
+ type: 'okapi',
+ records: 'configs',
+ path: 'configurations/entries?query=(module==TAGS and configName==tags_enabled)',
+ },
+ });
+
static contextType = CalloutContext;
constructor(props) {
@@ -452,7 +597,23 @@ class ItemView extends React.Component {
getEntity = () => this.props.resources.itemsResource.records[0];
getEntityTags = () => this.props.resources.itemsResource.records[0]?.tags?.tagList || [];
+ isLoading = () => {
+ const {
+ resources: {
+ instanceRecords,
+ itemsResource,
+ holdingsRecords,
+ },
+ } = this.props;
+
+ return !itemsResource?.hasLoaded || !instanceRecords?.hasLoaded || !holdingsRecords?.hasLoaded;
+ }
+
render() {
+ if (this.isLoading()) {
+ return ;
+ }
+
const {
resources: {
itemsResource,
@@ -1536,15 +1697,24 @@ ItemView.propTypes = {
})
}).isRequired,
resources: PropTypes.shape({
- instanceRecords: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object) }),
+ instanceRecords: PropTypes.shape({
+ hasLoaded: PropTypes.bool,
+ records: PropTypes.arrayOf(PropTypes.object),
+ }),
loanTypes: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object) }),
requests: PropTypes.shape({
records: PropTypes.arrayOf(PropTypes.object),
other: PropTypes.object,
}),
loans: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object) }),
- itemsResource: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object) }),
- holdingsRecords: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object) }),
+ itemsResource: PropTypes.shape({
+ hasLoaded: PropTypes.bool,
+ records: PropTypes.arrayOf(PropTypes.object),
+ }),
+ holdingsRecords: PropTypes.shape({
+ hasLoaded: PropTypes.bool,
+ records: PropTypes.arrayOf(PropTypes.object),
+ }),
callNumberTypes: PropTypes.shape({ records: PropTypes.arrayOf(PropTypes.object) }),
borrower: PropTypes.object,
staffMembers: PropTypes.object,
@@ -1596,4 +1766,7 @@ ItemView.propTypes = {
history: PropTypes.object.isRequired,
};
-export default withLocation(ItemView);
+export default flowRight(
+ stripesConnect,
+ withLocation,
+)(ItemView);
diff --git a/src/views/ItemView.test.js b/src/views/ItemView.test.js
index f65448ee8..13492c212 100644
--- a/src/views/ItemView.test.js
+++ b/src/views/ItemView.test.js
@@ -30,6 +30,7 @@ const stripesStub = {
const resources = {
holdingsRecords: {
+ hasLoaded: true,
records: [
{
permanentLocationId: 1,
@@ -38,6 +39,7 @@ const resources = {
],
},
itemsResource: {
+ hasLoaded: true,
records: [
{
id: 'item1',
@@ -86,6 +88,7 @@ const resources = {
],
},
instanceRecords: {
+ hasLoaded: true,
records: [
{
id: 1,
diff --git a/test/jest/__mock__/index.js b/test/jest/__mock__/index.js
index b75a3ae37..cb1422de0 100644
--- a/test/jest/__mock__/index.js
+++ b/test/jest/__mock__/index.js
@@ -6,7 +6,7 @@ import './stripesCore.mock';
import './stripesIcon.mock';
import './stripesSmartComponents.mock';
import './InstancePlugin.mock';
-import './quickMarc.mock';
+import './stripesMarcComponents.mock';
import './stripesComponents.mock';
import './reactBeautifulDnd.mock';
import './react-virtualized-auto-sizer';
diff --git a/test/jest/__mock__/quickMarc.mock.js b/test/jest/__mock__/quickMarc.mock.js
deleted file mode 100644
index d38605f94..000000000
--- a/test/jest/__mock__/quickMarc.mock.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react';
-
-jest.mock('@folio/quick-marc/src/QuickMarcView/QuickMarcView', () => ({ onClose, marcTitle }) => (
- <>
- {marcTitle}
-
- >
-));
diff --git a/test/jest/__mock__/stripesMarcComponents.mock.js b/test/jest/__mock__/stripesMarcComponents.mock.js
new file mode 100644
index 000000000..ea3bf1551
--- /dev/null
+++ b/test/jest/__mock__/stripesMarcComponents.mock.js
@@ -0,0 +1,13 @@
+import React from 'react';
+
+jest.mock('@folio/stripes-marc-components', () => ({
+ ...jest.requireActual('@folio/stripes-marc-components'),
+ MarcView: jest.fn(({ onClose, marcTitle }) => (
+ <>
+ {marcTitle}
+
+ >
+ )),
+}));