diff --git a/CHANGELOG.md b/CHANGELOG.md index abe8543b..57f62d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change history for ui-quick-marc +## [9.1.0] (IN PROGRESS) + +* [UIQM-730](https://issues.folio.org/browse/UIQM-730) Create/Edit/Derive MARC record - Retain focus when MARC record validation rules error display. Show validation issues toasts. + ## [9.0.1] (IN PROGRESS) * [UIQM-725](https://issues.folio.org/browse/UIQM-725) Fix wrong error message while saving MARC Bib record with invalid LDR position values. diff --git a/package.json b/package.json index aa506097..2e54542c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@folio/quick-marc", - "version": "9.0.0", + "version": "9.1.0", "description": "Quick MARC editor", "main": "index.js", "repository": "", diff --git a/src/QuickMarcEditor/QuickMarcEditor.js b/src/QuickMarcEditor/QuickMarcEditor.js index 3f90b497..1e35f3bb 100644 --- a/src/QuickMarcEditor/QuickMarcEditor.js +++ b/src/QuickMarcEditor/QuickMarcEditor.js @@ -136,6 +136,14 @@ const QuickMarcEditor = ({ const searchParameters = new URLSearchParams(location.search); const isShared = searchParameters.get('shared') === 'true'; + const saveLastFocusedInput = useCallback((e) => { + lastFocusedInput.current = e.target; + }, [lastFocusedInput]); + + const focusLastFocusedInput = useCallback(() => { + lastFocusedInput.current?.focus(); + }, [lastFocusedInput]); + const { unlinkAuthority } = useAuthorityLinking({ marcType, action }); useEffect(() => { @@ -161,13 +169,13 @@ const QuickMarcEditor = ({ if (continueAfterSave.current) { setRelatedRecordVersion(updatedRecord.version); - lastFocusedInput.current?.focus(); + focusLastFocusedInput(); return; } onSave(); - }, [setRelatedRecordVersion, onSave]); + }, [setRelatedRecordVersion, onSave, focusLastFocusedInput]); const closeModals = () => { setIsDeleteModalOpened(false); @@ -217,7 +225,7 @@ const QuickMarcEditor = ({ }; }, [setIsValidationModalOpen, isBackEndValidationMarcType, marcType]); - const showValidationIssuesCallouts = useCallback((issues) => { + const showErrorsForMissingFields = useCallback((issues) => { issues.forEach((error) => { showCallout({ message: error.message, @@ -228,6 +236,32 @@ const QuickMarcEditor = ({ }); }, [showCallout]); + const showValidationIssuesToasts = useCallback((validationErrors) => { + const allIssuesArray = Object.values(validationErrors).flat(); + const failCount = allIssuesArray.filter(issue => issue.severity === SEVERITY.ERROR).length; + const warnCount = allIssuesArray.length - failCount; + + const values = { + warnCount, + failCount, + }; + let messageId = null; + + if (failCount && warnCount) { + messageId = 'ui-quick-marc.record.save.error.failAndWarn'; + } else if (failCount) { + messageId = 'ui-quick-marc.record.save.error.fail'; + } else { + messageId = 'ui-quick-marc.record.save.error.warn'; + } + + showCallout({ + messageId, + values, + type: failCount ? 'error' : 'warning', + }); + }, [showCallout]); + const confirmSubmit = useCallback(async (e, isKeepEditing = false) => { continueAfterSave.current = isKeepEditing; let skipValidation = false; @@ -259,9 +293,12 @@ const QuickMarcEditor = ({ return; } + const validationErrorsWithoutFieldId = newValidationErrors[MISSING_FIELD_ID] || []; - showValidationIssuesCallouts(validationErrorsWithoutFieldId); + showErrorsForMissingFields(validationErrorsWithoutFieldId); + showValidationIssuesToasts(newValidationErrors); + focusLastFocusedInput(); setIsValidatedCurrentValues(true); } else { setValidationErrors({}); @@ -283,13 +320,15 @@ const QuickMarcEditor = ({ getState, hasErrorIssues, setValidationErrors, - showValidationIssuesCallouts, + showErrorsForMissingFields, showCallout, validate, runConfirmationChecks, isValidatedCurrentValues, setIsValidatedCurrentValues, manageBackendValidationModal, + focusLastFocusedInput, + showValidationIssuesToasts, ]); const paneFooter = useMemo(() => { @@ -450,10 +489,6 @@ const QuickMarcEditor = ({ } }, []); - const saveLastFocusedInput = useCallback((e) => { - lastFocusedInput.current = e.target; - }, [lastFocusedInput]); - const shortcuts = useMemo(() => ([{ name: 'save', shortcut: 'mod+s', @@ -580,7 +615,7 @@ const QuickMarcEditor = ({ /> { confirmRemoveAuthorityLinking && ( - + { - const { values } = useFormState(); - const { validationErrorsRef } = useContext(QuickMarcContext); - - const firstFieldWithErrors = useMemo(() => { - return values.records.find(({ id }) => Boolean(validationErrorsRef.current[id])); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [values.records, validationErrorsRef.current]); - - useEffect(() => { - if (!firstFieldWithErrors?.id) { - return; - } - - const fieldSelector = `[data-fieldid="${firstFieldWithErrors.id}"]`; - - document.querySelector(`${fieldSelector} input:enabled, ${fieldSelector} textarea:enabled`)?.focus(); - }, [firstFieldWithErrors?.id, validationErrorsRef]); -}; diff --git a/src/hooks/useFocusFirstFieldWithError/useFocusFirstFieldWithError.test.js b/src/hooks/useFocusFirstFieldWithError/useFocusFirstFieldWithError.test.js deleted file mode 100644 index 50baad04..00000000 --- a/src/hooks/useFocusFirstFieldWithError/useFocusFirstFieldWithError.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { - renderHook, - screen, -} from '@folio/jest-config-stripes/testing-library/react'; -import '@folio/jest-config-stripes/testing-library/jest-dom'; - -import Harness from '../../../test/jest/helpers/harness'; - -import { useFocusFirstFieldWithError } from './useFocusFirstFieldWithError'; - -jest.mock('react-final-form', () => ({ - useFormState: jest.fn().mockReturnValue({ - values: { - records: [{ - id: 'id-without-error', - }, { - id: 'id-with-error', - }], - }, - }), -})); - -const Wrapper = ({ children }) => ( - -
-
- {children} -
-); - -const renderUseFirstFieldWithError = () => { - return renderHook(() => useFocusFirstFieldWithError(), { wrapper: Wrapper }); -}; - -describe('Given useFocusFirstFieldWithError', () => { - describe('when a field has an error', () => { - it('should focus that field', () => { - renderUseFirstFieldWithError(); - - expect(screen.getByRole('textbox', { name: 'input-2' })).toHaveFocus(); - }); - }); -}); diff --git a/translations/ui-quick-marc/en.json b/translations/ui-quick-marc/en.json index 4e60e839..968e39ec 100644 --- a/translations/ui-quick-marc/en.json +++ b/translations/ui-quick-marc/en.json @@ -760,6 +760,9 @@ "record.save.error.notFound": "This record has been deleted by another user. You can no longer edit this record, hit the cancel button to return to the previous page.", "record.save.error.generic": "Record not saved: Communication problem with server. Please try again.", "record.save.error.derive": "Instance cannot be updated. Deprecated instance version.", + "record.save.error.fail": "{failCount} Fail error(s) was found. You cannot save record with a Fail error. Please resolve error(s) and hit Save to re-validate your record.", + "record.save.error.warn": "{warnCount} Warn error(s) was found. Hit Save again to continue to save record with Warn errors. Please resolve error(s) and hit Save to re-validate your record.", + "record.save.error.failAndWarn": "{warnCount} Warn error(s) was found. {failCount} Fail error(s) was found. You cannot save record with a Fail error. Please resolve error(s) and hit Save to re-validate your record.", "record.save.updatingLinkedBibRecords": "This record has successfully saved and is in process. {count} linked bibliographic record(s) updates have begun.", "record.save.continue": "Save & keep editing",