diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60577005..bc14612d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
# Change history for ui-organizations
-## 5.3.0 (IN PROGRESS)
+## 6.0.0 (IN PROGRESS)
+
+* *BREAKING* Display all versions in change log in fourth pane. Refs UIORGS-355.
## [5.2.0](https://github.com/folio-org/ui-organizations/tree/v5.2.0) (2024-10-31)
[Full Changelog](https://github.com/folio-org/ui-organizations/compare/v5.1.1...v5.2.0)
diff --git a/package.json b/package.json
index 3bb204a8..9ca265e0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@folio/organizations",
- "version": "5.2.0",
+ "version": "6.0.0",
"description": "Organizations",
"main": "index.js",
"repository": "folio-org/ui-organizations",
@@ -19,6 +19,7 @@
"home": "/organizations",
"okapiInterfaces": {
"acquisition-methods": "1.0",
+ "acquisition-organization-events": "1.0",
"banking-information": "1.0",
"data-export-spring": "1.0 2.0",
"configuration": "2.0",
@@ -66,6 +67,7 @@
"replaces": ["ui-organizations.third-party-services"],
"visible": false,
"subPermissions": [
+ "acquisition.organization.events.get",
"acquisitions-units.memberships.collection.get",
"acquisitions-units.units.collection.get",
"configuration.entries.collection.get",
diff --git a/src/Organizations/OrganizationDetails/OrganizationDetails.js b/src/Organizations/OrganizationDetails/OrganizationDetails.js
index d0990cf8..fca690ff 100644
--- a/src/Organizations/OrganizationDetails/OrganizationDetails.js
+++ b/src/Organizations/OrganizationDetails/OrganizationDetails.js
@@ -39,10 +39,12 @@ import {
TagsPane,
useAcqRestrictions,
useModalToggle,
+ VersionHistoryButton,
} from '@folio/stripes-acq-components';
import {
NOTES_ROUTE,
+ ORGANIZATION_VERSIONS_VIEW_ROUTE,
ORGANIZATIONS_ROUTE,
} from '../../common/constants';
import {
@@ -135,6 +137,13 @@ const OrganizationDetails = ({
if (isDetailsPaneInFocus) paneTitleRef.current.focus();
}, [isDetailsPaneInFocus]);
+ const openVersionHistory = useCallback(() => {
+ history.push({
+ pathname: ORGANIZATION_VERSIONS_VIEW_ROUTE.replace(':id', organization.id),
+ search: location.search,
+ });
+ }, [history, location.search, organization.id]);
+
const getActionMenu = useCallback(
({ onToggle }) => {
return (
@@ -200,6 +209,7 @@ const OrganizationDetails = ({
tagsQuantity={get(organization, 'tags.tagList', []).length}
tagsToggle={toggleTagsPane}
/>
+
);
diff --git a/src/Organizations/OrganizationVersion/OrganizationVersion.js b/src/Organizations/OrganizationVersion/OrganizationVersion.js
new file mode 100644
index 00000000..2f52d21f
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/OrganizationVersion.js
@@ -0,0 +1,111 @@
+import get from 'lodash/get';
+import {
+ memo,
+ useCallback,
+ useMemo,
+} from 'react';
+import ReactRouterPropTypes from 'react-router-prop-types';
+
+import {
+ useOrganization,
+ VersionHistoryPane,
+ VersionView,
+ VersionViewContextProvider,
+} from '@folio/stripes-acq-components';
+import { TitleManager } from '@folio/stripes/core';
+
+import {
+ ORGANIZATION_VERSIONS_VIEW_ROUTE,
+ ORGANIZATIONS_ROUTE,
+ VIEW_ORG_DETAILS,
+} from '../../common/constants';
+import { HIDDEN_FIELDS_FOR_ORGANIZATION_VERSION_HISTORY } from '../constants';
+import { getOrganizationFieldsLabelMap } from './getOrganizationFieldsLabelMap';
+import { useOrganizationVersions } from './hooks';
+
+const OrganizationVersion = ({
+ history,
+ location,
+ match,
+}) => {
+ const { id: organizationId, versionId } = match.params;
+ const snapshotPath = 'organizationSnapshot.map';
+
+ const {
+ isLoading: isOrganizationLoading,
+ organization,
+ } = useOrganization(organizationId);
+
+ const onHistoryClose = useCallback(() => history.push({
+ pathname: `${VIEW_ORG_DETAILS}${organizationId}`,
+ search: location.search,
+ }), [history, location.search, organizationId]);
+
+ const onVersionClose = useCallback(() => history.push({
+ pathname: ORGANIZATIONS_ROUTE,
+ search: location.search,
+ }), [history, location.search]);
+
+ const onSelectVersion = useCallback((_versionId) => {
+ history.push({
+ pathname: `${ORGANIZATION_VERSIONS_VIEW_ROUTE.replace(':id', organizationId)}/${_versionId}`,
+ search: location.search,
+ });
+ }, [history, location.search, organizationId]);
+
+ const {
+ versions,
+ isLoading: isHistoryLoading,
+ } = useOrganizationVersions(organizationId, {
+ onSuccess: ({ organizationAuditEvents }) => {
+ if (!versionId && organizationAuditEvents[0]?.id) onSelectVersion(organizationAuditEvents[0].id);
+ },
+ });
+
+ const isVersionLoading = (
+ isOrganizationLoading || isHistoryLoading
+ );
+
+ const labelsMap = useMemo(() => getOrganizationFieldsLabelMap(), []);
+
+ return (
+
+
+
+ {/* TODO: https://folio-org.atlassian.net/browse/UIORGS-356 */}
+
+
+
+
+ );
+};
+
+OrganizationVersion.propTypes = {
+ history: ReactRouterPropTypes.history,
+ location: ReactRouterPropTypes.location,
+ match: ReactRouterPropTypes.match,
+};
+
+export default memo(OrganizationVersion);
diff --git a/src/Organizations/OrganizationVersion/OrganizationVersion.test.js b/src/Organizations/OrganizationVersion/OrganizationVersion.test.js
new file mode 100644
index 00000000..2e18d322
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/OrganizationVersion.test.js
@@ -0,0 +1,141 @@
+import {
+ QueryClient,
+ QueryClientProvider,
+} from 'react-query';
+import {
+ MemoryRouter,
+ Route,
+ Switch,
+ withRouter,
+} from 'react-router-dom';
+
+import {
+ render,
+ screen,
+} from '@folio/jest-config-stripes/testing-library/react';
+import user from '@folio/jest-config-stripes/testing-library/user-event';
+import { useOkapiKy } from '@folio/stripes/core';
+import {
+ AUDIT_ACQ_EVENTS_API,
+ useOrganization,
+} from '@folio/stripes-acq-components';
+
+import {
+ organization,
+ organizationAuditEvent,
+} from 'fixtures';
+import { ORGANIZATION_VERSIONS_VIEW_ROUTE } from '../../common/constants';
+import OrganizationVersion from './OrganizationVersion';
+
+jest.mock('@folio/stripes-acq-components', () => ({
+ ...jest.requireActual('@folio/stripes-acq-components'),
+ useOrganization: jest.fn(() => ({})),
+}));
+
+const { organizationSnapshot, ...auditEvent } = organizationAuditEvent;
+
+const latestSnapshot = {
+ ...organizationSnapshot,
+ edition: 'Second edition',
+};
+const originalSnapshot = { ...organizationSnapshot };
+
+const versions = [
+ {
+ ...auditEvent,
+ id: 'testAuditEventId',
+ organizationSnapshot: { map: latestSnapshot },
+ },
+ {
+ ...auditEvent,
+ organizationSnapshot: { map: originalSnapshot },
+ },
+];
+
+const kyMock = {
+ get: jest.fn((url) => ({
+ json: async () => {
+ const result = {};
+
+ if (url.startsWith(`${AUDIT_ACQ_EVENTS_API}/organization/`)) {
+ result.organizationAuditEvents = versions;
+ }
+
+ return Promise.resolve({
+ isLoading: false,
+ ...result,
+ });
+ },
+ })),
+};
+
+const queryClient = new QueryClient();
+const wrapper = ({ children }) => (
+
+
+ {children}
+
+
+);
+
+const Component = withRouter(OrganizationVersion);
+const mockDefaultContent = 'Hello world';
+
+const renderOrganizationVersion = (props = {}) => render(
+
+ (
+
+ )}
+ />
+ (
+ {mockDefaultContent}
+ )}
+ />
+ ,
+ { wrapper },
+);
+
+describe('OrganizationVersion', () => {
+ beforeEach(() => {
+ useOkapiKy.mockReturnValue(kyMock);
+ useOrganization.mockReturnValue({
+ isLoading: false,
+ organization,
+ });
+ });
+
+ it('should close version view when \'Version close\' button was clicked', async () => {
+ renderOrganizationVersion();
+
+ await screen.findAllByRole('button', { name: 'stripes-acq-components.versionHistory.card.select.tooltip' })
+ .then(async ([selectVersionBtn]) => user.click(selectVersionBtn));
+
+ await screen.findAllByRole('button', { name: 'stripes-components.closeItem' })
+ .then(async ([closeVersionBtn]) => user.click(closeVersionBtn));
+
+ expect(screen.queryByText(organization.name)).not.toBeInTheDocument();
+ expect(screen.getByText(mockDefaultContent)).toBeInTheDocument();
+ });
+
+ it('should close version view when \'History close\' button was clicked', async () => {
+ renderOrganizationVersion();
+
+ await screen.findAllByRole('button', { name: 'stripes-acq-components.versionHistory.card.select.tooltip' })
+ .then(async ([selectVersionBtn]) => user.click(selectVersionBtn));
+
+ await screen.findAllByRole('button', { name: 'stripes-components.closeItem' })
+ .then(async ([_, closeHistoryBtn]) => user.click(closeHistoryBtn));
+
+ expect(screen.queryByText(organization.name)).not.toBeInTheDocument();
+ expect(screen.getByText(mockDefaultContent)).toBeInTheDocument();
+ });
+});
diff --git a/src/Organizations/OrganizationVersion/getOrganizationFieldsLabelMap.js b/src/Organizations/OrganizationVersion/getOrganizationFieldsLabelMap.js
new file mode 100644
index 00000000..f2073cca
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/getOrganizationFieldsLabelMap.js
@@ -0,0 +1,152 @@
+export const getOrganizationFieldsLabelMap = () => {
+ return {
+ 'tags': 'stripes-acq-components.label.tags',
+ 'tags.tagList': 'stripes-acq-components.label.tags',
+ 'tags.tagList[\\d]': 'stripes-acq-components.label.tags',
+
+ 'metadata.createdByUserId': 'ui-organizations.metadata.createdByUserId',
+ 'metadata.createdDate': 'ui-organizations.metadata.createdDate',
+ 'metadata.updatedByUserId': 'ui-organizations.metadata.updatedByUserId',
+ 'metadata.updatedDate': 'ui-organizations.metadata.updatedDate',
+
+ 'name': 'ui-organizations.summary.name',
+ 'code': 'ui-organizations.summary.code',
+ 'description': 'ui-organizations.summary.description',
+ 'exportToAccounting': 'ui-organizations.vendorInfo.exportToAccounting',
+ 'status': 'ui-organizations.summary.organizationStatus',
+ 'organizationTypes': 'ui-organizations.summary.type',
+ 'organizationTypes[\\d]': 'ui-organizations.summary.type',
+ 'language': 'ui-organizations.summary.defaultLanguage',
+ 'aliases': 'ui-organizations.summary.alternativeNames',
+ 'aliases[\\d]': 'ui-organizations.summary.alternativeNames',
+ 'aliases[\\d].value': 'ui-organizations.search.aliases',
+ 'aliases[\\d].description': 'ui-organizations.versionHistory.field.alias.description',
+
+ 'addresses': 'ui-organizations.data.contactTypes.address',
+ 'addresses[\\d]': 'ui-organizations.data.contactTypes.address',
+ 'addresses[\\d].addressLine1': 'ui-organizations.contactPeople.addressLine1',
+ 'addresses[\\d].addressLine2': 'ui-organizations.contactPeople.addressLine2',
+ 'addresses[\\d].city': 'ui-organizations.contactPeople.city',
+ 'addresses[\\d].stateRegion': 'ui-organizations.contactPeople.stateRegion',
+ 'addresses[\\d].zipCode': 'ui-organizations.contactPeople.zipCode',
+ 'addresses[\\d].country': 'ui-organizations.contactPeople.country',
+ 'addresses[\\d].isPrimary': 'ui-organizations.primaryItem',
+ 'addresses[\\d].categories': 'ui-organizations.contactPeople.categories',
+ 'addresses[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories',
+ 'addresses[\\d].language': 'ui-organizations.contactPeople.language',
+ 'phoneNumbers': 'ui-organizations.contactPeople.phoneNumbers',
+ 'phoneNumbers[\\d]': 'ui-organizations.contactPeople.phoneNumbers',
+ 'phoneNumbers[\\d].phoneNumber': 'ui-organizations.contactPeople.phoneNumber',
+ 'phoneNumbers[\\d].categories': 'ui-organizations.contactPeople.categories',
+ 'phoneNumbers[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories',
+ 'phoneNumbers[\\d].type': 'ui-organizations.contactPeople.type',
+ 'phoneNumbers[\\d].isPrimary': 'ui-organizations.primaryItem',
+ 'phoneNumbers[\\d].language': 'ui-organizations.contactPeople.language',
+ 'emails': 'ui-organizations.contactPeople.emails',
+ 'emails[\\d]': 'ui-organizations.contactPeople.emails',
+ 'emails[\\d].value': 'ui-organizations.contactPeople.emailAddress',
+ 'emails[\\d].description': 'ui-organizations.contactPeople.description',
+ 'emails[\\d].isPrimary': 'ui-organizations.primaryItem',
+ 'emails[\\d].categories': 'ui-organizations.contactPeople.categories',
+ 'emails[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories',
+ 'emails[\\d].language': 'ui-organizations.contactPeople.language',
+ 'urls': 'ui-organizations.contactPeople.urls',
+ 'urls[\\d]': 'ui-organizations.contactPeople.urls',
+ 'urls[\\d].value': 'ui-organizations.contactPeople.url',
+ 'urls[\\d].description': 'ui-organizations.contactPeople.description',
+ 'urls[\\d].language': 'ui-organizations.contactPeople.language',
+ 'urls[\\d].isPrimary': 'ui-organizations.primaryItem',
+ 'urls[\\d].categories': 'ui-organizations.contactPeople.categories',
+ 'urls[\\d].categories[\\d]': 'ui-organizations.contactPeople.categories',
+
+ 'contacts': 'ui-organizations.contactPeople',
+ 'contacts[\\d]': 'ui-organizations.contactPeople',
+ 'privilegedContacts': 'ui-organizations.privilegedDonorInformation',
+ 'agreements': 'ui-organizations.linkedAgreements.section',
+ 'agreements[\\d]': 'ui-organizations.linkedAgreements.section',
+ 'agreements[\\d].name': 'ui-organizations.agreement.name',
+ 'agreements[\\d].discount': 'ui-organizations.agreement.discount',
+ 'agreements[\\d].referenceUrl': 'ui-organizations.agreement.referenceUrl',
+ 'agreements[\\d].notes': 'ui-organizations.agreement.notes',
+ 'erpCode': 'ui-organizations.summary.accountingCode',
+ 'paymentMethod': 'ui-organizations.accounts.paymentMethod',
+ 'accessProvider': 'ui-organizations.versionHistory.field.accessProvider',
+ 'governmental': 'ui-organizations.versionHistory.field.governmental',
+ 'licensor': 'ui-organizations.versionHistory.field.governmental',
+ 'materialSupplier': 'ui-organizations.versionHistory.field.materialSupplier',
+ 'vendorCurrencies': 'ui-organizations.vendorInfo.vendorCurrencies',
+ 'vendorCurrencies[\\d]': 'ui-organizations.vendorInfo.vendorCurrencies',
+ 'claimingInterval': 'ui-organizations.vendorInfo.claimingInterval',
+ 'discountPercent': 'ui-organizations.vendorInfo.discountPercent',
+ 'expectedActivationInterval': 'ui-organizations.vendorInfo.expectedActivationInterval',
+ 'expectedInvoiceInterval': 'ui-organizations.vendorInfo.expectedInvoiceInterval',
+ 'renewalActivationInterval': 'ui-organizations.vendorInfo.renewalActivationInterval',
+ 'subscriptionInterval': 'ui-organizations.vendorInfo.subscriptionInterval',
+ 'expectedReceiptInterval': 'ui-organizations.vendorInfo.expectedReceiptInterval',
+ 'taxId': 'ui-organizations.vendorInfo.taxID',
+ 'liableForVat': 'ui-organizations.vendorInfo.liableForVAT',
+ 'taxPercentage': 'ui-organizations.vendorInfo.taxPercentage',
+ 'interfaces': 'ui-organizations.interface',
+ 'interfaces[\\d]': 'ui-organizations.interface',
+
+ 'edi': 'ui-organizations.integration.edi',
+ 'edi.vendorEdiCode': 'ui-organizations.integration.edi.vendorEDICode',
+ 'edi.vendorEdiType': 'ui-organizations.integration.edi.vendorEDIType',
+ 'edi.libEdiCode': 'ui-organizations.integration.edi.libraryEDICode',
+ 'edi.libEdiType': 'ui-organizations.integration.edi.libraryEDIType',
+ 'edi.prorateTax': 'ui-organizations.versionHistory.field.edi.prorateTax',
+ 'edi.prorateFees': 'ui-organizations.versionHistory.field.edi.prorateFees',
+ 'edi.ediNamingConvention': 'ui-organizations.integration.edi.ediNamingConvention',
+ 'edi.sendAcctNum': 'integration.edi.sendAccountNumber',
+ 'edi.supportOrder': 'ui-organizations.integration.edi.orders',
+ 'edi.supportInvoice': 'ui-organizations.integration.edi.invoices',
+ 'edi.notes': 'ui-organizations.integration.edi.notes',
+ 'edi.ediFtp': 'ui-organizations.integration.ftp',
+ 'edi.ediFtp.ftpFormat': 'ui-organizations.integration.ftp.ftpFormat',
+ 'edi.ediFtp.serverAddress': 'ui-organizations.integration.ftp.serverAddress',
+ 'edi.ediFtp.username': 'ui-organizations.integration.ftp.username',
+ 'edi.ediFtp.password': 'ui-organizations.edit.password', // NOSONAR - it's a label mapping, not the actual password
+ 'edi.ediFtp.ftpMode': 'ui-organizations.integration.ftp.ftpMode',
+ 'edi.ediFtp.ftpConnMode': 'ui-organizations.integration.ftp.ftpConnectionMode',
+ 'edi.ediFtp.ftpPort': 'ui-organizations.integration.ftp.ftpPort',
+ 'edi.ediFtp.orderDirectory': 'ui-organizations.integration.ftp.orderDirectory',
+ 'edi.ediFtp.invoiceDirectory': 'ui-organizations.integration.ftp.invoiceDirectory',
+ 'edi.ediFtp.notes': 'ui-organizations.integration.ftp.notes',
+ 'edi.ediJob': 'ui-organizations.integration.scheduling',
+ 'edi.ediJob.scheduleEdi': 'ui-organizations.integration.scheduling.scheduleEDI',
+ 'edi.ediJob.schedulingDate': 'ui-organizations.integration.scheduling.scheduleDate',
+ 'edi.ediJob.time': 'ui-organizations.integration.scheduling.scheduleTime',
+ 'edi.ediJob.isMonday': 'ui-organizations.integration.scheduling.scheduleWeekdays.MONDAY',
+ 'edi.ediJob.isTuesday': 'ui-organizations.integration.scheduling.scheduleWeekdays.TUESDAY',
+ 'edi.ediJob.isWednesday': 'ui-organizations.integration.scheduling.scheduleWeekdays.WEDNESDAY',
+ 'edi.ediJob.isThursday': 'ui-organizations.integration.scheduling.scheduleWeekdays.THURSDAY',
+ 'edi.ediJob.isFriday': 'ui-organizations.integration.scheduling.scheduleWeekdays.FRIDAY',
+ 'edi.ediJob.isSaturday': 'ui-organizations.integration.scheduling.scheduleWeekdays.SATURDAY',
+ 'edi.ediJob.isSunday': 'ui-organizations.integration.scheduling.scheduleWeekdays.SUNDAY',
+ 'edi.ediJob.sendToEmails': 'ui-organizations.versionHistory.field.edi.ediJob.sendToEmails',
+ 'edi.ediJob.notifyAllEdi': 'ui-organizations.versionHistory.field.edi.ediJob.notifyAllEdi',
+ 'edi.ediJob.notifyInvoiceOnly': 'ui-organizations.versionHistory.field.edi.ediJob.notifyInvoiceOnly',
+ 'edi.ediJob.notifyErrorOnly': 'ui-organizations.versionHistory.field.edi.ediJob.notifyErrorOnly',
+ 'edi.ediJob.schedulingNotes': 'ui-organizations.versionHistory.field.edi.ediJob.schedulingNotes',
+
+ 'accounts': 'ui-organizations.accounts',
+ 'accounts.name': 'ui-organizations.accounts.name',
+ 'accounts.accountNo': 'ui-organizations.accounts.accountNumber',
+ 'accounts.description': 'ui-organizations.accounts.description',
+ 'accounts.appSystemNo': 'ui-organizations.accounts.payable',
+ 'accounts.paymentMethod': 'ui-organizations.accounts.paymentMethod',
+ 'accounts.accountStatus': 'ui-organizations.accounts.account.accountStatus',
+ 'accounts.contactInfo': 'ui-organizations.accounts.account.contactInfo',
+ 'accounts.libraryCode': 'ui-organizations.accounts.libraryCode',
+ 'accounts.libraryEdiCode': 'ui-organizations.accounts.libraryEDICode',
+ 'accounts.notes': 'ui-organizations.accounts.notes',
+ 'accounts.acqUnitIds': 'ui-organizations.versionHistory.field.accounts.acqUnitsIds',
+ 'accounts.acqUnitIds[\\d]': 'ui-organizations.versionHistory.field.accounts.acqUnitsIds',
+
+ 'isVendor': 'ui-organizations.summary.isVendor',
+ 'isDonor': 'ui-organizations.summary.isDonor',
+ 'sanCode': 'ui-organizations.versionHistory.field.sanCode',
+ 'acqUnitIds': 'stripes-acq-components.label.acqUnits',
+ 'acqUnitIds[\\d]': 'stripes-acq-components.label.acqUnits',
+ };
+};
diff --git a/src/Organizations/OrganizationVersion/hooks/index.js b/src/Organizations/OrganizationVersion/hooks/index.js
new file mode 100644
index 00000000..ecff7cfd
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/hooks/index.js
@@ -0,0 +1 @@
+export { useOrganizationVersions } from './useOrganizationVersions';
diff --git a/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/index.js b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/index.js
new file mode 100644
index 00000000..ecff7cfd
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/index.js
@@ -0,0 +1 @@
+export { useOrganizationVersions } from './useOrganizationVersions';
diff --git a/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.js b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.js
new file mode 100644
index 00000000..ba5d1ccc
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.js
@@ -0,0 +1,34 @@
+import { useQuery } from 'react-query';
+
+import {
+ useNamespace,
+ useOkapiKy,
+} from '@folio/stripes/core';
+
+import { AUDIT_ACQ_EVENTS_API } from '@folio/stripes-acq-components';
+
+const DEFAULT_DATA = [];
+
+export const useOrganizationVersions = (organizationId, options = {}) => {
+ const ky = useOkapiKy();
+ const [namespace] = useNamespace({ key: 'organization-versions' });
+
+ const searchParams = {
+ sortBy: 'event_date',
+ sortOrder: 'desc',
+ };
+
+ const { isLoading, data } = useQuery(
+ [namespace, organizationId],
+ ({ signal }) => ky.get(`${AUDIT_ACQ_EVENTS_API}/organization/${organizationId}`, { signal, searchParams }).json(),
+ {
+ enabled: Boolean(organizationId),
+ ...options,
+ },
+ );
+
+ return {
+ isLoading,
+ versions: data?.organizationAuditEvents || DEFAULT_DATA,
+ };
+};
diff --git a/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.test.js b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.test.js
new file mode 100644
index 00000000..06743fe7
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/hooks/useOrganizationVersions/useOrganizationVersions.test.js
@@ -0,0 +1,53 @@
+import {
+ QueryClient,
+ QueryClientProvider,
+} from 'react-query';
+
+import {
+ renderHook,
+ waitFor,
+} from '@folio/jest-config-stripes/testing-library/react';
+import { useOkapiKy } from '@folio/stripes/core';
+
+import { useOrganizationVersions } from './useOrganizationVersions';
+
+const queryClient = new QueryClient();
+const wrapper = ({ children }) => (
+
+ {children}
+
+);
+
+describe('useOrganizationVersions', () => {
+ const mockGet = jest.fn();
+
+ beforeEach(() => {
+ useOkapiKy.mockReturnValue({ get: mockGet });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return default data when organizationId is not provided', async () => {
+ const { result } = renderHook(() => useOrganizationVersions(), { wrapper });
+
+ await waitFor(() => expect(result.current.isLoading).toBeFalsy());
+
+ expect(result.current.versions).toEqual([]);
+ });
+
+ it('should fetch and return organization versions', async () => {
+ const mockData = { organizationAuditEvents: [{ id: '1', name: 'Version 1' }] };
+
+ mockGet.mockReturnValueOnce({
+ json: () => Promise.resolve(mockData),
+ });
+
+ const { result } = renderHook(() => useOrganizationVersions('org-1'), { wrapper });
+
+ await waitFor(() => expect(result.current.isLoading).toBeFalsy());
+
+ expect(result.current.versions).toEqual(mockData.organizationAuditEvents);
+ });
+});
diff --git a/src/Organizations/OrganizationVersion/index.js b/src/Organizations/OrganizationVersion/index.js
new file mode 100644
index 00000000..8a312a96
--- /dev/null
+++ b/src/Organizations/OrganizationVersion/index.js
@@ -0,0 +1 @@
+export { default as OrganizationVersion } from './OrganizationVersion';
diff --git a/src/Organizations/OrganizationsList/OrganizationsList.js b/src/Organizations/OrganizationsList/OrganizationsList.js
index d054d860..ce3135fa 100644
--- a/src/Organizations/OrganizationsList/OrganizationsList.js
+++ b/src/Organizations/OrganizationsList/OrganizationsList.js
@@ -44,10 +44,12 @@ import {
} from '@folio/plugin-find-organization';
import {
+ ORGANIZATION_VERSIONS_VIEW_ROUTE,
ORGANIZATIONS_ROUTE,
VIEW_ORG_DETAILS,
} from '../../common/constants';
import { OrganizationDetailsContainer } from '../OrganizationDetails';
+import { OrganizationVersion } from '../OrganizationVersion';
import OrganizationsListLastMenu from './OrganizationsListLastMenu';
const resultsPaneTitle = ;
@@ -251,9 +253,16 @@ const OrganizationsList = ({
+
+
);
diff --git a/src/Organizations/constants.js b/src/Organizations/constants.js
index 9f59e372..360b0828 100644
--- a/src/Organizations/constants.js
+++ b/src/Organizations/constants.js
@@ -46,3 +46,5 @@ export const MAP_FIELD_ACCORDION = {
};
export const BANKING_INFORMATION_FIELD_NAME = 'bankingInformation';
+
+export const HIDDEN_FIELDS_FOR_ORGANIZATION_VERSION_HISTORY = [];
diff --git a/src/common/constants/routes.js b/src/common/constants/routes.js
index 455f24f9..c7e40458 100644
--- a/src/common/constants/routes.js
+++ b/src/common/constants/routes.js
@@ -1,3 +1,4 @@
export const NOTES_ROUTE = '/organizations/notes';
export const ORGANIZATIONS_ROUTE = '/organizations';
export const VIEW_ORG_DETAILS = '/organizations/view/';
+export const ORGANIZATION_VERSIONS_VIEW_ROUTE = `${VIEW_ORG_DETAILS}:id/versions`;
diff --git a/test/jest/fixtures/index.js b/test/jest/fixtures/index.js
index 7a39fa0b..5a07a642 100644
--- a/test/jest/fixtures/index.js
+++ b/test/jest/fixtures/index.js
@@ -2,4 +2,5 @@ export * from './contact';
export * from './integrationConfig';
export * from './interface';
export * from './organization';
+export * from './organizationAuditEvent';
export * from './organizationTypes';
diff --git a/test/jest/fixtures/organizationAuditEvent.js b/test/jest/fixtures/organizationAuditEvent.js
new file mode 100644
index 00000000..b2ab1a53
--- /dev/null
+++ b/test/jest/fixtures/organizationAuditEvent.js
@@ -0,0 +1,14 @@
+import { organization } from './organization';
+
+export const organizationAuditEvent = {
+ id: '3635ff84-ede6-4786-95e7-00a4801115ba',
+ action: 'Edit',
+ organizationId: '6e5bb3d1-cba7-47a8-87c2-eb9f3b1598fc',
+ userId: 'dd88964f-22f2-5579-898f-86920b2c1d71',
+ eventDate: '2024-11-14T08:48:48.340+00:00',
+ actionDate: '2024-11-14T08:48:48.335+00:00',
+ organizationSnapshot: {
+ map: { ...organization },
+ empty: false,
+ },
+};
diff --git a/translations/ui-organizations/en.json b/translations/ui-organizations/en.json
index 383f537a..2181146c 100644
--- a/translations/ui-organizations/en.json
+++ b/translations/ui-organizations/en.json
@@ -439,6 +439,24 @@
"save.error.creds": "Error while saving credentials",
"save.error.assignInterface": "Error while assigning interface to the organization",
+ "versionHistory.field.metadata.createdByUserId": "Created by",
+ "versionHistory.field.metadata.createdDate": "Created date",
+ "versionHistory.field.metadata.updatedByUserId": "Updated by",
+ "versionHistory.field.metadata.updatedDate": "Updated date",
+ "versionHistory.field.alias.description": "Alias description",
+ "versionHistory.field.sanCode": "SAN Code",
+ "versionHistory.field.accounts.acqUnitsIds": "Account acquisition units",
+ "versionHistory.field.accessProvider": "Access provider",
+ "versionHistory.field.governmental": "Governmental",
+ "versionHistory.field.licensor": "Licensor",
+ "versionHistory.field.edi.prorateTax": "Prorate tax",
+ "versionHistory.field.edi.prorateFees": "Prorate fees",
+ "versionHistory.field.edi.ediJob.sendToEmails": "Send to emails",
+ "versionHistory.field.edi.ediJob.notifyAllEdi": "Notify all",
+ "versionHistory.field.edi.ediJob.notifyInvoiceOnly": "Notify invoice only",
+ "versionHistory.field.edi.ediJob.notifyErrorOnly": "Notify error only",
+ "versionHistory.field.edi.ediJob.schedulingNotes": "Scheduling notes",
+
"settings.categories": "Categories",
"settings.categories.cannotDeleteTermHeader": "Cannot delete category",
"settings.categories.cannotDeleteTermMessage": "This category cannot be deleted, as it is in use by one or more records.",