diff --git a/frontend/src/components/Locality/Tabs/ClimateTab.tsx b/frontend/src/components/Locality/Tabs/ClimateTab.tsx index 76af2635..361add3f 100755 --- a/frontend/src/components/Locality/Tabs/ClimateTab.tsx +++ b/frontend/src/components/Locality/Tabs/ClimateTab.tsx @@ -129,9 +129,12 @@ export const ClimateTab = () => { ] const pollenRecord = [ - ['Arboreal pollen (AP%)', textField('pers_pollen_ap', { type: 'number' })], - ['Non-arboreal pollen (NAP%)', textField('pers_pollen_nap', { type: 'number' })], - ['Other pollen (OP%)', textField('pers_pollen_other', { type: 'number' })], + ['Arboreal pollen (AP%)', textField('pers_pollen_ap', { type: 'number', integerOnly: true, min: 0, max: 100 })], + [ + 'Non-arboreal pollen (NAP%)', + textField('pers_pollen_nap', { type: 'number', integerOnly: true, min: 0, max: 100 }), + ], + ['Other pollen (OP%)', textField('pers_pollen_other', { type: 'number', integerOnly: true, min: 0, max: 100 })], ] const pollenTotalError = ['pers_pollen_ap', 'pers_pollen_nap', 'pers_pollen_other'] diff --git a/frontend/src/components/Locality/Tabs/EcometricsTab.tsx b/frontend/src/components/Locality/Tabs/EcometricsTab.tsx index 12bc1fdb..787bca56 100755 --- a/frontend/src/components/Locality/Tabs/EcometricsTab.tsx +++ b/frontend/src/components/Locality/Tabs/EcometricsTab.tsx @@ -13,8 +13,11 @@ export const EcometricsTab = () => { 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' })], + [ + 'Estimate of net primary productivity (g/m2/yr)', + textField('estimate_npp', { type: 'number', integerOnly: true, min: 0 }), + ], + ['Woody cover percentage', textField('pers_woody_cover', { type: 'number', integerOnly: true, min: 0, max: 100 })], ] return ( diff --git a/frontend/src/components/Locality/Tabs/LocalityTab.tsx b/frontend/src/components/Locality/Tabs/LocalityTab.tsx index 2f5a7699..8f753758 100755 --- a/frontend/src/components/Locality/Tabs/LocalityTab.tsx +++ b/frontend/src/components/Locality/Tabs/LocalityTab.tsx @@ -135,7 +135,7 @@ export const LocalityTab = () => { 'Approximate Coordinates' ), ], - ['Altitude (m)', textField('altitude')], + ['Altitude (m)', textField('altitude', { type: 'number', integerOnly: true, min: -500, max: 9000 })], ] const { notify } = useNotify() diff --git a/frontend/src/components/Locality/Tabs/TaphonomyTab.tsx b/frontend/src/components/Locality/Tabs/TaphonomyTab.tsx index 857b831a..192dbed6 100755 --- a/frontend/src/components/Locality/Tabs/TaphonomyTab.tsx +++ b/frontend/src/components/Locality/Tabs/TaphonomyTab.tsx @@ -118,9 +118,9 @@ export const TaphonomyTab = () => { const taphonomy = [ ['Time Represented (years)', dropdown('time_rep', timeRepresentedOptions, 'Time Represented (years)')], ['Vertebrate Preservation', dropdown('vert_pres', vertebratePreservationOptions, 'Vertebrate Preservation')], - ['Approx. Number of Specimens', textField('appr_num_spm', { type: 'number' })], - ['Exact Number of Specimens', textField('num_spm', { type: 'number' })], - ['Number of Quadrats', textField('num_quad', { type: 'number' })], + ['Approx. Number of Specimens', textField('appr_num_spm', { type: 'number', integerOnly: true, min: 1 })], + ['Exact Number of Specimens', textField('num_spm', { type: 'number', integerOnly: true, min: 1 })], + ['Number of Quadrats', textField('num_quad', { type: 'number', integerOnly: true, min: 1 })], ['Unbiased Collecting', dropdown('true_quant', unbiasedCollectingOptions, 'Unbiased Collecting')], ['Species List Complete', dropdown('complete', speciesListCompleteOptions, 'Species List Complete')], ] diff --git a/frontend/src/components/Occurrence/Tabs/OccurrenceCoreTab.tsx b/frontend/src/components/Occurrence/Tabs/OccurrenceCoreTab.tsx index 67421ae5..90209194 100644 --- a/frontend/src/components/Occurrence/Tabs/OccurrenceCoreTab.tsx +++ b/frontend/src/components/Occurrence/Tabs/OccurrenceCoreTab.tsx @@ -43,10 +43,19 @@ export const OccurrenceCoreTab = () => { key="counts" title="Occurrence counts" array={[ - ['NISP', mode.read ? toText(sourceData.nis) : textField('nis', { type: 'number' })], + [ + 'NISP', + mode.read ? toText(sourceData.nis) : textField('nis', { type: 'number', integerOnly: true, min: 1 }), + ], ['Percent', mode.read ? toText(sourceData.pct) : textField('pct', { type: 'number' })], - ['Quadrate', mode.read ? toText(sourceData.quad) : textField('quad', { type: 'number' })], - ['MNI', mode.read ? toText(sourceData.mni) : textField('mni', { type: 'number' })], + [ + 'Quadrate', + mode.read ? toText(sourceData.quad) : textField('quad', { type: 'number', integerOnly: true, min: 1 }), + ], + [ + 'MNI', + mode.read ? toText(sourceData.mni) : textField('mni', { type: 'number', integerOnly: true, min: 1 }), + ], ['Quantity', mode.read ? formatQuantity(sourceData.qua) : dropdown('qua', quantityOptions, 'Quantity')], ]} />, @@ -54,7 +63,12 @@ export const OccurrenceCoreTab = () => { diff --git a/frontend/src/components/Occurrence/Tabs/OccurrenceIsotopeTab.tsx b/frontend/src/components/Occurrence/Tabs/OccurrenceIsotopeTab.tsx index aafb23d5..91967682 100644 --- a/frontend/src/components/Occurrence/Tabs/OccurrenceIsotopeTab.tsx +++ b/frontend/src/components/Occurrence/Tabs/OccurrenceIsotopeTab.tsx @@ -16,7 +16,12 @@ export const OccurrenceIsotopeTab = () => { title="δ13C" array={[ ['Mean', mode.read ? toText(sourceData.dc13_mean) : textField('dc13_mean', { type: 'number' })], - ['N', mode.read ? toText(sourceData.dc13_n) : textField('dc13_n', { type: 'number' })], + [ + 'N', + mode.read + ? toText(sourceData.dc13_n) + : textField('dc13_n', { type: 'number', integerOnly: true, min: 1 }), + ], ['Max', mode.read ? toText(sourceData.dc13_max) : textField('dc13_max', { type: 'number' })], ['Min', mode.read ? toText(sourceData.dc13_min) : textField('dc13_min', { type: 'number' })], ['Stdev', mode.read ? toText(sourceData.dc13_stdev) : textField('dc13_stdev', { type: 'number' })], @@ -27,7 +32,12 @@ export const OccurrenceIsotopeTab = () => { title="δ18O" array={[ ['Mean', mode.read ? toText(sourceData.do18_mean) : textField('do18_mean', { type: 'number' })], - ['N', mode.read ? toText(sourceData.do18_n) : textField('do18_n', { type: 'number' })], + [ + 'N', + mode.read + ? toText(sourceData.do18_n) + : textField('do18_n', { type: 'number', integerOnly: true, min: 1 }), + ], ['Max', mode.read ? toText(sourceData.do18_max) : textField('do18_max', { type: 'number' })], ['Min', mode.read ? toText(sourceData.do18_min) : textField('do18_min', { type: 'number' })], ['Stdev', mode.read ? toText(sourceData.do18_stdev) : textField('do18_stdev', { type: 'number' })], diff --git a/frontend/src/components/Occurrence/Tabs/OccurrenceWearTab.tsx b/frontend/src/components/Occurrence/Tabs/OccurrenceWearTab.tsx index 9ae7e8d2..3a3780ca 100644 --- a/frontend/src/components/Occurrence/Tabs/OccurrenceWearTab.tsx +++ b/frontend/src/components/Occurrence/Tabs/OccurrenceWearTab.tsx @@ -33,11 +33,36 @@ export const OccurrenceWearTab = () => { 'Mesowear', mode.read ? formatMesowear(sourceData.mesowear) : dropdown('mesowear', mesowearOptions, 'Mesowear'), ], - ['MW OR High', mode.read ? toText(sourceData.mw_or_high) : textField('mw_or_high', { type: 'number' })], - ['MW OR Low', mode.read ? toText(sourceData.mw_or_low) : textField('mw_or_low', { type: 'number' })], - ['MW CS Sharp', mode.read ? toText(sourceData.mw_cs_sharp) : textField('mw_cs_sharp', { type: 'number' })], - ['MW CS Round', mode.read ? toText(sourceData.mw_cs_round) : textField('mw_cs_round', { type: 'number' })], - ['MW CS Blunt', mode.read ? toText(sourceData.mw_cs_blunt) : textField('mw_cs_blunt', { type: 'number' })], + [ + 'MW OR High', + mode.read + ? toText(sourceData.mw_or_high) + : textField('mw_or_high', { type: 'number', integerOnly: true, min: 0, max: 100 }), + ], + [ + 'MW OR Low', + mode.read + ? toText(sourceData.mw_or_low) + : textField('mw_or_low', { type: 'number', integerOnly: true, min: 0, max: 100 }), + ], + [ + 'MW CS Sharp', + mode.read + ? toText(sourceData.mw_cs_sharp) + : textField('mw_cs_sharp', { type: 'number', integerOnly: true, min: 0, max: 100 }), + ], + [ + 'MW CS Round', + mode.read + ? toText(sourceData.mw_cs_round) + : textField('mw_cs_round', { type: 'number', integerOnly: true, min: 0, max: 100 }), + ], + [ + 'MW CS Blunt', + mode.read + ? toText(sourceData.mw_cs_blunt) + : textField('mw_cs_blunt', { type: 'number', integerOnly: true, min: 0, max: 100 }), + ], ]} />,
@@ -47,11 +72,15 @@ export const OccurrenceWearTab = () => { array={[ [ 'MW scale min', - mode.read ? toText(sourceData.mw_scale_min) : textField('mw_scale_min', { type: 'number' }), + mode.read + ? toText(sourceData.mw_scale_min) + : textField('mw_scale_min', { type: 'number', integerOnly: true, min: 0 }), ], [ 'MW scale max', - mode.read ? toText(sourceData.mw_scale_max) : textField('mw_scale_max', { type: 'number' }), + mode.read + ? toText(sourceData.mw_scale_max) + : textField('mw_scale_max', { type: 'number', integerOnly: true, min: 0 }), ], ['MW value', mode.read ? toText(sourceData.mw_value) : textField('mw_value', { type: 'number' })], ['Normalized Score', normalizedScoreText], diff --git a/frontend/src/components/Reference/Tabs/ReferenceTab.tsx b/frontend/src/components/Reference/Tabs/ReferenceTab.tsx index fa86be68..7f13dd23 100755 --- a/frontend/src/components/Reference/Tabs/ReferenceTab.tsx +++ b/frontend/src/components/Reference/Tabs/ReferenceTab.tsx @@ -98,9 +98,13 @@ export const ReferenceTab = () => { ? bigTextField(field.field_name! as keyof ReferenceDetailsType) : textField(field.field_name! as keyof ReferenceDetailsType) if (field.field_name && numberFields.includes(field.field_name)) { + const isPageField = field.field_name === 'start_page' || field.field_name === 'end_page' nonAuthorFieldsArray.push([ field.ref_field_name, - textField(field.field_name as keyof ReferenceDetailsType, { type: 'number' }), + textField(field.field_name as keyof ReferenceDetailsType, { + type: 'number', + ...(isPageField ? { integerOnly: true, min: 1 } : {}), + }), ]) } else if (field.field_name == 'exact_date') { nonAuthorFieldsArray.push([ diff --git a/frontend/src/components/Species/Tabs/SizeTab.tsx b/frontend/src/components/Species/Tabs/SizeTab.tsx index 11d8631b..a48d03fc 100755 --- a/frontend/src/components/Species/Tabs/SizeTab.tsx +++ b/frontend/src/components/Species/Tabs/SizeTab.tsx @@ -26,8 +26,8 @@ export const SizeTab = () => { ] const size = [ - ['Body Mass (g)', textField('body_mass', { type: 'number' })], - ['Brain Mass (g)', textField('brain_mass', { type: 'number' })], + ['Body Mass (g)', textField('body_mass', { type: 'number', integerOnly: true, min: 1 })], + ['Brain Mass (g)', textField('brain_mass', { type: 'number', integerOnly: true, min: 1 })], ['Snout-Vent Length', dropdown('sv_length', snoutVentLengthOptions, 'Snout-Vent Length')], ] diff --git a/frontend/src/components/Species/Tabs/TeethTab.tsx b/frontend/src/components/Species/Tabs/TeethTab.tsx index ddf9eea3..bb94b0a0 100755 --- a/frontend/src/components/Species/Tabs/TeethTab.tsx +++ b/frontend/src/components/Species/Tabs/TeethTab.tsx @@ -135,13 +135,13 @@ export const TeethTab = () => { const mesowear = [ ['Mesowear'], ['Type', dropdown('mesowear', mesowearOptions, 'Type')], - ['Cusp Relief Low (OR%)', textField('mw_or_low', { type: 'number' })], - ['Cusp Relief High (OR%)', textField('mw_or_high', { type: 'number' })], - ['Cusp Shape Sharp (CS%)', textField('mw_cs_sharp', { type: 'number' })], - ['Cusp Shape Rounded (CS%)', textField('mw_cs_round', { type: 'number' })], - ['Cusp Shape Blunt (CS%)', textField('mw_cs_blunt', { type: 'number' })], - ['Scale Minimum', textField('mw_scale_min', { type: 'number' })], - ['Scale Maximum', textField('mw_scale_max', { type: 'number' })], + ['Cusp Relief Low (OR%)', textField('mw_or_low', { type: 'number', integerOnly: true, min: 0, max: 100 })], + ['Cusp Relief High (OR%)', textField('mw_or_high', { type: 'number', integerOnly: true, min: 0, max: 100 })], + ['Cusp Shape Sharp (CS%)', textField('mw_cs_sharp', { type: 'number', integerOnly: true, min: 0, max: 100 })], + ['Cusp Shape Rounded (CS%)', textField('mw_cs_round', { type: 'number', integerOnly: true, min: 0, max: 100 })], + ['Cusp Shape Blunt (CS%)', textField('mw_cs_blunt', { type: 'number', integerOnly: true, min: 0, max: 100 })], + ['Scale Minimum', textField('mw_scale_min', { type: 'number', integerOnly: true, min: 0 })], + ['Scale Maximum', textField('mw_scale_max', { type: 'number', integerOnly: true, min: 0 })], ['Reported Value', textField('mw_value', { type: 'number' })], [ 'Normalized score', diff --git a/frontend/src/shared/validators/locality.ts b/frontend/src/shared/validators/locality.ts index 3ceec71a..d1684b0e 100755 --- a/frontend/src/shared/validators/locality.ts +++ b/frontend/src/shared/validators/locality.ts @@ -15,7 +15,18 @@ const validatePollenPercentage = (name: string, num: number) => { } const validateNonNegativeInteger = (name: string, num: number) => { - if (!Number.isInteger(num) || num < 0) return `${name} must be a positive integer.` + if (!Number.isInteger(num) || num < 0) return `${name} must be a non-negative integer.` + return +} + +const validateIntegerInRange = (name: string, num: number, min: number, max: number) => { + if (!Number.isInteger(num)) return `${name} must be an integer value` + if (num < min || num > max) return `${name} must be between ${min} and ${max}` + return +} + +const validatePositiveInteger = (name: string, num: number) => { + if (!Number.isInteger(num) || num < 1) return `${name} must be a positive integer.` return } @@ -202,6 +213,30 @@ export const validateLocality = ( name: 'Estimate of annual precipitation (mm)', asNumber: (num: number) => validateNonNegativeInteger('Estimate of annual precipitation (mm)', num), }, + altitude: { + name: 'Altitude (m)', + asNumber: (num: number) => validateIntegerInRange('Altitude (m)', num, -500, 9000), + }, + appr_num_spm: { + name: 'Approx. Number of Specimens', + asNumber: (num: number) => validatePositiveInteger('Approx. Number of Specimens', num), + }, + num_spm: { + name: 'Exact Number of Specimens', + asNumber: (num: number) => validatePositiveInteger('Exact Number of Specimens', num), + }, + num_quad: { + name: 'Number of Quadrats', + asNumber: (num: number) => validatePositiveInteger('Number of Quadrats', num), + }, + estimate_npp: { + name: 'Estimate of net primary productivity (g/m2/yr)', + asNumber: (num: number) => validateNonNegativeInteger('Estimate of net primary productivity (g/m2/yr)', num), + }, + pers_woody_cover: { + name: 'Woody cover percentage', + asNumber: (num: number) => validateIntegerInRange('Woody cover percentage', num, 0, 100), + }, } return validator>(validators, editData, fieldName) diff --git a/frontend/src/shared/validators/occurrence.ts b/frontend/src/shared/validators/occurrence.ts index a2623717..e9c2495e 100644 --- a/frontend/src/shared/validators/occurrence.ts +++ b/frontend/src/shared/validators/occurrence.ts @@ -23,6 +23,17 @@ const validatePositiveInteger = (name: string, value: number) => { return } +const validateNonNegativeInteger = (name: string, value: number) => { + if (!Number.isInteger(value) || value < 0) return `${name} must be a non-negative integer.` + return +} + +const validateIntegerInRange = (name: string, value: number, min: number, max: number) => { + if (!Number.isInteger(value)) return `${name} must be a whole number.` + if (value < min || value > max) return `${name} must be between ${min} and ${max}.` + return +} + const validatePositiveDecimal = (name: string, value: number) => { if (!Number.isFinite(value) || value <= 0) return `${name} must be a positive decimal number.` return @@ -61,15 +72,15 @@ export const validateOccurrence = (editData: EditableOccurrenceData, fieldName: body_mass: { name: 'Body mass', asNumber: value => validatePositiveInteger('Body mass', value) }, dc13_n: { name: 'δ13C N', asNumber: value => validatePositiveInteger('δ13C N', value) }, do18_n: { name: 'δ18O N', asNumber: value => validatePositiveInteger('δ18O N', value) }, - mw_or_high: { name: 'MW OR High', asNumber: value => validatePositiveInteger('MW OR High', value) }, - mw_or_low: { name: 'MW OR Low', asNumber: value => validatePositiveInteger('MW OR Low', value) }, - mw_cs_sharp: { name: 'MW CS Sharp', asNumber: value => validatePositiveInteger('MW CS Sharp', value) }, - mw_cs_round: { name: 'MW CS Round', asNumber: value => validatePositiveInteger('MW CS Round', value) }, - mw_cs_blunt: { name: 'MW CS Blunt', asNumber: value => validatePositiveInteger('MW CS Blunt', value) }, + mw_or_high: { name: 'MW OR High', asNumber: value => validateIntegerInRange('MW OR High', value, 0, 100) }, + mw_or_low: { name: 'MW OR Low', asNumber: value => validateIntegerInRange('MW OR Low', value, 0, 100) }, + mw_cs_sharp: { name: 'MW CS Sharp', asNumber: value => validateIntegerInRange('MW CS Sharp', value, 0, 100) }, + mw_cs_round: { name: 'MW CS Round', asNumber: value => validateIntegerInRange('MW CS Round', value, 0, 100) }, + mw_cs_blunt: { name: 'MW CS Blunt', asNumber: value => validateIntegerInRange('MW CS Blunt', value, 0, 100) }, mw_scale_min: { name: 'Scale Minimum', asNumber: value => { - const positivityError = validatePositiveInteger('Scale Minimum', value) + const positivityError = validateNonNegativeInteger('Scale Minimum', value) if (positivityError) return positivityError if (hasNumericValue(editData.mw_scale_max) && value > editData.mw_scale_max) return 'Scale Minimum cannot be greater than Scale Maximum.' @@ -79,7 +90,7 @@ export const validateOccurrence = (editData: EditableOccurrenceData, fieldName: mw_scale_max: { name: 'Scale Maximum', asNumber: value => { - const positivityError = validatePositiveInteger('Scale Maximum', value) + const positivityError = validateNonNegativeInteger('Scale Maximum', value) if (positivityError) return positivityError if (hasNumericValue(editData.mw_scale_min) && value < editData.mw_scale_min) return 'Scale Maximum cannot be less than Scale Minimum.' diff --git a/frontend/src/shared/validators/reference.ts b/frontend/src/shared/validators/reference.ts index 3bbe8ac5..15a8a26c 100755 --- a/frontend/src/shared/validators/reference.ts +++ b/frontend/src/shared/validators/reference.ts @@ -109,6 +109,13 @@ const yearCheck: (year: number) => ValidationError = (year: number) => { return null } +const positiveIntegerCheck: (name: string, num: number) => ValidationError = (name: string, num: number) => { + if (!Number.isFinite(num)) return `${name} must be a valid number` + if (!Number.isInteger(num)) return `${name} must be a whole number` + if (num < 1) return `${name} must be a positive integer` + return null +} + export type ReferenceFieldDisplayNames = Partial, string>> export type ReferenceDisplayLabelMap = Partial> @@ -217,12 +224,19 @@ export const validateReference = ( start_page: { name: 'start_page', required: false, - asNumber: true, + asNumber: (num: number) => positiveIntegerCheck('start_page', num), }, end_page: { name: 'end_page', required: false, - asNumber: true, + asNumber: (num: number) => { + const base = positiveIntegerCheck('end_page', num) + if (base) return base + if (typeof editData.start_page === 'number' && num < editData.start_page) { + return 'end_page must be greater than or equal to start_page' + } + return null + }, }, date_secondary: { name: 'date_secondary', diff --git a/frontend/src/shared/validators/species.ts b/frontend/src/shared/validators/species.ts index 91f9202f..abe59569 100755 --- a/frontend/src/shared/validators/species.ts +++ b/frontend/src/shared/validators/species.ts @@ -11,6 +11,22 @@ export const validateSpecies = ( editData: EditDataType, fieldName: keyof EditDataType ) => { + const validatePositiveInteger = (name: string, value: number) => { + if (!Number.isInteger(value) || value < 1) return `${name} must be a positive integer.` + return + } + + const validateNonNegativeInteger = (name: string, value: number) => { + if (!Number.isInteger(value) || value < 0) return `${name} must be a non-negative integer.` + return + } + + const validateIntegerInRange = (name: string, value: number, min: number, max: number) => { + if (!Number.isInteger(value)) return `${name} must be a whole number.` + if (value < min || value > max) return `${name} must be between ${min} and ${max}.` + return + } + const validators: Validators>> = { subclass_or_superorder_name: { name: 'Subclass or Superorder', @@ -103,11 +119,40 @@ export const validateSpecies = ( return }, }, + body_mass: { + name: 'Body Mass (g)', + asNumber: (value: number) => validatePositiveInteger('Body Mass (g)', value), + }, + brain_mass: { + name: 'Brain Mass (g)', + asNumber: (value: number) => validatePositiveInteger('Brain Mass (g)', value), + }, + mw_or_low: { + name: 'Cusp Relief Low (OR%)', + asNumber: (value: number) => validateIntegerInRange('Cusp Relief Low (OR%)', value, 0, 100), + }, + mw_or_high: { + name: 'Cusp Relief High (OR%)', + asNumber: (value: number) => validateIntegerInRange('Cusp Relief High (OR%)', value, 0, 100), + }, + mw_cs_sharp: { + name: 'Cusp Shape Sharp (CS%)', + asNumber: (value: number) => validateIntegerInRange('Cusp Shape Sharp (CS%)', value, 0, 100), + }, + mw_cs_round: { + name: 'Cusp Shape Rounded (CS%)', + asNumber: (value: number) => validateIntegerInRange('Cusp Shape Rounded (CS%)', value, 0, 100), + }, + mw_cs_blunt: { + name: 'Cusp Shape Blunt (CS%)', + asNumber: (value: number) => validateIntegerInRange('Cusp Shape Blunt (CS%)', value, 0, 100), + }, mw_scale_min: { name: 'Scale Minimum', asNumber: (value: number) => { - if (value < 0) return 'Scale Minimum cannot be negative.' + const nonNegativeError = validateNonNegativeInteger('Scale Minimum', value) + if (nonNegativeError) return nonNegativeError if (typeof editData.mw_scale_max === 'number' && value > editData.mw_scale_max) return 'Scale Minimum cannot be greater than Scale Maximum.' return @@ -116,7 +161,8 @@ export const validateSpecies = ( mw_scale_max: { name: 'Scale Maximum', asNumber: (value: number) => { - if (value < 0) return 'Scale Maximum cannot be negative.' + const nonNegativeError = validateNonNegativeInteger('Scale Maximum', value) + if (nonNegativeError) return nonNegativeError if (typeof editData.mw_scale_min === 'number' && value < editData.mw_scale_min) return 'Scale Maximum cannot be less than Scale Minimum.' return