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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ UIIN-3437.
* Wrong message is displayed after updating ownership of item. Fixes UIIN-3560.
* When moving item within one holding manually, recalculate other item orders based on their position in the list. Fixes UIIN-3539.
* Handle audit-marc dependency: hide audit button. Refs UIIN-3576.
* Include additional call numbers in Version History for Inventory Item. Refs UIIN-3558.

## [13.0.10](https://github.com/folio-org/ui-inventory/tree/v13.0.10) (2025-09-01)
[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v13.0.9...v13.0.10)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,32 @@ export const createFieldFormatter = (referenceData, circulationHistory) => ({
},
});

export const createItemFormatter = (fieldLabelsMap, fieldFormatter) => (element, i) => {
if (!element) return null;

const { name: fieldName, value, collectionName } = element;
const compositeKey = collectionName && fieldName
? `${collectionName}.${fieldName}`
: null;

const label = (compositeKey && fieldLabelsMap?.[compositeKey])
|| fieldLabelsMap?.[fieldName]
|| fieldLabelsMap?.[collectionName];

const formattedValue = (compositeKey && fieldFormatter?.[compositeKey]?.(value))
|| fieldFormatter?.[fieldName]?.(value)
|| fieldFormatter?.[collectionName]?.(value)
|| value;

return (
<li key={i}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think it is possible to use stripes' List component here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stripes List component includes <ul>:
https://github.com/folio-org/stripes-components/blob/58e8eb022e3ef11726fcb90d129e946f57b9703d/lib/List/List.js#L52
but for the itemFormatter, we just like to define <li>, since the itemFormatter is called already inside a List:

      <List
        items={map(fieldValue, (value, name) => ({ name, value, collectionName: fieldName }))}
        itemFormatter={listItemFormatter}
        listStyle="bullets"
        marginBottom0
      />

{fieldName && <strong>{label}: </strong>}
{formattedValue}
</li>
);
};


const ItemVersionHistory = ({
item,
onClose,
Expand Down Expand Up @@ -137,9 +163,21 @@ const ItemVersionHistory = ({
typeId: formatMessage({ id: 'ui-inventory.effectiveCallNumberType' }),
volume: formatMessage({ id: 'ui-inventory.volume' }),
yearCaption: formatMessage({ id: 'ui-inventory.yearCaption' }),
additionalCallNumbers: formatMessage({ id: 'ui-inventory.additionalCallNumbers' }),
'additionalCallNumbers.prefix': formatMessage({ id: 'ui-inventory.additionalCallNumberPrefix' }),
'additionalCallNumbers.suffix': formatMessage({ id: 'ui-inventory.additionalCallNumberSuffix' }),
'additionalCallNumbers.typeId': formatMessage({ id: 'ui-inventory.additionalCallNumberType' }),
'additionalCallNumbers.callNumber': formatMessage({ id: 'ui-inventory.additionalCallNumber' }),
'circulationNotes.noteType': formatMessage({ id: 'ui-inventory.noteType' }),
'circulationNotes.note': formatMessage({ id: 'ui-inventory.note' }),
'circulationNotes.id': formatMessage({ id: 'ui-inventory.identifier' }),
'circulationNotes.date': formatMessage({ id: 'ui-inventory.date' }),
'circulationNotes.staffOnly': formatMessage({ id: 'ui-inventory.staffOnly' }),
'circulationNotes.source': formatMessage({ id: 'ui-inventory.source' }),
};

const fieldFormatter = createFieldFormatter(referenceData, circulationHistory);
const itemFormatter = createItemFormatter(fieldLabelsMap, fieldFormatter);

return (
<AuditLogPane
Expand All @@ -151,6 +189,7 @@ const ItemVersionHistory = ({
isInitialLoading={isLoading}
fieldLabelsMap={fieldLabelsMap}
fieldFormatter={fieldFormatter}
itemFormatter={itemFormatter}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you describe the difference between itemFormatter and fieldFormatter, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The itemFormatter is adding the fieldname before the value (in case its an object). It is formatting a list inside the list (of the fieldFormatter).
I added a second screenshot to the description.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you are right, I have to adapt the README.md here:
folio-org/stripes-components#2519

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added itemFormatter to AuditLog/readme.md

actionsMap={actionsMap}
totalVersions={totalVersions}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
translationsProperties,
} from '../../../../../test/jest/helpers';

import ItemVersionHistory, { createFieldFormatter } from './ItemVersionHistory';
import ItemVersionHistory, { createFieldFormatter, createItemFormatter } from './ItemVersionHistory';
import { DataContext } from '../../../../contexts';

import {
Expand Down Expand Up @@ -278,3 +278,104 @@ describe('createFieldFormatter', () => {
expect(fieldFormatter.source({ personal: { lastName: 'Doe' } })).toBe('Doe');
});
});

describe('createItemFormatter', () => {
const fieldLabelsMap = {
barcode: 'Item Barcode',
discoverySuppress: 'Discovery Suppress',
circulationNotes: 'Circulation History',
'additionalCallNumbers.prefix': 'Additional call number prefix',
'additionalCallNumbers.suffix': 'Additional call number suffix',
'additionalCallNumbers.typeId': 'Additional call number type',
'additionalCallNumbers.callNumber': 'Additional call number',
'circulationNotes.staffOnly': 'Staff Only',
'circulationNotes.note': 'Note',
'circulationNotes.noteType': 'Note Type',
materialTypeId: 'Material Type',
};

const fieldFormatter = createFieldFormatter(mockReferenceData, {
servicePointName: 'Main Desk',
source: 'Librarian User',
});

const itemFormatter = createItemFormatter(fieldLabelsMap, fieldFormatter);

it('should return null for null element', () => {
expect(itemFormatter(null, 0)).toBeNull();
});

it('should return null for undefined element', () => {
expect(itemFormatter(undefined, 0)).toBeNull();
});

it('should format field with collectionName using composite key', () => {
const element = {
name: 'staffOnly',
value: false,
collectionName: 'circulationNotes',
};

const result = itemFormatter(element, 0);
const { container } = renderWithIntl(result, translationsProperties);

expect(container.querySelector('strong')).toHaveTextContent('Staff Only:');
expect(container.querySelector('li')).toHaveTextContent('Staff Only: false');
});

it('should fallback to fieldName label when composite key not found', () => {
const element = {
name: 'discoverySuppress',
value: true,
collectionName: 'unknownCollection',
};

const result = itemFormatter(element, 0);
const { container } = renderWithIntl(result, translationsProperties);

expect(container.querySelector('strong')).toHaveTextContent('Discovery Suppress:');
expect(container.querySelector('li')).toHaveTextContent('Discovery Suppress: true');
});

it('should fallback to collectionName label when fieldName not found', () => {
const element = {
name: 'unknownField',
value: 'test value',
collectionName: 'circulationNotes',
};

const result = itemFormatter(element, 0);
const { container } = renderWithIntl(result, translationsProperties);

expect(container.querySelector('strong')).toHaveTextContent('Circulation History:');
expect(container.querySelector('li')).toHaveTextContent('Circulation History: test value');
});

it('should render additionalCallNumbers.prefix with label and value', () => {
const element = {
name: 'prefix',
value: 'ABC',
collectionName: 'additionalCallNumbers',
};

const result = itemFormatter(element, 0);
const { container } = renderWithIntl(result, translationsProperties);

expect(container.querySelector('li'))
.toHaveTextContent('Additional call number prefix: ABC');
});

it('should render circulationNotes.note with label and value', () => {
const element = {
name: 'note',
value: 'Damaged cover',
collectionName: 'circulationNotes',
};

const result = itemFormatter(element, 0);
const { container } = renderWithIntl(result, translationsProperties);

expect(container.querySelector('li'))
.toHaveTextContent('Note: Damaged cover');
});
});
8 changes: 4 additions & 4 deletions src/edit/items/ItemForm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ describe('ItemForm', () => {
...mockReferenceTables,
callNumberTypes: [{ id: '1', name: 'Library of Congress classification' }],
};
const { getByText, getAllByText, queryByText } = renderItemForm({
const { getByText, getAllByText } = renderItemForm({
initialValues,
referenceTables,
});
Expand Down Expand Up @@ -291,9 +291,9 @@ describe('ItemForm', () => {
'itemLevelCallNumberTypeId': { value: '2' },
'additionalCallNumbers': {
value: [{
callNumber: 'cn1',
prefix: 'prefix1',
suffix: 'suffix1',
additionalCallNumber: 'cn1',
additionalCallNumberPrefix: 'prefix1',
additionalCallNumberSuffix: 'suffix1',
typeId: '1'
}]
}
Expand Down
5 changes: 5 additions & 0 deletions translations/ui-inventory/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@
"callNumberPrefix": "Call number prefix",
"callNumberSuffix": "Call number suffix",
"primaryItemCallNumber": "Primary item call number",
"additionalCallNumbers": "Additional call numbers",
"additionalCallNumber": "Additional call number",
"additionalCallNumberPrefix": "Additional call number prefix",
"additionalCallNumberSuffix": "Additional call number suffix",
"additionalCallNumberType": "Additional call number type",
"additionalItemCallNumbers": "Additional item call numbers",
"primaryHoldingsCallNumber": "Primary holdings call number",
"additionalHoldingsCallNumbers": "Additional holdings call numbers",
Expand Down
Loading