Validate estimate_precip as non-negative integer (#88)

This commit is contained in:
karilint
2026-04-20 13:30:58 +03:00
parent f4e7cb4acb
commit 82bcc1842e
4 changed files with 40 additions and 4 deletions
@@ -48,6 +48,9 @@ export type TextFieldOptions =
} & CommonTextFieldOptions)
| ({
type: 'number'
integerOnly?: boolean
min?: number
max?: number
round?: number
handleSetEditData?: (value: number | '') => void
} & CommonTextFieldOptions)
@@ -277,6 +277,8 @@ export const EditableTextField = <T extends object>(props: EditableTextFieldProp
const type = props.type ?? 'text'
const trim = type === 'text' ? (props as EditableTextFieldTextProps<T>).trim ?? false : false
const round = type === 'number' ? (props as EditableTextFieldNumberProps<T>).round : undefined
const integerOnly = type === 'number' ? (props as EditableTextFieldNumberProps<T>).integerOnly ?? false : false
const min = type === 'number' ? (props as EditableTextFieldNumberProps<T>).min : undefined
const { setEditData, editData, validator, fieldsWithErrors, setFieldsWithErrors } = useDetailContext<T>()
const errorObject = validator(editData, field)
const { error } = errorObject
@@ -308,6 +310,7 @@ export const EditableTextField = <T extends object>(props: EditableTextFieldProp
const value = event?.currentTarget?.value
if (type === 'number') {
const setNumberValue = handleSetEditData as EditableTextFieldNumberProps<T>['handleSetEditData']
const allowNegative = min === undefined || min < 0
if (value === '') {
setNumberInputValue('')
if (setNumberValue) {
@@ -320,11 +323,19 @@ export const EditableTextField = <T extends object>(props: EditableTextFieldProp
return
}
if (!isAllowedNumberInputValue(value)) return
if (integerOnly) {
if (!isAllowedIntegerInputValue(value, allowNegative)) return
} else {
if (!isAllowedNumberInputValue(value)) return
}
setNumberInputValue(value)
const asNumber = Number(value)
if (isPartialNumberInputValue(value) || Number.isNaN(asNumber)) return
if (
(integerOnly ? isPartialIntegerInputValue(value, allowNegative) : isPartialNumberInputValue(value)) ||
Number.isNaN(asNumber)
)
return
if (setNumberValue) {
setNumberValue(asNumber)
@@ -366,7 +377,8 @@ export const EditableTextField = <T extends object>(props: EditableTextFieldProp
const value = numberInputValue
const setNumberValue = handleSetEditData as EditableTextFieldNumberProps<T>['handleSetEditData']
if (value === '') return
if (isPartialNumberInputValue(value)) {
const allowNegative = min === undefined || min < 0
if (integerOnly ? isPartialIntegerInputValue(value, allowNegative) : isPartialNumberInputValue(value)) {
setNumberInputValue('')
if (setNumberValue) setNumberValue('')
else setEditData({ ...editData, [field]: '' })
@@ -384,6 +396,7 @@ export const EditableTextField = <T extends object>(props: EditableTextFieldProp
onKeyDown={event => {
if (type !== 'number') return
if (event.key === 'e' || event.key === 'E' || event.key === '+') event.preventDefault()
if (integerOnly && event.key === '.') event.preventDefault()
}}
id={`${String(field)}-textfield`}
value={type === 'number' ? numberInputValue : editData[field] ?? ''}
@@ -409,6 +422,11 @@ const isAllowedNumberInputValue = (value: string) => /^-?\d*(\.\d*)?$/.test(valu
const isPartialNumberInputValue = (value: string) => value === '-' || value === '.' || value === '-.'
const isAllowedIntegerInputValue = (value: string, allowNegative: boolean) =>
(allowNegative ? /^-?\d*$/.test(value) : /^\d*$/.test(value)) || isPartialIntegerInputValue(value, allowNegative)
const isPartialIntegerInputValue = (value: string, allowNegative: boolean) => allowNegative && value === '-'
type EditableTextFieldCommonProps<T extends object> = {
field: keyof EditDataType<T>
big?: boolean
@@ -424,6 +442,9 @@ type EditableTextFieldTextProps<T extends object> = EditableTextFieldCommonProps
type EditableTextFieldNumberProps<T extends object> = EditableTextFieldCommonProps<T> & {
type: 'number'
integerOnly?: boolean
min?: number
max?: number
round?: number
handleSetEditData?: (value: number | '') => void
}
@@ -8,7 +8,10 @@ export const EcometricsTab = () => {
const ecometrics = [
['Mean hypsodonty', calculateMeanHypsodonty(data)],
['Estimate of annual precipitation (mm)', textField('estimate_precip', { type: 'number' })],
[
'Estimate of annual precipitation (mm)',
textField('estimate_precip', { type: 'number', integerOnly: true, min: 0 }),
],
['Estimate of mean annual temperature (°C)', textField('estimate_temp', { type: 'number' })],
['Estimate of net primary productivity (g/m2/yr)', textField('estimate_npp', { type: 'number' })],
['Woody cover percentage', textField('pers_woody_cover', { type: 'number' })],
@@ -14,6 +14,11 @@ const validatePollenPercentage = (name: string, num: number) => {
return
}
const validateNonNegativeInteger = (name: string, num: number) => {
if (!Number.isInteger(num) || num < 0) return `${name} must be a positive integer.`
return
}
const validatePollenRecordTotal = (editData: Partial<EditDataType<LocalityDetailsType>>) => {
const values = pollenFields
.map(fieldName => editData[fieldName])
@@ -193,6 +198,10 @@ export const validateLocality = (
return
},
},
estimate_precip: {
name: 'Estimate of annual precipitation (mm)',
asNumber: (num: number) => validateNonNegativeInteger('Estimate of annual precipitation (mm)', num),
},
}
return validator<EditDataType<LocalityDetailsType>>(validators, editData, fieldName)