diff --git a/CHANGELOG.md b/CHANGELOG.md index e841e4d8a..b764412a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ * Provide an instance `tenantId` to the PO line form when creating an order from the instance. Refs UIIN-2614. * Bump @folio/stripes-acq-components dependency version to 5.0.0. Refs UIIN-2620. * ECS: Check when sharing instance with source=MARC is complete before re-fetching it. Refs UIIN-2605. +* Instance 3rd pane: Adjust behavior when returning to instance from holdings/item full screen. Refs UIIN-2453. ## [9.4.12](https://github.com/folio-org/ui-inventory/tree/v9.4.12) (2023-09-21) [Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.11...v9.4.12) diff --git a/src/Instance/HoldingsList/Holding/Holding.js b/src/Instance/HoldingsList/Holding/Holding.js index b04382bb0..5e33963ac 100644 --- a/src/Instance/HoldingsList/Holding/Holding.js +++ b/src/Instance/HoldingsList/Holding/Holding.js @@ -20,6 +20,8 @@ const Holding = ({ isDraggable, isItemsDroppable, tenantId, + instanceId, + pathToAccordionsState, }) => { return (
@@ -49,6 +51,8 @@ const Holding = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + instanceId={instanceId} + pathToAccordionsState={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 }); @@ -113,10 +119,12 @@ 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), }; export default HoldingAccordion; diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js index 45df04f4c..b8aa058f9 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js @@ -37,6 +37,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 93f77f883..baf078aff 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.js @@ -36,6 +36,8 @@ const DraggableHolding = ({ onViewHolding, onAddItem, tenantId, + instanceId, + pathToAccordionsState, ...rest }) => { const rowStyles = useMemo(() => ( @@ -68,6 +70,8 @@ const DraggableHolding = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + instanceId={instanceId} + pathToAccordionsState={pathToAccordionsState} /> ) } @@ -87,6 +91,8 @@ DraggableHolding.propTypes = { onViewHolding: PropTypes.func, onAddItem: PropTypes.func, tenantId: PropTypes.string, + instanceId: PropTypes.string, + pathToAccordionsState: PropTypes.arrayOf(PropTypes.string), }; const HoldingContainer = ({ @@ -99,6 +105,7 @@ const HoldingContainer = ({ holdingIndex, draggingHoldingsCount, tenantId, + pathToAccordionsState, ...rest }) => { const onViewHolding = useCallback(() => { @@ -130,6 +137,8 @@ const HoldingContainer = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + instanceId={instance?.id} + pathToAccordionsState={pathToAccordionsState} {...rest} /> )} @@ -141,6 +150,8 @@ const HoldingContainer = ({ onViewHolding={onViewHolding} onAddItem={onAddItem} tenantId={tenantId} + instanceId={instance?.id} + pathToAccordionsState={pathToAccordionsState} /> ); }; @@ -157,6 +168,7 @@ HoldingContainer.propTypes = { isDraggable: PropTypes.bool, draggingHoldingsCount: PropTypes.number, tenantId: PropTypes.string, + pathToAccordionsState: PropTypes.arrayOf(PropTypes.string), }; export default withRouter(HoldingContainer); diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js index 1cb2c6129..d053d5e37 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js @@ -54,6 +54,7 @@ const renderHoldingContainer = (props = {}) => renderWithIntl( provided={{ draggableProps: { style: true } }} onViewHolding={jest.fn()} onAddItem={jest.fn()} + pathToAccordionsState={['holdings']} {...props} /> diff --git a/src/Instance/HoldingsList/HoldingsList.js b/src/Instance/HoldingsList/HoldingsList.js index 97c01fff5..5ab203c56 100644 --- a/src/Instance/HoldingsList/HoldingsList.js +++ b/src/Instance/HoldingsList/HoldingsList.js @@ -7,6 +7,7 @@ const HoldingsList = ({ instance, holdings, tenantId, + pathToAccordionsState, draggable, droppable, @@ -19,6 +20,7 @@ const HoldingsList = ({ droppable={droppable} holdings={holdings} tenantId={tenantId} + pathToAccordionsState={pathToAccordionsState} /> )); @@ -26,6 +28,7 @@ HoldingsList.propTypes = { instance: PropTypes.object.isRequired, holdings: PropTypes.arrayOf(PropTypes.object), tenantId: PropTypes.string, + pathToAccordionsState: PropTypes.arrayOf(PropTypes.string), draggable: PropTypes.bool, droppable: PropTypes.bool, diff --git a/src/Instance/HoldingsList/HoldingsListContainer.js b/src/Instance/HoldingsList/HoldingsListContainer.js index be19ea8ee..dcc578d2c 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.js @@ -13,6 +13,7 @@ const HoldingsListContainer = ({ instance, isHoldingsMove, tenantId, + pathToAccordionsState, ...rest }) => { const { holdingsRecords: holdings, isLoading } = useInstanceHoldingsQuery(instance.id, { tenantId }); @@ -26,6 +27,7 @@ const HoldingsListContainer = ({ holdings={holdings} instance={instance} tenantId={tenantId} + pathToAccordionsState={pathToAccordionsState} /> ) : ( ) ); @@ -42,6 +45,7 @@ HoldingsListContainer.propTypes = { instance: PropTypes.object.isRequired, isHoldingsMove: PropTypes.bool, tenantId: PropTypes.string, + pathToAccordionsState: PropTypes.arrayOf(PropTypes.string), }; export default HoldingsListContainer; diff --git a/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js b/src/Instance/InstanceDetails/ConsortialHoldings/ConsortialHoldings.js index d3623b753..aae864206 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,25 +17,42 @@ import { import { MemberTenantHoldings } from '../MemberTenantHoldings'; import { DataContext } from '../../../contexts'; -import { useSearchForShadowInstanceTenants } from '../../../hooks'; +import { + useHoldingsAccordionState, + useSearchForShadowInstanceTenants, +} from '../../../hooks'; const ConsortialHoldings = ({ instance }) => { + const instanceId = instance?.id; + const prevInstanceId = useRef(instanceId); + const stripes = useStripes(); 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 ? @@ -39,7 +60,7 @@ const ConsortialHoldings = ({ instance }) => { {memberTenants.map(memberTenant => ( diff --git a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js index 0ada3eff2..47fc6aa2f 100644 --- a/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js +++ b/src/Instance/InstanceDetails/MemberTenantHoldings/MemberTenantHoldings.js @@ -16,6 +16,7 @@ import { InstanceNewHolding } from '../InstanceNewHolding'; import { MoveItemsContext } from '../../MoveItemsContext'; import { useInstanceHoldingsQuery } from '../../../providers'; +import { useHoldingsAccordionState } from '../../../hooks'; import css from './MemberTenantHoldings.css'; @@ -27,7 +28,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); @@ -38,7 +45,8 @@ const MemberTenantHoldings = ({ className={css.memberTenantHoldings} id={`${name}-holdings`} label={name} - closedByDefault + open={isMemberTenantAccOpen} + onToggle={() => setMemberTenantAccOpen(prevValue => !prevValue)} >
{isLoading @@ -51,6 +59,7 @@ const MemberTenantHoldings = ({ tenantId={id} draggable={false} droppable={false} + pathToAccordionsState={pathToHoldingsAccordion} /> )} diff --git a/src/Instance/InstanceDetails/utils.js b/src/Instance/InstanceDetails/utils.js index 3b8082097..b97ad2555 100644 --- a/src/Instance/InstanceDetails/utils.js +++ b/src/Instance/InstanceDetails/utils.js @@ -48,13 +48,13 @@ export const getAccordionState = (instance = {}, accordions = {}) => { return ({ [accordions.administrative]: !areAllFieldsEmpty(values(instanceData)), [accordions.title]: !areAllFieldsEmpty(values(titleData)), - [accordions.identifiers]: !areAllFieldsEmpty([instance.identifiers ?? []]), - [accordions.contributors]: !areAllFieldsEmpty([instance.contributors ?? []]), + [accordions.identifiers]: !areAllFieldsEmpty([instance?.identifiers ?? []]), + [accordions.contributors]: !areAllFieldsEmpty([instance?.contributors ?? []]), [accordions.descriptiveData]: !areAllFieldsEmpty(values(descriptiveData)), [accordions.notes]: !areAllFieldsEmpty([instance?.notes ?? []]), - [accordions.electronicAccess]: !areAllFieldsEmpty([instance.electronicAccess ?? []]), - [accordions.subjects]: !areAllFieldsEmpty([instance.subjects ?? []]), - [accordions.classifications]: !areAllFieldsEmpty([instance.classifications ?? []]), + [accordions.electronicAccess]: !areAllFieldsEmpty([instance?.electronicAccess ?? []]), + [accordions.subjects]: !areAllFieldsEmpty([instance?.subjects ?? []]), + [accordions.classifications]: !areAllFieldsEmpty([instance?.classifications ?? []]), [accordions.relationship]: !areAllFieldsEmpty(values(instanceRelationship)), }); }; diff --git a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js index 5c0788920..b582a23fb 100644 --- a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js +++ b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.js @@ -23,6 +23,7 @@ const HoldingsListMovement = ({ draggable, droppable, tenantId, + pathToAccordionsState, }) => { const { selectItemsForDrag, @@ -58,6 +59,7 @@ const HoldingsListMovement = ({ holdingIndex={index} draggingHoldingsCount={draggingHoldingsCount} tenantId={tenantId} + pathToAccordionsState={pathToAccordionsState} /> )) ) : ( @@ -80,6 +82,7 @@ HoldingsListMovement.propTypes = { draggable: PropTypes.bool, droppable: PropTypes.bool, tenantId: PropTypes.string, + pathToAccordionsState: PropTypes.arrayOf(PropTypes.string), }; HoldingsListMovement.defaultProps = { diff --git a/src/ViewInstance.js b/src/ViewInstance.js index 676336d2c..f085b903e 100644 --- a/src/ViewInstance.js +++ b/src/ViewInstance.js @@ -860,6 +860,7 @@ class ViewInstance extends React.Component { }, ]; const isInstanceLoading = this.state.isLoading || !instance || isCentralTenantPermissionsLoading; + const keyInStorageToHoldingsAccsState = ['holdings']; return ( @@ -887,6 +888,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..e4fd30aae --- /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, setOpen] = useState(currentAccState); + + useEffect(() => { + let newState = { + [instanceId]: { + ...cloneDeep(instanceHoldingsAccordionsState[instanceId]), + } + }; + + newState = set(newState, [instanceId, ...pathToAccordion], isOpen); + + setItem(key, newState); + }, [instanceHoldingsAccordionsState, isOpen, instanceId]); + + return [isOpen, setOpen]; +}; + +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)); + }); +});