diff --git a/src/Item/ItemVersionHistory/ItemVersionHistory.js b/src/Item/ItemVersionHistory/ItemVersionHistory.js
index 11e3cd47e..4d1d78bb2 100644
--- a/src/Item/ItemVersionHistory/ItemVersionHistory.js
+++ b/src/Item/ItemVersionHistory/ItemVersionHistory.js
@@ -1,10 +1,13 @@
-import { useContext } from 'react';
+import { useContext, useState } from 'react';
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';
-import { AuditLogPane } from '@folio/stripes-acq-components';
+import { AuditLogPane } from '@folio/stripes/components';
-import { useItemAuditDataQuery } from '../../hooks';
+import {
+ useItemAuditDataQuery,
+ useVersionHistory,
+} from '../../hooks';
import { DataContext } from '../../contexts';
import { getDateWithTime } from '../../utils';
@@ -42,7 +45,19 @@ const ItemVersionHistory = ({
const { formatMessage } = useIntl();
const referenceData = useContext(DataContext);
- const { data, isLoading } = useItemAuditDataQuery(itemId);
+ const [lastVersionEventTs, setLastVersionEventTs] = useState(null);
+
+ const {
+ data,
+ totalRecords,
+ isLoading,
+ } = useItemAuditDataQuery(itemId, lastVersionEventTs);
+
+ const {
+ actionsMap,
+ isLoadedMoreVisible,
+ versionsToDisplay,
+ } = useVersionHistory(data, totalRecords);
const fieldLabelsMap = {
discoverySuppress: formatMessage({ id: 'ui-inventory.discoverySuppress' }),
@@ -91,13 +106,20 @@ const ItemVersionHistory = ({
const fieldFormatter = createFieldFormatter(referenceData, circulationHistory);
+ const handleLoadMore = lastEventTs => {
+ setLastVersionEventTs(lastEventTs);
+ };
+
return (
);
};
diff --git a/src/hooks/index.js b/src/hooks/index.js
index d3eab25e8..173cf265f 100644
--- a/src/hooks/index.js
+++ b/src/hooks/index.js
@@ -15,6 +15,7 @@ export { default as useClassificationBrowseConfig } from './useClassificationBro
export { default as useUpdateOwnership } from './useUpdateOwnership';
export { default as useLocalStorageItems } from './useLocalStorageItems';
export { default as useItemAuditDataQuery } from './useItemAuditDataQuery';
+export { default as useVersionHistory } from './useVersionHistory';
export * from './useQuickExport';
export * from '@folio/stripes-inventory-components/lib/queries/useInstanceDateTypes';
export * from './useCallNumberTypesQuery';
diff --git a/src/hooks/useItemAuditDataQuery/useItemAuditDataQuery.js b/src/hooks/useItemAuditDataQuery/useItemAuditDataQuery.js
index 3163baf1f..1d9d627d4 100644
--- a/src/hooks/useItemAuditDataQuery/useItemAuditDataQuery.js
+++ b/src/hooks/useItemAuditDataQuery/useItemAuditDataQuery.js
@@ -5,18 +5,24 @@ import {
useOkapiKy,
} from '@folio/stripes/core';
-const useItemAuditDataQuery = (itemId) => {
+const useItemAuditDataQuery = (itemId, eventTs) => {
const ky = useOkapiKy();
const [namespace] = useNamespace({ key: 'item-audit-data' });
+ // eventTs param is used to load more data
const { isLoading, data = {} } = useQuery({
- queryKey: [namespace, itemId],
- queryFn: () => ky.get(`audit-data/inventory/item/${itemId}`).json(),
+ queryKey: [namespace, itemId, eventTs],
+ queryFn: () => ky.get(`audit-data/inventory/item/${itemId}`, {
+ searchParams: {
+ ...(eventTs && { eventTs })
+ }
+ }).json(),
enabled: Boolean(itemId),
});
return {
data: data?.inventoryAuditItems || [],
+ totalRecords: data?.totalRecords,
isLoading,
};
};
diff --git a/src/hooks/useVersionHistory/getActionLabel.js b/src/hooks/useVersionHistory/getActionLabel.js
new file mode 100644
index 000000000..c91228afb
--- /dev/null
+++ b/src/hooks/useVersionHistory/getActionLabel.js
@@ -0,0 +1,12 @@
+/**
+ * Gets translated change type label
+ * @param {function} formatMessage
+ * @returns {{ADDED, MODIFIED, REMOVED}}
+ */
+export const getActionLabel = formatMessage => {
+ return {
+ ADDED: formatMessage({ id: 'stripes-acq-components.audit-log.action.added' }),
+ MODIFIED: formatMessage({ id: 'stripes-acq-components.audit-log.action.edited' }),
+ REMOVED: formatMessage({ id: 'stripes-acq-components.audit-log.action.removed' }),
+ };
+};
diff --git a/src/hooks/useVersionHistory/getActionLabel.test.js b/src/hooks/useVersionHistory/getActionLabel.test.js
new file mode 100644
index 000000000..444a7c5ac
--- /dev/null
+++ b/src/hooks/useVersionHistory/getActionLabel.test.js
@@ -0,0 +1,15 @@
+import { getActionLabel } from './getActionLabel';
+
+const intl = { formatMessage: ({ id }) => id };
+
+describe('getActionLabel', () => {
+ it('should return correct action labels', () => {
+ const labels = {
+ ADDED: 'stripes-acq-components.audit-log.action.added',
+ MODIFIED: 'stripes-acq-components.audit-log.action.edited',
+ REMOVED: 'stripes-acq-components.audit-log.action.removed',
+ };
+
+ expect(getActionLabel(intl.formatMessage)).toEqual(labels);
+ });
+});
diff --git a/src/hooks/useVersionHistory/getChangedFieldsList.js b/src/hooks/useVersionHistory/getChangedFieldsList.js
new file mode 100644
index 000000000..d6c33b7f9
--- /dev/null
+++ b/src/hooks/useVersionHistory/getChangedFieldsList.js
@@ -0,0 +1,28 @@
+import { sortBy } from 'lodash';
+
+/**
+ * Merge fieldChanges and collectionChanges into a list of changed fields and sort by changeType
+ * @param {Object} diff
+ * @param {Array} diff.fieldChanges
+ * @param {Array} diff.collectionChanges
+ * @returns {Array.<{fieldName: String, changeType: String, newValue: any, oldValue: any}>}
+ */
+export const getChangedFieldsList = diff => {
+ const fieldChanges = diff.fieldChanges ? diff.fieldChanges.map(field => ({
+ fieldName: field.fieldName,
+ changeType: field.changeType,
+ newValue: field.newValue,
+ oldValue: field.oldValue,
+ })) : [];
+
+ const collectionChanges = diff.collectionChanges ? diff.collectionChanges.flatMap(collection => {
+ return collection.itemChanges.map(field => ({
+ fieldName: collection.collectionName,
+ changeType: field.changeType,
+ newValue: field.newValue,
+ oldValue: field.oldValue,
+ }));
+ }) : [];
+
+ return sortBy([...fieldChanges, ...collectionChanges], data => data.changeType);
+};
diff --git a/src/hooks/useVersionHistory/index.js b/src/hooks/useVersionHistory/index.js
new file mode 100644
index 000000000..af5a0400f
--- /dev/null
+++ b/src/hooks/useVersionHistory/index.js
@@ -0,0 +1 @@
+export { default } from './useVersionHistory';
diff --git a/src/hooks/useVersionHistory/useVersionHistory.js b/src/hooks/useVersionHistory/useVersionHistory.js
new file mode 100644
index 000000000..4ba1eb1da
--- /dev/null
+++ b/src/hooks/useVersionHistory/useVersionHistory.js
@@ -0,0 +1,104 @@
+import {
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
+import { useIntl } from 'react-intl';
+import { Link } from 'react-router-dom';
+import {
+ keyBy,
+ uniq,
+} from 'lodash';
+
+import {
+ formatDateTime,
+ useUsersBatch,
+} from '@folio/stripes-acq-components';
+
+import { getChangedFieldsList } from './getChangedFieldsList';
+import { getActionLabel } from './getActionLabel';
+
+const useVersionHistory = (data, totalRecords) => {
+ const intl = useIntl();
+ const anonymousUserLabel = intl.formatMessage({ id: 'stripes-components.versionHistory.anonymousUser' });
+
+ const [versions, setVersions] = useState([]);
+ const [usersId, setUsersId] = useState([]);
+ const [usersMap, setUsersMap] = useState({});
+ const [isLoadedMoreVisible, setIsLoadedMoreVisible] = useState(true);
+
+ const { users } = useUsersBatch(usersId);
+
+ // cleanup when component unmounts
+ useEffect(() => () => {
+ setVersions([]);
+ setUsersMap({});
+ }, []);
+
+ // update usersId when data changes
+ useEffect(() => {
+ if (!data?.length) return;
+
+ const newUsersId = uniq(data.map(version => version.userId));
+
+ setUsersId(newUsersId);
+ }, [data]);
+
+ // update usersMap when new users are fetched
+ useEffect(() => {
+ if (!users?.length) return;
+
+ setUsersMap(prevState => ({
+ ...prevState,
+ ...keyBy(users, 'id'),
+ }));
+ }, [users]);
+
+ useEffect(() => {
+ if (!data?.length) return;
+
+ setVersions(prevState => [...prevState, ...data]);
+ }, [data]);
+
+ useEffect(() => {
+ setIsLoadedMoreVisible(versions.length < totalRecords);
+ }, [versions]);
+
+ const versionsToDisplay = useMemo(
+ () => {
+ const getUserName = userId => {
+ const user = usersMap[userId];
+
+ return user ? `${user.personal.lastName}, ${user.personal.firstName}` : null;
+ };
+ const getSourceLink = userId => {
+ return userId ? {getUserName(userId)} : anonymousUserLabel;
+ };
+
+ const transformDiffToVersions = diffArray => {
+ return diffArray
+ .filter(({ action }) => action !== 'CREATE')
+ .map(({ eventDate, eventTs, userId, eventId, diff }) => ({
+ eventDate: formatDateTime(eventDate, intl),
+ source: getSourceLink(userId),
+ userName: getUserName(userId) || anonymousUserLabel,
+ fieldChanges: diff ? getChangedFieldsList(diff) : [],
+ eventId,
+ eventTs,
+ }));
+ };
+
+ return transformDiffToVersions(versions);
+ }, [versions, usersMap],
+ );
+
+ const actionsMap = { ...getActionLabel(intl.formatMessage) };
+
+ return {
+ actionsMap,
+ isLoadedMoreVisible,
+ versionsToDisplay,
+ };
+};
+
+export default useVersionHistory;