diff --git a/CHANGELOG.md b/CHANGELOG.md
index e18f92d55..680d4960b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
## 10.0.1 IN PROGRESS
+* Instance 3rd pane: Adjust behavior when returning to instance from holdings/item full screen. Refs UIIN-2453.
+* Consortial holdings accordion is not appearing after the sharing of Instance. Fixes UIIN-2629.
* Enable/disable consortial holdings/item actions based on User permissions. Refs UIIN-2452.
## [10.0.0](https://github.com/folio-org/ui-inventory/tree/v10.0.0) (2023-10-13)
diff --git a/src/Instance/HoldingsList/Holding/Holding.js b/src/Instance/HoldingsList/Holding/Holding.js
index 907d2283b..8a0b1adb9 100644
--- a/src/Instance/HoldingsList/Holding/Holding.js
+++ b/src/Instance/HoldingsList/Holding/Holding.js
@@ -26,6 +26,8 @@ const Holding = ({
showViewHoldingsButton,
showAddItemButton,
isBarcodeAsHotlink,
+ instanceId,
+ pathToAccordionsState,
}) => {
return (
@@ -55,6 +57,8 @@ const Holding = ({
onViewHolding={onViewHolding}
onAddItem={onAddItem}
tenantId={tenantId}
+ instanceId={instanceId}
+ pathToAccordionsState={pathToAccordionsState}
showViewHoldingsButton={showViewHoldingsButton}
showAddItemButton={showAddItemButton}
>
@@ -75,6 +79,7 @@ Holding.propTypes = {
holding: PropTypes.object.isRequired,
onViewHolding: PropTypes.func.isRequired,
onAddItem: PropTypes.func.isRequired,
+ instanceId: PropTypes.string.isRequired,
holdings: PropTypes.arrayOf(PropTypes.object),
draggable: PropTypes.bool,
droppable: PropTypes.bool,
@@ -86,10 +91,12 @@ Holding.propTypes = {
showViewHoldingsButton: PropTypes.bool,
showAddItemButton: PropTypes.bool,
isBarcodeAsHotlink: PropTypes.bool,
+ pathToAccordionsState: PropTypes.arrayOf(PropTypes.string),
};
Holding.defaultProps = {
isItemsDroppable: true,
+ pathToAccordionsState: [],
};
export default Holding;
diff --git a/src/Instance/HoldingsList/Holding/Holding.test.js b/src/Instance/HoldingsList/Holding/Holding.test.js
index d024cea30..d8acd3b8b 100644
--- a/src/Instance/HoldingsList/Holding/Holding.test.js
+++ b/src/Instance/HoldingsList/Holding/Holding.test.js
@@ -29,6 +29,7 @@ const HoldingSetup = () => (
(
isHoldingDragSelected={isHoldingDragSelected}
isDraggable={isDraggable}
isItemsDroppable={isItemsDroppable}
+ pathToAccordionsState={['holdings']}
/>
);
diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.js
index 31397aaf4..401cbc991 100644
--- a/src/Instance/HoldingsList/Holding/HoldingAccordion.js
+++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.js
@@ -11,13 +11,14 @@ import {
} from '@folio/stripes/components';
import { callNumberLabel } from '../../../utils';
+
+import HoldingButtonsGroup from './HoldingButtonsGroup';
import {
+ useHoldingsAccordionState,
useLocationsQuery,
useHoldingItemsQuery,
} from '../../../hooks';
-import HoldingButtonsGroup from './HoldingButtonsGroup';
-
const HoldingAccordion = ({
children,
holding,
@@ -28,13 +29,16 @@ const HoldingAccordion = ({
tenantId,
showViewHoldingsButton,
showAddItemButton,
+ instanceId,
+ pathToAccordionsState,
}) => {
const searchParams = {
limit: 0,
offset: 0,
};
- const [open, setOpen] = useState(false);
+ const pathToAccordion = [...pathToAccordionsState, holding?.id];
+ const [open, setOpen] = useHoldingsAccordionState({ instanceId, pathToAccordion });
const [openFirstTime, setOpenFirstTime] = useState(false);
const { totalRecords, isFetching } = useHoldingItemsQuery(holding.id, { searchParams, key: 'itemCount', tenantId });
const { data: locations } = useLocationsQuery({ tenantId });
@@ -121,12 +125,16 @@ HoldingAccordion.propTypes = {
holding: PropTypes.object.isRequired,
onViewHolding: PropTypes.func.isRequired,
onAddItem: PropTypes.func.isRequired,
+ instanceId: PropTypes.string.isRequired,
holdings: PropTypes.arrayOf(PropTypes.object),
withMoveDropdown: PropTypes.bool,
children: PropTypes.object,
tenantId: PropTypes.string,
+ pathToAccordionsState: PropTypes.arrayOf(PropTypes.string),
showViewHoldingsButton: PropTypes.bool,
showAddItemButton: PropTypes.bool,
};
+HoldingAccordion.defaultProps = { pathToAccordionsState: [] };
+
export default HoldingAccordion;
diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js
index fef613db5..a8e6bead9 100644
--- a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js
+++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js
@@ -39,6 +39,8 @@ const HoldingAccordionSetup = () => (
onViewHolding={noop}
onAddItem={noop}
withMoveDropdown={false}
+ instanceId="instanceId"
+ pathToAccordionsState={['holdings']}
>
<>>
diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.js b/src/Instance/HoldingsList/Holding/HoldingContainer.js
index ae7a4ced9..0764001ab 100644
--- a/src/Instance/HoldingsList/Holding/HoldingContainer.js
+++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js
@@ -41,6 +41,8 @@ const DraggableHolding = ({
showViewHoldingsButton,
showAddItemButton,
isBarcodeAsHotlink,
+ instanceId,
+ pathToAccordionsState,
...rest
}) => {
const rowStyles = useMemo(() => (
@@ -76,6 +78,8 @@ const DraggableHolding = ({
showViewHoldingsButton={showViewHoldingsButton}
showAddItemButton={showAddItemButton}
isBarcodeAsHotlink={isBarcodeAsHotlink}
+ instanceId={instanceId}
+ pathToAccordionsState={pathToAccordionsState}
/>
)
}
@@ -91,6 +95,7 @@ DraggableHolding.propTypes = {
draggingHoldingsCount: PropTypes.number,
provided: PropTypes.object.isRequired,
snapshot: PropTypes.object.isRequired,
+ instanceId: PropTypes.string.isRequired,
holding: PropTypes.object,
onViewHolding: PropTypes.func,
onAddItem: PropTypes.func,
@@ -98,8 +103,11 @@ DraggableHolding.propTypes = {
showViewHoldingsButton: PropTypes.bool,
showAddItemButton: PropTypes.bool,
isBarcodeAsHotlink: PropTypes.bool,
+ pathToAccordionsState: PropTypes.arrayOf(PropTypes.string),
};
+DraggableHolding.defaultProps = { pathToAccordionsState: [] };
+
const HoldingContainer = ({
location,
history,
@@ -112,6 +120,7 @@ const HoldingContainer = ({
holdingIndex,
draggingHoldingsCount,
tenantId,
+ pathToAccordionsState,
...rest
}) => {
const stripes = useStripes();
@@ -156,6 +165,8 @@ const HoldingContainer = ({
showViewHoldingsButton={showViewHoldingsButton}
showAddItemButton={showAddItemButton}
isBarcodeAsHotlink={isBarcodeAsHotlink}
+ instanceId={instance?.id}
+ pathToAccordionsState={pathToAccordionsState}
{...rest}
/>
)}
@@ -170,6 +181,8 @@ const HoldingContainer = ({
showViewHoldingsButton={showViewHoldingsButton}
showAddItemButton={showAddItemButton}
isBarcodeAsHotlink={isBarcodeAsHotlink}
+ instanceId={instance?.id}
+ pathToAccordionsState={pathToAccordionsState}
/>
);
};
@@ -188,6 +201,9 @@ HoldingContainer.propTypes = {
showViewHoldingsButton: PropTypes.bool,
showAddItemButton: PropTypes.bool,
isBarcodeAsHotlink: PropTypes.bool,
+ pathToAccordionsState: PropTypes.arrayOf(PropTypes.string),
};
+HoldingContainer.defaultProps = { pathToAccordionsState: [] };
+
export default withRouter(HoldingContainer);
diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js
index 2a307ce06..f8036f4d5 100644
--- a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js
+++ b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js
@@ -56,6 +56,7 @@ const renderHoldingContainer = (props = {}) => renderWithIntl(
onAddItem={jest.fn()}
showViewHoldingsButton
showAddItemButton
+ pathToAccordionsState={['holdings']}
{...props}
/>
diff --git a/src/Instance/HoldingsList/HoldingsList.js b/src/Instance/HoldingsList/HoldingsList.js
index 233329569..1f5c7bbb6 100644
--- a/src/Instance/HoldingsList/HoldingsList.js
+++ b/src/Instance/HoldingsList/HoldingsList.js
@@ -10,6 +10,7 @@ const HoldingsList = ({
showViewHoldingsButton,
showAddItemButton,
isBarcodeAsHotlink,
+ pathToAccordionsState,
draggable,
droppable,
}) => holdings.map(holding => (
@@ -24,6 +25,7 @@ const HoldingsList = ({
showViewHoldingsButton={showViewHoldingsButton}
showAddItemButton={showAddItemButton}
isBarcodeAsHotlink={isBarcodeAsHotlink}
+ pathToAccordionsState={pathToAccordionsState}
/>
));
@@ -34,12 +36,14 @@ HoldingsList.propTypes = {
showViewHoldingsButton: PropTypes.bool,
showAddItemButton: PropTypes.bool,
isBarcodeAsHotlink: PropTypes.bool,
+ pathToAccordionsState: PropTypes.arrayOf(PropTypes.string),
draggable: PropTypes.bool,
droppable: PropTypes.bool,
};
HoldingsList.defaultProps = {
holdings: [],
+ pathToAccordionsState: [],
};
export default HoldingsList;
diff --git a/src/Instance/HoldingsList/HoldingsListContainer.js b/src/Instance/HoldingsList/HoldingsListContainer.js
index 72baed8a4..d5fbbee03 100644
--- a/src/Instance/HoldingsList/HoldingsListContainer.js
+++ b/src/Instance/HoldingsList/HoldingsListContainer.js
@@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useStripes } from '@folio/stripes/core';
-import {
- Loading,
-} from '@folio/stripes/components';
+import { Loading } from '@folio/stripes/components';
import HoldingsList from './HoldingsList';
import { HoldingsListMovement } from '../InstanceMovement/HoldingMovementList';
@@ -14,6 +12,7 @@ const HoldingsListContainer = ({
instance,
isHoldingsMove,
tenantId,
+ pathToAccordionsState,
...rest
}) => {
const stripes = useStripes();
@@ -35,6 +34,7 @@ const HoldingsListContainer = ({
showViewHoldingsButton={canViewHoldings}
showAddItemButton={canCreateItem}
isBarcodeAsHotlink={canViewItems}
+ pathToAccordionsState={pathToAccordionsState}
/>
) : (
)
);
@@ -54,6 +55,9 @@ HoldingsListContainer.propTypes = {
instance: PropTypes.object.isRequired,
isHoldingsMove: PropTypes.bool,
tenantId: PropTypes.string,
+ pathToAccordionsState: PropTypes.arrayOf(PropTypes.string),
};
+HoldingsListContainer.defaultProps = { pathToAccordionsState: [] };
+
export default HoldingsListContainer;
diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js
index 2e22607b8..fabf873f5 100644
--- a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js
+++ b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js
@@ -1,4 +1,8 @@
-import React, { useContext } from 'react';
+import React, {
+ useContext,
+ useEffect,
+ useRef,
+} from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
@@ -13,28 +17,45 @@ import {
import { MemberTenantHoldings } from '../MemberTenantHoldings';
import { DataContext } from '../../../contexts';
-import { useSearchForShadowInstanceTenants } from '../../../hooks';
+import {
+ useHoldingsAccordionState,
+ useSearchForShadowInstanceTenants,
+} from '../../../hooks';
const ConsortialHoldings = ({
instance,
userTenantPermissions,
}) => {
const stripes = useStripes();
+ const instanceId = instance?.id;
+ const prevInstanceId = useRef(instanceId);
+
const { consortiaTenantsById } = useContext(DataContext);
- const { tenants } = useSearchForShadowInstanceTenants({ instanceId: instance?.id });
+ const { tenants } = useSearchForShadowInstanceTenants({ instanceId });
const memberTenants = tenants
.map(tenant => consortiaTenantsById[tenant.id])
.filter(tenant => !tenant?.isCentral && (tenant?.id !== stripes.okapi.tenant))
.sort((a, b) => a.name.localeCompare(b.name));
+ const pathToAccordion = ['consortialHoldings', '_state'];
+ const [isConsortialAccOpen, setConsortialAccOpen] = useHoldingsAccordionState({ instanceId, pathToAccordion });
+
+ useEffect(() => {
+ if (instanceId !== prevInstanceId.current) {
+ setConsortialAccOpen(false);
+ prevInstanceId.current = instanceId;
+ }
+ }, [instanceId]);
+
return (
}
- closedByDefault
+ open={isConsortialAccOpen}
+ onToggle={() => setConsortialAccOpen(prevState => !prevState)}
>
{!memberTenants.length
?
@@ -42,7 +63,7 @@ const ConsortialHoldings = ({
{memberTenants.map(memberTenant => (
{
const isInstanceShared = Boolean(isShared || isInstanceShadowCopy(instance?.source));
@@ -204,7 +206,7 @@ const InstanceDetails = forwardRef(({
/>
)}
- {instance?.shared && (
+ {isConsortialHoldingsVisible && (
jest
jest.mock('../InstanceDetails/SubInstanceGroup/SubInstanceGroup', () => jest.fn().mockReturnValue('SubInstanceGroup'));
jest.mock('../InstanceDetails/InstanceAcquisition/InstanceAcquisition', () => jest.fn().mockReturnValue('InstanceAcquisition'));
jest.mock('../InstanceDetails/InstanceTitleData/InstanceTitleData', () => jest.fn().mockReturnValue('InstanceTitleData'));
+jest.mock('./ConsortialHoldings', () => ({
+ ...jest.requireActual('./ConsortialHoldings'),
+ ConsortialHoldings: () => ,
+}));
const instance = {
title: 'Test Title',
+ source: 'FOLIO',
contributors: [],
identifiers: [],
instanceTypeId: '1234',
@@ -30,6 +35,7 @@ const instance = {
notes: [],
staffSuppress: false,
discoverySuppress: false,
+ shared: false,
};
const userTenantPermissions = [{
@@ -53,24 +59,31 @@ const queryClient = new QueryClient();
const actionMenu = jest.fn();
const onClose = jest.fn();
const tagsEnabled = true;
+
+const renderInstanceDetails = (props) => {
+ const component = (
+
+
+
+
+
+
+
+ );
+
+ return renderWithIntl(component, translationsProperties);
+};
+
describe('InstanceDetails', () => {
it('renders the InstanceDetails component', () => {
- renderWithIntl(
-
-
-
- ,
-
-
- ,
- translationsProperties
- );
+ renderInstanceDetails();
+
expect(screen.getByText('InstanceTitle')).toBeInTheDocument();
expect(screen.getByText('Add holdings')).toBeInTheDocument();
expect(screen.getByText('Administrative data')).toBeInTheDocument();
@@ -109,22 +122,7 @@ describe('InstanceDetails', () => {
...instance,
staffSuppress: true,
};
- renderWithIntl(
-
-
-
- ,
-
-
- ,
- translationsProperties
- );
+ renderInstanceDetails({ instance: staffSuppressedInstance });
expect(screen.getByText('Warning: Instance is marked staff suppressed')).toBeInTheDocument();
expect(screen.getByText('Staff suppressed')).toBeInTheDocument();
@@ -135,22 +133,7 @@ describe('InstanceDetails', () => {
...instance,
discoverySuppress: true,
};
- renderWithIntl(
-
-
-
- ,
-
-
- ,
- translationsProperties
- );
+ renderInstanceDetails({ instance: discoverySuppressedInstance });
expect(screen.getByText('Warning: Instance is marked suppressed from discovery')).toBeInTheDocument();
expect(screen.getByText('Suppressed from discovery')).toBeInTheDocument();
@@ -161,50 +144,21 @@ describe('InstanceDetails', () => {
staffSuppress: true,
discoverySuppress: true,
};
- renderWithIntl(
-
-
-
- ,
-
-
- ,
- translationsProperties
- );
+ renderInstanceDetails({ instance: bothSuppressedInstance });
expect(screen.getByText('Warning: Instance is marked suppressed from discovery and staff suppressed')).toBeInTheDocument();
});
it('expands and collapses the accordion sections', () => {
- renderWithIntl(
-
-
-
- ,
-
-
- ,
- translationsProperties
- );
+ renderInstanceDetails();
const expandAllButtons = screen.getByText('Expand all');
const firstAccordionSection = screen.getByRole('button', { name: /Administrative data/i });
const secondAccordionSection = screen.getByRole('button', { name: /Instance notes/i });
const thirdAccordionSection = screen.getByRole('button', { name: /Electronic access/i });
const fourthAccordionSection = screen.getByRole('button', { name: /Classification/i });
- expect(firstAccordionSection.getAttribute('aria-expanded')).toBe('false');
+ // Administrative data is open because it has initial data inside it
+ expect(firstAccordionSection.getAttribute('aria-expanded')).toBe('true');
expect(secondAccordionSection.getAttribute('aria-expanded')).toBe('false');
expect(thirdAccordionSection.getAttribute('aria-expanded')).toBe('false');
expect(fourthAccordionSection.getAttribute('aria-expanded')).toBe('false');
@@ -222,24 +176,39 @@ describe('InstanceDetails', () => {
});
it('renders tags button if tagsEnabled is true', () => {
- renderWithIntl(
-
-
-
- ,
-
-
- ,
- translationsProperties
- );
+ renderInstanceDetails();
+
const button = screen.getAllByRole('button', { id: 'clickable-show-tags' });
fireEvent.click(button[1]);
expect(button[1]).toBeEnabled();
});
+
+ describe('Consortial holdings accordion', () => {
+ it('should be visible for shared instances', () => {
+ const sharedInstance = {
+ ...instance,
+ shared: true,
+ };
+ renderInstanceDetails({ instance: sharedInstance });
+
+ expect(screen.getByRole('button', { name: 'Consortial holdings' })).toBeInTheDocument();
+ });
+
+ it('should be visible for shadow instances', () => {
+ const shadowInstance = {
+ ...instance,
+ shared: false,
+ source: 'CONSORTIUM-FOLIO',
+ };
+ renderInstanceDetails({ instance: shadowInstance });
+
+ expect(screen.getByRole('button', { name: 'Consortial holdings' })).toBeInTheDocument();
+ });
+
+ it('should not be visible for local instances', () => {
+ renderInstanceDetails();
+
+ expect(screen.queryByRole('button', { name: 'Consortial holdings' })).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js
index 45bb6c1dd..0df1daf07 100644
--- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js
+++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js
@@ -17,6 +17,7 @@ import { MoveItemsContext } from '../../MoveItemsContext';
import { useInstanceHoldingsQuery } from '../../../providers';
import { hasMemberTenantPermission } from '../../../utils';
+import { useHoldingsAccordionState } from '../../../hooks';
import css from './MemberTenantHoldings.css';
@@ -29,7 +30,13 @@ const MemberTenantHoldings = ({
name,
id,
} = memberTenant;
+ const instanceId = instance?.id;
const stripes = useStripes();
+
+ const pathToAccordion = ['consortialHoldings', id, '_state'];
+ const pathToHoldingsAccordion = ['consortialHoldings', id];
+ const [isMemberTenantAccOpen, setMemberTenantAccOpen] = useHoldingsAccordionState({ instanceId, pathToAccordion });
+
const { holdingsRecords, isLoading } = useInstanceHoldingsQuery(instance?.id, { tenantId: id });
const isUserInCentralTenant = checkIfUserInCentralTenant(stripes);
@@ -45,7 +52,8 @@ const MemberTenantHoldings = ({
className={css.memberTenantHoldings}
id={`${name}-holdings`}
label={name}
- closedByDefault
+ open={isMemberTenantAccOpen}
+ onToggle={() => setMemberTenantAccOpen(prevValue => !prevValue)}
>
{isLoading
@@ -61,6 +69,7 @@ const MemberTenantHoldings = ({
showViewHoldingsButton={canViewHoldings}
showAddItemButton={canCreateItem}
isBarcodeAsHotlink={canViewItems}
+ pathToAccordionsState={pathToHoldingsAccordion}
/>
)}
diff --git a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js
index 8941f41e9..009c66090 100644
--- a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js
+++ b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js
@@ -26,6 +26,7 @@ const HoldingsListMovement = ({
showViewHoldingsButton,
showAddItemButton,
isBarcodeAsHotlink,
+ pathToAccordionsState,
}) => {
const {
selectItemsForDrag,
@@ -64,6 +65,7 @@ const HoldingsListMovement = ({
showViewHoldingsButton={showViewHoldingsButton}
showAddItemButton={showAddItemButton}
isBarcodeAsHotlink={isBarcodeAsHotlink}
+ pathToAccordionsState={pathToAccordionsState}
/>
))
) : (
@@ -88,10 +90,12 @@ HoldingsListMovement.propTypes = {
draggable: PropTypes.bool,
droppable: PropTypes.bool,
tenantId: PropTypes.string,
+ pathToAccordionsState: PropTypes.arrayOf(PropTypes.string),
};
HoldingsListMovement.defaultProps = {
holdings: [],
+ pathToAccordionsState: [],
};
export default HoldingsListMovement;
diff --git a/src/ViewInstance.js b/src/ViewInstance.js
index 0da9d92ce..3aa636308 100644
--- a/src/ViewInstance.js
+++ b/src/ViewInstance.js
@@ -919,6 +919,7 @@ class ViewInstance extends React.Component {
},
];
const isInstanceLoading = this.state.isLoading || !instance || isCentralTenantPermissionsLoading;
+ const keyInStorageToHoldingsAccsState = ['holdings'];
return (
@@ -947,6 +948,7 @@ class ViewInstance extends React.Component {
instance={instance}
draggable={this.state.isItemsMovement}
tenantId={okapi.tenant}
+ pathToAccordionsState={keyInStorageToHoldingsAccsState}
droppable
/>
diff --git a/src/hooks/index.js b/src/hooks/index.js
index 8d61406b9..51310d19e 100644
--- a/src/hooks/index.js
+++ b/src/hooks/index.js
@@ -3,6 +3,7 @@ export { default as useBrowseValidation } from './useBrowseValidation';
export { default as useCallout } from './useCallout';
export { default as useHoldingItemsQuery } from './useHoldingItemsQuery';
export { default as useHoldingMutation } from './useHoldingMutation';
+export { default as useHoldingsAccordionState } from './useHoldingsAccordionState';
export { default as useInstanceMutation } from './useInstanceMutation';
export { default as useHoldingsQueryByHrids } from './useHoldingsQueryByHrids';
export { default as useInventoryBrowse } from './useInventoryBrowse';
diff --git a/src/hooks/useHoldingsAccordionState/index.js b/src/hooks/useHoldingsAccordionState/index.js
new file mode 100644
index 000000000..83af4d1a1
--- /dev/null
+++ b/src/hooks/useHoldingsAccordionState/index.js
@@ -0,0 +1 @@
+export { default } from './useHoldingsAccordionState';
diff --git a/src/hooks/useHoldingsAccordionState/useHoldingsAccordionState.js b/src/hooks/useHoldingsAccordionState/useHoldingsAccordionState.js
new file mode 100644
index 000000000..86cad1abb
--- /dev/null
+++ b/src/hooks/useHoldingsAccordionState/useHoldingsAccordionState.js
@@ -0,0 +1,42 @@
+import {
+ useEffect,
+ useState,
+} from 'react';
+import {
+ cloneDeep,
+ get,
+ set,
+} from 'lodash';
+
+import { useNamespace } from '@folio/stripes/core';
+
+import {
+ getItem,
+ setItem,
+} from '../../storage';
+
+const useHoldingsAccordionState = ({ instanceId, pathToAccordion = [] }) => {
+ const [namespace] = useNamespace();
+ const key = `${namespace}.instanceHoldingsAccordionsState`;
+
+ const instanceHoldingsAccordionsState = getItem(key) ?? {};
+ const currentAccState = get(instanceHoldingsAccordionsState, [instanceId, ...pathToAccordion], false);
+
+ const [isOpen, setIsOpen] = useState(currentAccState);
+
+ useEffect(() => {
+ let newState = {
+ [instanceId]: {
+ ...cloneDeep(instanceHoldingsAccordionsState[instanceId]),
+ }
+ };
+
+ newState = set(newState, [instanceId, ...pathToAccordion], isOpen);
+
+ setItem(key, newState);
+ }, [instanceHoldingsAccordionsState, isOpen, instanceId]);
+
+ return [isOpen, setIsOpen];
+};
+
+export default useHoldingsAccordionState;
diff --git a/src/hooks/useHoldingsAccordionState/useHoldingsAccordionState.test.js b/src/hooks/useHoldingsAccordionState/useHoldingsAccordionState.test.js
new file mode 100644
index 000000000..8e60b2ffa
--- /dev/null
+++ b/src/hooks/useHoldingsAccordionState/useHoldingsAccordionState.test.js
@@ -0,0 +1,43 @@
+import {
+ renderHook,
+ waitFor,
+ act,
+} from '@folio/jest-config-stripes/testing-library/react';
+
+import '../../../test/jest/__mock__';
+
+import useHoldingsAccordionState from './useHoldingsAccordionState';
+
+import { getItem } from '../../storage';
+
+describe('useHoldingsAccordionState', () => {
+ it('should save initial holdings state in storage', () => {
+ const { result } = renderHook(() => useHoldingsAccordionState({
+ instanceId: 'instanceId',
+ pathToAccordion: ['holdings', '_self'],
+ }));
+
+ const expectedResult = { instanceId: { holdings: { _self: false } } };
+
+ expect(getItem('@folio/inventory.instanceHoldingsAccordionsState')).toEqual(expectedResult);
+ expect(result.current[0]).toEqual(false);
+ });
+
+ it('should set new state on accordion toggle', () => {
+ const { result } = renderHook(() => useHoldingsAccordionState({
+ instanceId: 'instanceId',
+ pathToAccordion: ['holdings', '_self'],
+ }));
+
+ const [isOpen, setOpen] = result.current;
+
+ act(() => {
+ setOpen(true);
+ });
+
+ const expectedResult = { instanceId: { holdings: { _self: true } } };
+
+ waitFor(() => expect(isOpen).toBeTruthy());
+ waitFor(() => expect(getItem('@folio/inventory.instanceHoldingsAccordionsState')).toEqual(expectedResult));
+ });
+});