Guard finalize action when entry unchanged

This commit is contained in:
Kari Lintulaakso
2025-12-04 16:16:44 +02:00
parent 472f6ba495
commit ec205fda1e
3 changed files with 61 additions and 27 deletions
@@ -1,9 +1,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { Button, Box, Typography, CircularProgress, Divider, alpha, List, ListItemText, Tooltip } from '@mui/material'
import SaveIcon from '@mui/icons-material/Save'
import { useDetailContext } from './Context/DetailContext'
import { EditDataType, Editable, Reference, Species } from '@/shared/types'
import { useState, useEffect, Fragment } from 'react'
import SaveIcon from '@mui/icons-material/Save'
import { referenceValidator } from '@/shared/validators/validator'
import { checkSpeciesTaxonomy, convertSpeciesTaxonomyFields, hasTaxonomyChanges } from '../../util/taxonomyUtilities'
import { useGetAllSpeciesQuery, useGetAllSynonymsQuery } from '@/redux/speciesReducer'
@@ -12,6 +12,7 @@ import { countryBoundingBoxes } from '@/country_data/countryBoundingBoxes'
import { boundingBoxSplit, isPointInBoxes } from '@/util/isPointInBox'
import { OutOfBoundsWarningModal, OutOfBoundsWarningModalState } from './OutOfBoundsWarningModal'
import { skipToken } from '@reduxjs/toolkit/query'
import { finalizeEntry } from '@/services/entryApi'
export { ReturnButton } from '@/components/common/ReturnButton'
export const WriteButton = <T,>({
@@ -111,7 +112,7 @@ export const WriteButton = <T,>({
// @ts-expect-error Reason: Typescript doesn't recognise that references do exist. Unable to find a way around it. Fix if extra time
}, [mode, editData.references])
const writeWithTaxonomyCheck = () => {
const writeWithTaxonomyCheck = async () => {
let speciesEditData: EditDataType<Species> | undefined = undefined
if (!mode.staging && taxonomy) {
@@ -154,37 +155,37 @@ export const WriteButton = <T,>({
}
}
void onWrite((speciesEditData as EditDataType<T>) ?? editData, setEditData).then(() => {
setMode('read')
return
})
await onWrite((speciesEditData as EditDataType<T>) ?? editData, setEditData)
setMode('read')
}
const handleWriteButtonClick = () => {
const dirtyBlocked = !mode.staging && !isDirty
const handleWriteButtonClick = async () => {
const finalize = async () => {
if (taxonomy) {
setLoading(true)
await writeWithTaxonomyCheck()
setLoading(false)
return
}
if (dirtyBlocked) {
notify('Please make changes before finalizing the entry.', 'info')
return
}
if (!mode.staging && hasStagingMode) {
setMode(mode.new ? 'staging-new' : 'staging-edit')
return
}
if (taxonomy) {
setLoading(true)
writeWithTaxonomyCheck()
setLoading(false)
return
}
if (!mode.staging && hasStagingMode) {
setMode(mode.new ? 'staging-new' : 'staging-edit')
return
}
setLoading(true)
void onWrite(editData, setEditData).then(() => {
await onWrite(editData, setEditData)
setLoading(false)
setMode('read')
}
const result = await finalizeEntry({
isDirty,
finalize,
onBlocked: () => notify('Please make changes before finalizing the entry.', 'info'),
})
if (result.blocked) return
}
return (
@@ -221,13 +222,13 @@ export const WriteButton = <T,>({
!('dec_long' in (editData as object)) ||
!('country' in (editData as object))
) {
handleWriteButtonClick()
void handleWriteButtonClick()
return
}
const localityObject = editData as unknown as { dec_lat: number; dec_long: number; country: string }
if (!isCoordinateOutOfBounds(localityObject.dec_lat, localityObject.dec_long, localityObject.country)) {
handleWriteButtonClick()
void handleWriteButtonClick()
return
}
+15
View File
@@ -0,0 +1,15 @@
export type FinalizeEntryParams = {
isDirty: boolean
finalize: () => Promise<void> | void
onBlocked?: () => void
}
export const finalizeEntry = async ({ isDirty, finalize, onBlocked }: FinalizeEntryParams) => {
if (!isDirty) {
onBlocked?.()
return { blocked: true as const }
}
await finalize()
return { blocked: false as const }
}
@@ -38,6 +38,7 @@ const mockUseGetAllSynonymsQuery = useGetAllSynonymsQuery as jest.MockedFunction
const mockCheckSpeciesTaxonomy = checkSpeciesTaxonomy as jest.MockedFunction<typeof checkSpeciesTaxonomy>
const modeEdit = { read: false, staging: false, new: false, option: 'edit' as const }
const modeStagingEdit = { read: false, staging: true, new: false, option: 'staging-edit' as const }
const textField: DetailContextType<Species>['textField'] = () => <span />
const bigTextField: DetailContextType<Species>['bigTextField'] = () => <span />
@@ -198,4 +199,21 @@ describe('WriteButton taxonomy handling', () => {
const button = screen.getByRole('button', { name: /save changes/i })
expect((button as HTMLButtonElement).disabled).toBe(false)
})
it('blocks finalize handler and shows feedback when dirty flag is false', async () => {
const onWrite = jest.fn(() => Promise.resolve())
renderButton(baseSpecies as EditDataType<Species>, onWrite, {
isDirty: false,
mode: modeStagingEdit,
})
fireEvent.click(screen.getByRole('button', { name: /complete and save/i }))
await waitFor(() => {
expect(onWrite).not.toHaveBeenCalled()
})
expect(mockNotify).toHaveBeenCalledWith('Please make changes before finalizing the entry.', 'info')
})
})