Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Extend Search page with Authority search and create options. Refs [UILD-825].
* Fix field collision in the record generation. Fixes [UILD-838].
* Update language used for importing works/instances. Refs [UILD-839].
* Fix multiple settings per profile. Refs [UILD-780].

[UILD-744]:https://folio-org.atlassian.net/browse/UILD-744
[UILD-816]:https://folio-org.atlassian.net/browse/UILD-816
Expand All @@ -21,6 +22,7 @@
[UILD-825]:https://folio-org.atlassian.net/browse/UILD-825
[UILD-838]:https://folio-org.atlassian.net/browse/UILD-838
[UILD-839]:https://folio-org.atlassian.net/browse/UILD-839
[UILD-780]:https://folio-org.atlassian.net/browse/UILD-780

## 2.0.4 (2026-06-03)
* Fix default profile type persistence across edit form and profile settings. Fixes [UILD-820].
Expand Down
39 changes: 33 additions & 6 deletions src/common/api/profiles.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
PROFILE_API_ENDPOINT,
PROFILE_METADATA_API_ENDPOINT,
PROFILE_PREFERRED_API_ENDPOINT,
PROFILE_SETTINGS_API_ENDPOINT,
PROFILE_SETTINGS_PATH,
} from '@/common/constants/api.constants';

import baseApi from './base.api';
Expand Down Expand Up @@ -52,16 +52,21 @@ export const deletePreferredProfile = (resourceType: ResourceTypeURL) => {
});
};

export const fetchProfileSettings = (profileId: string | number) =>
export const fetchAllSettingsForProfile = (profileId: string | number) =>
baseApi.getJson({
url: `${PROFILE_SETTINGS_API_ENDPOINT}/${profileId}`,
url: `${PROFILE_API_ENDPOINT}/${profileId}/${PROFILE_SETTINGS_PATH}`,
}) as Promise<ProfileSettingsMetaList>;

export const fetchProfileSettings = (profileId: string | number, profileSettingsId: string | number) =>
baseApi.getJson({
url: `${PROFILE_API_ENDPOINT}/${profileId}/${PROFILE_SETTINGS_PATH}/${profileSettingsId}`,
}) as Promise<ProfileSettings>;

export const saveProfileSettings = (profileId: string | number, settings: ProfileSettings) => {
const url = `${PROFILE_SETTINGS_API_ENDPOINT}/${profileId}`;
export const createProfileSettings = async (profileId: string | number, settings: ProfileSettings) => {
const url = `${PROFILE_API_ENDPOINT}/${profileId}/${PROFILE_SETTINGS_PATH}`;
const body = JSON.stringify(settings);

return baseApi.request({
const response = await baseApi.request({
url,
requestParams: {
method: 'POST',
Expand All @@ -71,4 +76,26 @@ export const saveProfileSettings = (profileId: string | number, settings: Profil
},
},
});

return response?.json();
};

export const saveProfileSettings = (
profileId: string | number,
profileSettingsId: string | number,
settings: ProfileSettings,
) => {
const url = `${PROFILE_API_ENDPOINT}/${profileId}/${PROFILE_SETTINGS_PATH}/${profileSettingsId}`;
const body = JSON.stringify(settings);

return baseApi.request({
url,
requestParams: {
method: 'PUT',
body,
headers: {
'content-type': 'application/json',
},
},
});
};
4 changes: 3 additions & 1 deletion src/common/constants/api.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const INVENTORY_API_ENDPOINT = '/linked-data/inventory-instance';
export const PROFILE_API_ENDPOINT = '/linked-data/profile';
export const PROFILE_METADATA_API_ENDPOINT = '/linked-data/profile/metadata';
export const PROFILE_PREFERRED_API_ENDPOINT = '/linked-data/profile/preferred';
export const PROFILE_SETTINGS_API_ENDPOINT = '/linked-data/profile/settings';
export const PROFILE_SETTINGS_PATH = 'settings';
export const AUTHORITY_ASSIGNMENT_CHECK_API_ENDPOINT = '/linked-data/authority-assignment-check';
export const IMPORT_JSON_FILE_API_ENDPOINT = '/linked-data/import/file';
export const IMPORT_JSON_URL_API_ENDPOINT = '/linked-data/import/url';
Expand Down Expand Up @@ -50,6 +50,8 @@ export enum ApiErrorCodes {
RequiredPrimaryMainTitle = 'required_primary_main_title',
LccnDoesNotMatchPattern = 'lccn_does_not_match_pattern',
LccnNotUnique = 'lccn_not_unique',
ProfileSettingsNameNotUnique = 'profile_settings_name_not_unique',
NotBlank = 'must not be blank',
FailedDependency = 'failed_dependency',
NotFound = 'not_found',
Mapping = 'mapping',
Expand Down
9 changes: 9 additions & 0 deletions src/common/constants/profileSettings.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ export const DEFAULT_INACTIVE_SETTINGS: ProfileSettingsWithDrift = {
children: [],
missingFromSettings: [],
};

export const PROFILE_SETTINGS_DEFAULT_OPTION = 'default';

export const BASE_SETTINGS_OPTIONS = [
{
label: '',
value: '',
},
];
1 change: 1 addition & 0 deletions src/common/constants/routes.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export enum QueryParams {
Source = 'source',
SourceUri = 'sourceUri',
ProfileId = 'profileId',
ProfileSettingsId = 'profileSettingsId',
}

export enum SearchQueryParams {
Expand Down
6 changes: 6 additions & 0 deletions src/common/helpers/navigation.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ export const generatePageURL = ({
url,
queryParams,
profileId,
profileSettingsId,
}: {
url: string;
queryParams: Record<QueryParams, string>;
profileId: string | number;
profileSettingsId?: string | number;
}) => {
const urlParams = new URLSearchParams();

Expand All @@ -60,6 +62,10 @@ export const generatePageURL = ({
urlParams.set(QueryParams.ProfileId, profileId.toString());
}

if (profileSettingsId) {
urlParams.set(QueryParams.ProfileSettingsId, profileSettingsId.toString());
}

const paramString = urlParams.toString();

return paramString ? `${url}?${paramString}` : url;
Expand Down
46 changes: 46 additions & 0 deletions src/common/hooks/useDismissMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { RefObject, useEffect, useState } from 'react';

export const useDismissMenu = (ref: RefObject<HTMLElement | null>) => {
const [isOpen, setIsOpen] = useState(false);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
setIsOpen(false);
}
};

const handleFocusOutside = (event: FocusEvent) => {
if (!event.relatedTarget || (ref.current && !ref.current.contains(event.relatedTarget as Node))) {
setIsOpen(false);
}
};

const handleEscape = (event: KeyboardEvent) => {
if (ref.current && event.key === 'Escape') {
setIsOpen(false);
ref.current.focus();
}
};

document.addEventListener('pointerdown', handleClickOutside);
document.addEventListener('focusout', handleFocusOutside);
document.addEventListener('keydown', handleEscape);

return () => {
document.removeEventListener('pointerdown', handleClickOutside);
document.removeEventListener('focusout', handleFocusOutside);
document.removeEventListener('keydown', handleEscape);
};
}, [isOpen]);

const toggle = () => {
setIsOpen(prev => !prev);
};

return {
isOpen,
setIsOpen,
toggle,
};
};
2 changes: 2 additions & 0 deletions src/features/edit/components/EditSection/DrawComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { getPlaceholderForProperty } from '@/features/search/ui';

import { FieldWithMetadataAndControls } from '../FieldWithMetadataAndControls';
import { BlockActions } from './BlockActions';
import { ProfileSettingsSelector } from './ProfileSettingsSelector';
import { EditSectionDataProps } from './renderDrawComponent';

export type IDrawComponent = {
Expand Down Expand Up @@ -67,6 +68,7 @@ export const DrawComponent: FC<IDrawComponent & EditSectionDataProps> = ({
<FormattedMessage id={collapsedEntries.size ? 'ld.expandAll' : 'ld.collapseAll'} />
</Button>
)}
<ProfileSettingsSelector />
<BlockActions entry={entry} />
</div>
</FieldWithMetadataAndControls>
Expand Down
34 changes: 34 additions & 0 deletions src/features/edit/components/EditSection/EditSection.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,40 @@
width: 20rem;
}
}

.profile-settings-selector {
display: inline-block;
position: relative;
margin: 0;
padding: 0;

#selector-title {
font-size: $font-md;
font-weight: 700;
color: $buttonIcon;
padding-bottom: 5px;
}

&-menu {
position: absolute;
list-style: none;
z-index: 2;
top: 10px;
right: 0;
width: max-content;
padding: 15px 10px;
border: 1px solid $gray-350;
background-color: $base;
box-shadow: $menu-drop-shadow;

button {
font-size: $font-md;
font-weight: 400;
width: 100%;
justify-content: flex-start;
}
}
}
}

.edit-section-passive {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { setInitialGlobalState } from '@/test/__mocks__/store';

import { MemoryRouter } from 'react-router-dom';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';

import { useProfileStore } from '@/store';

import { ProfileSettingsSelector } from './ProfileSettingsSelector';

const mockGetRecordProfileId = jest.fn();

jest.mock('@/common/helpers/record.helper', () => ({
getRecordProfileId: () => mockGetRecordProfileId(),
}));

describe('ProfileSettingsSelector', () => {
const mockProfileId = 'profile-id';
const mockSetSelectedProfileSettingsId = jest.fn();

const renderComponent = () => {
setInitialGlobalState([
{
store: useProfileStore,
state: {
selectedProfileSettingsId: '32',
setSelectedProfileSettingsId: mockSetSelectedProfileSettingsId,
},
},
]);

const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
queryClient.setQueryData(
['profileSettingsMeta', mockProfileId],
[
{
id: 15,
name: 'fifteen',
},
{
id: 32,
name: 'thirty-two',
},
],
);

return render(
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<ProfileSettingsSelector />
</MemoryRouter>
</QueryClientProvider>,
);
};

it('renders the component', () => {
renderComponent();

expect(screen.getByTestId('profile-settings-selector-button')).toBeInTheDocument();
});

it('clicking button shows menu', () => {
renderComponent();

fireEvent.click(screen.getByTestId('profile-settings-selector-button'));

expect(screen.getByTestId('profile-settings-selector-menu')).toBeInTheDocument();
});

it('profile setting currently in use is disabled', async () => {
mockGetRecordProfileId.mockReturnValue(mockProfileId);

renderComponent();

fireEvent.click(screen.getByTestId('profile-settings-selector-button'));

await waitFor(() => {
expect(screen.getByText('thirty-two')).toBeDisabled();
});
});

it('clicking a setting selects it', async () => {
mockGetRecordProfileId.mockReturnValue(mockProfileId);

renderComponent();

fireEvent.click(screen.getByTestId('profile-settings-selector-button'));
fireEvent.click(screen.getByText('fifteen'));

await waitFor(() => {
expect(screen.queryByTestId('profile-settings-selector-menu')).not.toBeInTheDocument();
expect(mockSetSelectedProfileSettingsId).toHaveBeenCalledWith('15');
});
});
});
Loading
Loading