diff --git a/client/src/components/LogEntryEditor.tsx b/client/src/components/LogEntryEditor.tsx index 591f435..f6fe772 100644 --- a/client/src/components/LogEntryEditor.tsx +++ b/client/src/components/LogEntryEditor.tsx @@ -43,7 +43,13 @@ import { computeTrackStats, formatTrackStats } from '../utils/trackStats.js' import { computeFuelPerMotorHour, formatFuelPerMotorHour } from '../utils/fuelStats.js' import { useRegisterUnsavedChanges } from '../context/UnsavedChangesContext.tsx' import TankLiterInput from './TankLiterInput.tsx' -import { extractTankCapacitiesFromYacht, type VesselTankCapacities } from '../utils/tankCapacity.js' +import { + computeEveningTankMaxLiters, + computeRefilledTankMaxLiters, + extractTankCapacitiesFromYacht, + formatTankLitersForInput, + type VesselTankCapacities +} from '../utils/tankCapacity.js' function emptyTankLevels() { return { morning: 0, refilled: 0, evening: 0, consumption: 0 } @@ -279,6 +285,36 @@ export default function LogEntryEditor({ const tankCapacityTooltip = t('logs.tank_capacity_tooltip') + const fwRefilledMax = useMemo( + () => computeRefilledTankMaxLiters(fwMorning, tankCapacities.freshwaterCapacityL), + [fwMorning, tankCapacities.freshwaterCapacityL] + ) + + const fwEveningMax = useMemo( + () => + computeEveningTankMaxLiters( + fwMorning, + fwRefilled, + tankCapacities.freshwaterCapacityL + ), + [fwMorning, fwRefilled, tankCapacities.freshwaterCapacityL] + ) + + const fuelRefilledMax = useMemo( + () => computeRefilledTankMaxLiters(fuelMorning, tankCapacities.fuelCapacityL), + [fuelMorning, tankCapacities.fuelCapacityL] + ) + + const fuelEveningMax = useMemo( + () => + computeEveningTankMaxLiters( + fuelMorning, + fuelRefilled, + tankCapacities.fuelCapacityL + ), + [fuelMorning, fuelRefilled, tankCapacities.fuelCapacityL] + ) + const currentFingerprint = useMemo(() => { const payload = buildPayloadForSigning() return JSON.stringify({ @@ -538,6 +574,38 @@ export default function LogEntryEditor({ setFuelConsumption(cons >= 0 ? String(cons) : '0') }, [fuelMorning, fuelRefilled, fuelEvening]) + useEffect(() => { + if (fwRefilledMax == null) return + const refilled = parseFloat(fwRefilled) || 0 + if (refilled > fwRefilledMax) { + setFwRefilled(formatTankLitersForInput(fwRefilledMax)) + } + }, [fwRefilledMax, fwMorning]) + + useEffect(() => { + if (fwEveningMax == null) return + const evening = parseFloat(fwEvening) || 0 + if (evening > fwEveningMax) { + setFwEvening(formatTankLitersForInput(fwEveningMax)) + } + }, [fwEveningMax, fwMorning, fwRefilled]) + + useEffect(() => { + if (fuelRefilledMax == null) return + const refilled = parseFloat(fuelRefilled) || 0 + if (refilled > fuelRefilledMax) { + setFuelRefilled(formatTankLitersForInput(fuelRefilledMax)) + } + }, [fuelRefilledMax, fuelMorning]) + + useEffect(() => { + if (fuelEveningMax == null) return + const evening = parseFloat(fuelEvening) || 0 + if (evening > fuelEveningMax) { + setFuelEvening(formatTankLitersForInput(fuelEveningMax)) + } + }, [fuelEveningMax, fuelMorning, fuelRefilled]) + // Load yacht sails and tank capacities useEffect(() => { async function loadYachtMeta() { @@ -1249,7 +1317,7 @@ export default function LogEntryEditor({ label={t('logs.refilled')} value={fwRefilled} onChange={setFwRefilled} - maxLiters={tankCapacities.freshwaterCapacityL} + maxLiters={fwRefilledMax ?? tankCapacities.freshwaterCapacityL} disabled={saving || readOnly} titleTooltip={tankCapacityTooltip} /> @@ -1258,7 +1326,7 @@ export default function LogEntryEditor({ label={t('logs.evening')} value={fwEvening} onChange={setFwEvening} - maxLiters={tankCapacities.freshwaterCapacityL} + maxLiters={fwEveningMax} disabled={saving || readOnly} titleTooltip={tankCapacityTooltip} /> @@ -1298,7 +1366,7 @@ export default function LogEntryEditor({ label={t('logs.refilled')} value={fuelRefilled} onChange={setFuelRefilled} - maxLiters={tankCapacities.fuelCapacityL} + maxLiters={fuelRefilledMax ?? tankCapacities.fuelCapacityL} disabled={saving || readOnly} titleTooltip={tankCapacityTooltip} /> @@ -1307,7 +1375,7 @@ export default function LogEntryEditor({ label={t('logs.evening')} value={fuelEvening} onChange={setFuelEvening} - maxLiters={tankCapacities.fuelCapacityL} + maxLiters={fuelEveningMax} disabled={saving || readOnly} titleTooltip={tankCapacityTooltip} /> diff --git a/client/src/utils/tankCapacity.test.ts b/client/src/utils/tankCapacity.test.ts index c0705d9..a917360 100644 --- a/client/src/utils/tankCapacity.test.ts +++ b/client/src/utils/tankCapacity.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from 'vitest' import { clampTankLiters, + computeEveningTankMaxLiters, + computeRefilledTankMaxLiters, extractTankCapacitiesFromYacht, formatTankLitersForInput, parseOptionalTankLiters, @@ -44,4 +46,17 @@ describe('tankCapacity', () => { expect(clampTankLiters(-5, 200)).toBe(0) expect(clampTankLiters(50)).toBe(50) }) + + it('computes refilled max as capacity minus morning', () => { + expect(computeRefilledTankMaxLiters('10', 60)).toBe(50) + expect(computeRefilledTankMaxLiters('60', 60)).toBeUndefined() + expect(computeRefilledTankMaxLiters('10', undefined)).toBeUndefined() + }) + + it('computes evening max as morning plus refilled capped by capacity', () => { + expect(computeEveningTankMaxLiters('10', '20', 60)).toBe(30) + expect(computeEveningTankMaxLiters('40', '40', 60)).toBe(60) + expect(computeEveningTankMaxLiters('10', '20')).toBe(30) + expect(computeEveningTankMaxLiters('0', '0', 60)).toBeUndefined() + }) }) diff --git a/client/src/utils/tankCapacity.ts b/client/src/utils/tankCapacity.ts index 81e0ee0..44a2833 100644 --- a/client/src/utils/tankCapacity.ts +++ b/client/src/utils/tankCapacity.ts @@ -50,6 +50,47 @@ export function extractTankCapacitiesFromYacht(decrypted: unknown): VesselTankCa return capacities } +/** Parse a liter amount from form state (string). */ +export function parseTankLitersFromInput(input: string): number { + const trimmed = input.trim().replace(',', '.') + if (!trimmed) return 0 + const parsed = Number(trimmed) + return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0 +} + +/** + * Max for refilled amount: remaining capacity after morning level. + * Returns undefined when no positive max (no slider). + */ +export function computeRefilledTankMaxLiters( + morningInput: string, + tankCapacityL?: number +): number | undefined { + if (tankCapacityL == null || tankCapacityL <= 0) return undefined + const remaining = tankCapacityL - parseTankLitersFromInput(morningInput) + if (remaining <= 0) return undefined + return remaining +} + +/** + * Max for evening fill level: morning + refilled, capped by tank capacity when known. + * Returns undefined when no positive max (no slider). + */ +export function computeEveningTankMaxLiters( + morningInput: string, + refilledInput: string, + tankCapacityL?: number +): number | undefined { + const sum = parseTankLitersFromInput(morningInput) + parseTankLitersFromInput(refilledInput) + if (sum <= 0) return undefined + + if (tankCapacityL != null && tankCapacityL > 0) { + return Math.min(tankCapacityL, sum) + } + + return sum +} + /** Clamp numeric liter value to [0, max] when max is known. */ export function clampTankLiters(value: number, maxLiters?: number): number { const clamped = Math.max(0, value)