feat: mirror the new backend numeric bounds as HTML min/max on inputs

The previous two commits added @Min(0)/@Max(100) validation and a
sign-inversion guard on the backend, but the number inputs themselves
had no min/max attributes - a user could still type a negative weight,
quantity, or an out-of-range percentage and only find out via a
generic "failed" toast after submitting, with no indication of why.

Added min={0} (and max={100} on water-content percentages) to every
input for a field the backend now bounds: harvest weights/super
count, honey-batch amount/water content, honey measurements, feeding
amount, extraction amount/water content, stock item quantity/min
threshold, and varroa mite count/duration/calculated value - across
both the create and edit form for each.

The stock-movement quantity input is the one exception requiring
care: min is only applied when type !== "ADJUSTMENT", matching the
backend guard, since a correction movement must still be allowed to
go negative. Added a short explanatory line under that field too.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-09 11:12:24 +02:00
1 parent b208f52fd1
commit 96207d0096
14 files changed
+34

No files matched your search

@@ -108,6 +108,7 @@ export function FeedingEditDialog({ feeding }: { feeding: Feeding }) {
<Input <Input
id="feeding-edit-amount" id="feeding-edit-amount"
type="number" type="number"
min={0}
value={amount} value={amount}
onChange={(event) => setAmount(event.target.value)} onChange={(event) => setAmount(event.target.value)}
/> />
@@ -111,6 +111,7 @@ export function VarroaEditDialog({ measurement }: { measurement: VarroaMeasureme
<Input <Input
id="varroa-edit-count" id="varroa-edit-count"
type="number" type="number"
min={0}
value={miteCount} value={miteCount}
onChange={(event) => setMiteCount(event.target.value)} onChange={(event) => setMiteCount(event.target.value)}
/> />
@@ -120,6 +121,7 @@ export function VarroaEditDialog({ measurement }: { measurement: VarroaMeasureme
<Input <Input
id="varroa-edit-duration" id="varroa-edit-duration"
type="number" type="number"
min={0}
value={durationMinutes} value={durationMinutes}
onChange={(event) => setDurationMinutes(event.target.value)} onChange={(event) => setDurationMinutes(event.target.value)}
/> />
@@ -130,6 +132,7 @@ export function VarroaEditDialog({ measurement }: { measurement: VarroaMeasureme
id="varroa-edit-calculated" id="varroa-edit-calculated"
type="number" type="number"
step="0.1" step="0.1"
min={0}
value={calculatedValue} value={calculatedValue}
onChange={(event) => setCalculatedValue(event.target.value)} onChange={(event) => setCalculatedValue(event.target.value)}
/> />
@@ -78,6 +78,7 @@ export function ExtractionEditDialog({ extraction }: { extraction: Extraction })
<Input <Input
id="extraction-edit-amount" id="extraction-edit-amount"
type="number" type="number"
min={0}
value={amount} value={amount}
onChange={(event) => setAmount(event.target.value)} onChange={(event) => setAmount(event.target.value)}
/> />
@@ -97,6 +98,8 @@ export function ExtractionEditDialog({ extraction }: { extraction: Extraction })
<Input <Input
id="extraction-edit-water" id="extraction-edit-water"
type="number" type="number"
min={0}
max={100}
value={waterContent} value={waterContent}
onChange={(event) => setWaterContent(event.target.value)} onChange={(event) => setWaterContent(event.target.value)}
/> />
@@ -123,6 +123,7 @@ export default function NewExtractionPage() {
id="extraction-amount" id="extraction-amount"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
className="h-11" className="h-11"
value={amount} value={amount}
onChange={(event) => setAmount(event.target.value)} onChange={(event) => setAmount(event.target.value)}
@@ -143,6 +144,8 @@ export default function NewExtractionPage() {
id="extraction-water" id="extraction-water"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
max={100}
className="h-11" className="h-11"
value={waterContent} value={waterContent}
onChange={(event) => setWaterContent(event.target.value)} onChange={(event) => setWaterContent(event.target.value)}
@@ -158,6 +158,7 @@ export default function NewFeedingPage() {
id="feeding-amount" id="feeding-amount"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
required required
className="h-11" className="h-11"
value={amount} value={amount}
@@ -88,6 +88,7 @@ export function HarvestEditDialog({ harvest }: { harvest: Harvest }) {
<Input <Input
id="harvest-edit-gross-weight" id="harvest-edit-gross-weight"
type="number" type="number"
min={0}
value={grossWeight} value={grossWeight}
onChange={(event) => setGrossWeight(event.target.value)} onChange={(event) => setGrossWeight(event.target.value)}
/> />
@@ -97,6 +98,7 @@ export function HarvestEditDialog({ harvest }: { harvest: Harvest }) {
<Input <Input
id="harvest-edit-weight" id="harvest-edit-weight"
type="number" type="number"
min={0}
value={netWeight} value={netWeight}
onChange={(event) => setNetWeight(event.target.value)} onChange={(event) => setNetWeight(event.target.value)}
/> />
@@ -106,6 +108,7 @@ export function HarvestEditDialog({ harvest }: { harvest: Harvest }) {
<Input <Input
id="harvest-edit-supers" id="harvest-edit-supers"
type="number" type="number"
min={0}
value={honeySuperCount} value={honeySuperCount}
onChange={(event) => setHoneySuperCount(event.target.value)} onChange={(event) => setHoneySuperCount(event.target.value)}
/> />
@@ -202,6 +202,7 @@ export default function NewHarvestPage() {
id="harvest-super-count" id="harvest-super-count"
type="number" type="number"
inputMode="numeric" inputMode="numeric"
min={0}
className="h-11" className="h-11"
value={honeySuperCount} value={honeySuperCount}
onChange={(event) => setHoneySuperCount(event.target.value)} onChange={(event) => setHoneySuperCount(event.target.value)}
@@ -213,6 +214,7 @@ export default function NewHarvestPage() {
id="harvest-gross-weight" id="harvest-gross-weight"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
className="h-11" className="h-11"
value={grossWeight} value={grossWeight}
onChange={(event) => setGrossWeight(event.target.value)} onChange={(event) => setGrossWeight(event.target.value)}
@@ -224,6 +226,7 @@ export default function NewHarvestPage() {
id="harvest-net-weight" id="harvest-net-weight"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
className="h-11" className="h-11"
value={netWeight} value={netWeight}
onChange={(event) => setNetWeight(event.target.value)} onChange={(event) => setNetWeight(event.target.value)}
@@ -70,6 +70,8 @@ export function AddMeasurementDialog({ honeyBatchId }: { honeyBatchId: string })
id="measurement-water" id="measurement-water"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
max={100}
required required
value={waterContent} value={waterContent}
onChange={(event) => setWaterContent(event.target.value)} onChange={(event) => setWaterContent(event.target.value)}
@@ -77,6 +77,7 @@ function ExtractionEditForm({
<Input <Input
id="extraction-edit-amount" id="extraction-edit-amount"
type="number" type="number"
min={0}
value={amount} value={amount}
onChange={(event) => setAmount(event.target.value)} onChange={(event) => setAmount(event.target.value)}
/> />
@@ -96,6 +97,8 @@ function ExtractionEditForm({
id="extraction-edit-water" id="extraction-edit-water"
type="number" type="number"
step="0.1" step="0.1"
min={0}
max={100}
value={waterContent} value={waterContent}
onChange={(event) => setWaterContent(event.target.value)} onChange={(event) => setWaterContent(event.target.value)}
/> />
@@ -72,6 +72,7 @@ export function HoneyBatchEditDialog({ honeyBatch }: { honeyBatch: HoneyBatch })
id="batch-edit-amount" id="batch-edit-amount"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
value={amount} value={amount}
onChange={(event) => setAmount(event.target.value)} onChange={(event) => setAmount(event.target.value)}
/> />
@@ -82,6 +83,8 @@ export function HoneyBatchEditDialog({ honeyBatch }: { honeyBatch: HoneyBatch })
id="batch-edit-water" id="batch-edit-water"
type="number" type="number"
step="0.1" step="0.1"
min={0}
max={100}
value={waterContent} value={waterContent}
onChange={(event) => setWaterContent(event.target.value)} onChange={(event) => setWaterContent(event.target.value)}
/> />
@@ -109,6 +109,7 @@ export function CreateStockItemDialog() {
id="item-quantity" id="item-quantity"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
value={quantity} value={quantity}
onChange={(event) => setQuantity(event.target.value)} onChange={(event) => setQuantity(event.target.value)}
/> />
@@ -112,6 +112,7 @@ export function EditStockItemDialog({ item }: { item: StockItem }) {
id="item-edit-min-threshold" id="item-edit-min-threshold"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={0}
value={minThreshold} value={minThreshold}
onChange={(event) => setMinThreshold(event.target.value)} onChange={(event) => setMinThreshold(event.target.value)}
/> />
@@ -87,10 +87,15 @@ export function RecordMovementDialog({ stockItemId }: { stockItemId: string }) {
id="movement-quantity" id="movement-quantity"
type="number" type="number"
inputMode="decimal" inputMode="decimal"
min={type === "ADJUSTMENT" ? undefined : 0}
required required
value={quantity} value={quantity}
onChange={(event) => setQuantity(event.target.value)} onChange={(event) => setQuantity(event.target.value)}
/> />
<p className="text-xs text-muted-foreground">
Bei Eingang/Ausgang/Umlagerung immer positiv angeben - die Richtung ergibt sich aus
der Art. Nur bei Korrektur ist ein negativer Wert möglich.
</p>
</div> </div>
{error ? ( {error ? (
<p role="alert" className="text-sm text-destructive"> <p role="alert" className="text-sm text-destructive">
@@ -149,6 +149,7 @@ export default function NewVarroaMeasurementPage() {
id="varroa-mite-count" id="varroa-mite-count"
type="number" type="number"
inputMode="numeric" inputMode="numeric"
min={0}
className="h-11" className="h-11"
value={miteCount} value={miteCount}
onChange={(event) => setMiteCount(event.target.value)} onChange={(event) => setMiteCount(event.target.value)}
@@ -160,6 +161,7 @@ export default function NewVarroaMeasurementPage() {
id="varroa-duration" id="varroa-duration"
type="number" type="number"
inputMode="numeric" inputMode="numeric"
min={0}
className="h-11" className="h-11"
value={durationMinutes} value={durationMinutes}
onChange={(event) => setDurationMinutes(event.target.value)} onChange={(event) => setDurationMinutes(event.target.value)}