diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf5c453c..80b5050b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## (6.1.0 IN PROGRESS)
* Add more reusable hooks and utilities. Refs UISACQCOMP-228.
+* Move reusable version history components to the ACQ lib. Refs UISACQCOMP-230.
## (6.0.1 IN PROGRESS)
diff --git a/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js
new file mode 100644
index 00000000..5c7ad0d9
--- /dev/null
+++ b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.js
@@ -0,0 +1,40 @@
+import PropTypes from 'prop-types';
+import { useContext, useMemo } from 'react';
+
+import { Checkbox } from '@folio/stripes/components';
+
+import { VersionViewContext } from '../../VersionViewContext';
+
+export const VersionCheckbox = ({
+ checked,
+ label,
+ name,
+ ...props
+}) => {
+ const versionContext = useContext(VersionViewContext);
+ const isUpdated = useMemo(() => (
+ versionContext?.paths?.includes(name)
+ ), [name, versionContext?.paths]);
+
+ const checkboxLabel = isUpdated ? {label} : label;
+
+ return (
+
+ );
+};
+
+VersionCheckbox.defaultProps = {
+ checked: false,
+};
+
+VersionCheckbox.propTypes = {
+ checked: PropTypes.bool,
+ label: PropTypes.node.isRequired,
+ name: PropTypes.string.isRequired,
+};
diff --git a/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js
new file mode 100644
index 00000000..4a1fbbe0
--- /dev/null
+++ b/lib/VersionHistory/components/VersionCheckbox/VersionCheckbox.test.js
@@ -0,0 +1,37 @@
+import {
+ render,
+ screen,
+} from '@testing-library/react';
+
+import { VersionViewContext } from '../../VersionViewContext';
+import { VersionCheckbox } from './VersionCheckbox';
+
+const defaultProps = {
+ label: 'Test Label',
+ name: 'testName',
+};
+
+const renderVersionCheckbox = (props = {}, contextValue = {}) => {
+ return render(
+
+
+ ,
+ );
+};
+
+describe('VersionCheckbox', () => {
+ it('renders with marked label when name is in context paths', () => {
+ renderVersionCheckbox({}, { paths: ['testName'] });
+
+ expect(screen.getByText('Test Label').closest('mark')).toBeInTheDocument();
+ });
+
+ it('renders with normal label when name is not in context paths', () => {
+ renderVersionCheckbox({}, { paths: ['otherName'] });
+
+ expect(screen.getByText('Test Label').closest('mark')).not.toBeInTheDocument();
+ });
+});
diff --git a/lib/VersionHistory/components/VersionCheckbox/index.js b/lib/VersionHistory/components/VersionCheckbox/index.js
new file mode 100644
index 00000000..6b54ebbc
--- /dev/null
+++ b/lib/VersionHistory/components/VersionCheckbox/index.js
@@ -0,0 +1 @@
+export { VersionCheckbox } from './VersionCheckbox';
diff --git a/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js
new file mode 100644
index 00000000..fa340550
--- /dev/null
+++ b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.js
@@ -0,0 +1,49 @@
+import PropTypes from 'prop-types';
+import {
+ useContext,
+ useMemo,
+} from 'react';
+
+import {
+ KeyValue,
+ NoValue,
+} from '@folio/stripes/components';
+
+import { VersionViewContext } from '../../VersionViewContext';
+
+export const VersionKeyValue = ({
+ children,
+ label,
+ multiple,
+ name,
+ value,
+}) => {
+ const versionContext = useContext(VersionViewContext);
+ const isUpdated = useMemo(() => (
+ multiple
+ ? versionContext?.paths?.find((field) => new RegExp(`^${name}\\[\\d\\]$`).test(field))
+ : versionContext?.paths?.includes(name)
+ ), [multiple, name, versionContext?.paths]);
+
+ const content = (children || value) || ;
+ const displayValue = isUpdated ? {content} : content;
+
+ return (
+
+ );
+};
+
+VersionKeyValue.defaultProps = {
+ multiple: false,
+};
+
+VersionKeyValue.propTypes = {
+ children: PropTypes.node,
+ label: PropTypes.node.isRequired,
+ multiple: PropTypes.bool,
+ name: PropTypes.string.isRequired,
+ value: PropTypes.node,
+};
diff --git a/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.test.js b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.test.js
new file mode 100644
index 00000000..f20a184a
--- /dev/null
+++ b/lib/VersionHistory/components/VersionKeyValue/VersionKeyValue.test.js
@@ -0,0 +1,64 @@
+import {
+ render,
+ screen,
+} from '@testing-library/react';
+
+import { VersionViewContext } from '../../VersionViewContext';
+import { VersionKeyValue } from './VersionKeyValue';
+
+const defaultProps = {
+ label: 'Test Label',
+ value: 'Test Value',
+ name: 'testName',
+};
+
+const renderComponent = (props = {}, contextValue = {}) => {
+ return render(
+
+
+ ,
+ );
+};
+
+describe('VersionKeyValue', () => {
+ it('should render label and value', () => {
+ renderComponent();
+
+ expect(screen.getByText('Test Label')).toBeInTheDocument();
+ expect(screen.getByText('Test Value')).toBeInTheDocument();
+ });
+
+ it('should render NoValue when value is not provided', () => {
+ renderComponent({ value: undefined });
+
+ expect(screen.getByText('Test Label')).toBeInTheDocument();
+ expect(screen.getByText('stripes-components.noValue.noValueSet')).toBeInTheDocument();
+ });
+
+ it('should highlight updated value', () => {
+ renderComponent({ name: 'testName' }, { paths: ['testName'] });
+
+ expect(screen.getByText('Test Value').closest('mark')).toBeInTheDocument();
+ });
+
+ it('should not highlight non-updated value', () => {
+ renderComponent({}, { paths: ['anotherName'] });
+
+ expect(screen.getByText('Test Value').closest('mark')).not.toBeInTheDocument();
+ });
+
+ it('should highlight updated value for multiple fields', () => {
+ renderComponent({ multiple: true }, { paths: ['testName[0]'] });
+
+ expect(screen.getByText('Test Value').closest('mark')).toBeInTheDocument();
+ });
+
+ it('should not highlight non-updated value for multiple fields', () => {
+ renderComponent({ multiple: true }, { paths: ['anotherName[0]'] });
+
+ expect(screen.getByText('Test Value').closest('mark')).not.toBeInTheDocument();
+ });
+});
diff --git a/lib/VersionHistory/components/VersionKeyValue/index.js b/lib/VersionHistory/components/VersionKeyValue/index.js
new file mode 100644
index 00000000..a27963d2
--- /dev/null
+++ b/lib/VersionHistory/components/VersionKeyValue/index.js
@@ -0,0 +1 @@
+export { VersionKeyValue } from './VersionKeyValue';
diff --git a/lib/VersionHistory/components/VersionView/VersionView.js b/lib/VersionHistory/components/VersionView/VersionView.js
new file mode 100644
index 00000000..c8b58a4a
--- /dev/null
+++ b/lib/VersionHistory/components/VersionView/VersionView.js
@@ -0,0 +1,76 @@
+import PropTypes from 'prop-types';
+import {
+ memo,
+ useMemo,
+} from 'react';
+import { FormattedMessage } from 'react-intl';
+
+import {
+ Layout,
+ LoadingPane,
+ Pane,
+ PaneMenu,
+} from '@folio/stripes/components';
+
+import { TagsBadge } from '../../../Tags';
+import { VersionHistoryButton } from '../../VersionHistoryButton';
+
+const VersionView = ({
+ children,
+ id,
+ isLoading,
+ onClose,
+ tags,
+ versionId,
+ ...props
+}) => {
+ const isVersionExist = Boolean(versionId && !isLoading);
+
+ const lastMenu = useMemo(() => (
+
+ {tags && (
+
+ )}
+
+
+ ), [tags]);
+
+ if (isLoading) return ;
+
+ return (
+
+ {
+ isVersionExist
+ ? children
+ : (
+
+
+
+ )
+ }
+
+ );
+};
+
+VersionView.propTypes = {
+ children: PropTypes.node.isRequired,
+ id: PropTypes.string.isRequired,
+ isLoading: PropTypes.bool,
+ onClose: PropTypes.func,
+ tags: PropTypes.arrayOf(PropTypes.object),
+ versionId: PropTypes.string,
+};
+
+export default memo(VersionView);
diff --git a/lib/VersionHistory/components/VersionView/VersionView.test.js b/lib/VersionHistory/components/VersionView/VersionView.test.js
new file mode 100644
index 00000000..422b0596
--- /dev/null
+++ b/lib/VersionHistory/components/VersionView/VersionView.test.js
@@ -0,0 +1,52 @@
+import {
+ render,
+ screen,
+} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import VersionView from './VersionView';
+
+const defaultProps = {
+ children:
Version Content
,
+ id: 'test-id',
+ isLoading: false,
+ onClose: jest.fn(),
+ tags: [{ id: 'tag1' }, { id: 'tag2' }],
+ versionId: 'version1',
+ dismissible: true,
+};
+
+const renderComponent = (props = {}) => render(
+ ,
+);
+
+describe('VersionView', () => {
+ it('should render loading pane when isLoading is true', () => {
+ renderComponent({ isLoading: true });
+
+ expect(screen.queryByText('Version Content')).not.toBeInTheDocument();
+ });
+
+ it('should render children when version exists and is not loading', () => {
+ renderComponent();
+
+ expect(screen.getByText('Version Content')).toBeInTheDocument();
+ });
+
+ it('should render no version message when version does not exist', () => {
+ renderComponent({ versionId: null });
+
+ expect(screen.getByText('stripes-acq-components.versionHistory.noVersion')).toBeInTheDocument();
+ });
+
+ it('should call onClose when Pane onClose is triggered', async () => {
+ renderComponent();
+
+ await userEvent.click(screen.getByRole('button', { name: 'stripes-components.closeItem' }));
+
+ expect(defaultProps.onClose).toHaveBeenCalled();
+ });
+});
diff --git a/lib/VersionHistory/components/VersionView/index.js b/lib/VersionHistory/components/VersionView/index.js
new file mode 100644
index 00000000..77c4a41e
--- /dev/null
+++ b/lib/VersionHistory/components/VersionView/index.js
@@ -0,0 +1 @@
+export { default as VersionView } from './VersionView';
diff --git a/lib/VersionHistory/components/index.js b/lib/VersionHistory/components/index.js
new file mode 100644
index 00000000..6c8b0a4f
--- /dev/null
+++ b/lib/VersionHistory/components/index.js
@@ -0,0 +1,3 @@
+export { VersionCheckbox } from './VersionCheckbox';
+export { VersionKeyValue } from './VersionKeyValue';
+export { VersionView } from './VersionView';
diff --git a/lib/VersionHistory/index.js b/lib/VersionHistory/index.js
index 099a7b0d..97242923 100644
--- a/lib/VersionHistory/index.js
+++ b/lib/VersionHistory/index.js
@@ -1,3 +1,4 @@
+export * from './components';
export { getFieldLabels } from './getFieldLabels';
export { getHighlightedFields } from './getHighlightedFields';
export * from './hooks';
diff --git a/lib/constants/api.js b/lib/constants/api.js
index 6a4dfbf3..ef5dbb98 100644
--- a/lib/constants/api.js
+++ b/lib/constants/api.js
@@ -1,6 +1,7 @@
export const ACQUISITION_METHODS_API = 'orders/acquisition-methods';
export const ACQUISITIONS_UNITS_API = 'acquisitions-units/units';
export const ACQUISITIONS_UNIT_MEMBERSHIPS_API = 'acquisitions-units/memberships';
+export const AUDIT_ACQ_EVENTS_API = 'audit-data/acquisition';
export const BATCH_GROUPS_API = 'batch-groups';
export const BUDGETS_API = 'finance/budgets';
export const CAMPUSES_API = 'location-units/campuses';
diff --git a/lib/hooks/useLineHoldings/useLineHoldings.js b/lib/hooks/useLineHoldings/useLineHoldings.js
index cd79b33f..3b01813e 100644
--- a/lib/hooks/useLineHoldings/useLineHoldings.js
+++ b/lib/hooks/useLineHoldings/useLineHoldings.js
@@ -11,9 +11,9 @@ export const useLineHoldings = (holdingIds) => {
const query = useQuery(
[namespace, holdingIds],
- () => {
+ ({ signal }) => {
return batchRequest(
- ({ params: searchParams }) => ky.get(HOLDINGS_API, { searchParams }).json(),
+ ({ params: searchParams }) => ky.get(HOLDINGS_API, { searchParams, signal }).json(),
holdingIds,
);
},
diff --git a/lib/hooks/useOrganization/useOrganization.js b/lib/hooks/useOrganization/useOrganization.js
index 4e1f4b17..8d629b2a 100644
--- a/lib/hooks/useOrganization/useOrganization.js
+++ b/lib/hooks/useOrganization/useOrganization.js
@@ -23,7 +23,7 @@ export const useOrganization = (id, options = {}) => {
isLoading,
} = useQuery({
queryKey: [namespace, id, tenantId],
- queryFn: () => ky.get(`${VENDORS_API}/${id}`).json(),
+ queryFn: ({ signal }) => ky.get(`${VENDORS_API}/${id}`, { signal }).json(),
enabled: enabled && Boolean(id),
...queryOptions,
});
diff --git a/lib/hooks/useOrganization/useOrganization.test.js b/lib/hooks/useOrganization/useOrganization.test.js
index a3a381f1..5c91bbb7 100644
--- a/lib/hooks/useOrganization/useOrganization.test.js
+++ b/lib/hooks/useOrganization/useOrganization.test.js
@@ -11,8 +11,6 @@ import { VENDORS_API } from '../../constants';
import { useOrganization } from './useOrganization';
const queryClient = new QueryClient();
-
-// eslint-disable-next-line react/prop-types
const wrapper = ({ children }) => (
{children}
@@ -42,6 +40,6 @@ describe('useOrganization', () => {
await waitFor(() => !result.current.isLoading);
expect(result.current.organization).toEqual(organization);
- expect(mockGet).toHaveBeenCalledWith(`${VENDORS_API}/${organization.id}`);
+ expect(mockGet).toHaveBeenCalledWith(`${VENDORS_API}/${organization.id}`, expect.any(Object));
});
});
diff --git a/lib/hooks/useUsersBatch/useUsersBatch.js b/lib/hooks/useUsersBatch/useUsersBatch.js
index b4650009..256ef2c6 100644
--- a/lib/hooks/useUsersBatch/useUsersBatch.js
+++ b/lib/hooks/useUsersBatch/useUsersBatch.js
@@ -25,9 +25,9 @@ export const useUsersBatch = (userIds, options = {}) => {
isLoading,
} = useQuery(
[namespace, userIds],
- async () => {
+ async ({ signal }) => {
const response = await batchRequest(
- ({ params: searchParams }) => ky.get(USERS_API, { searchParams }).json(),
+ ({ params: searchParams }) => ky.get(USERS_API, { searchParams, signal }).json(),
userIds,
);
diff --git a/lib/hooks/useUsersBatch/useUsersBatch.test.js b/lib/hooks/useUsersBatch/useUsersBatch.test.js
index 97b71787..68eb8e16 100644
--- a/lib/hooks/useUsersBatch/useUsersBatch.test.js
+++ b/lib/hooks/useUsersBatch/useUsersBatch.test.js
@@ -10,8 +10,6 @@ import { USERS_API } from '../../constants';
import { useUsersBatch } from './useUsersBatch';
const queryClient = new QueryClient();
-
-// eslint-disable-next-line react/prop-types
const wrapper = ({ children }) => (
{children}
@@ -45,6 +43,7 @@ describe('useUsersBatch', () => {
searchParams: expect.objectContaining({
query: userIds.map(id => `id==${id}`).join(' or '),
}),
+ signal: expect.any(AbortSignal),
});
});
});
diff --git a/translations/stripes-acq-components/en.json b/translations/stripes-acq-components/en.json
index c642972f..3e357d86 100644
--- a/translations/stripes-acq-components/en.json
+++ b/translations/stripes-acq-components/en.json
@@ -221,6 +221,7 @@
"versionHistory.card.version.current": "Current version",
"versionHistory.card.version.original": "Original version",
"versionHistory.deletedRecord": "Record deleted",
+ "versionHistory.noVersion": "No version history to display.",
"versionHistory.pane.header": "Version history",
"versionHistory.pane.sub": "{count, plural, one {# version} other {# versions}}",