diff --git a/components/ArbeitszeitForm.tsx b/components/ArbeitszeitForm.tsx index 87ef65f..6dc182f 100644 --- a/components/ArbeitszeitForm.tsx +++ b/components/ArbeitszeitForm.tsx @@ -2,7 +2,14 @@ import { useState } from 'react'; import { ORTE, ARBEITSZEIT_KUNDE_FIX, type ArbeitszeitEintrag } from '@/types/arbeitszeit'; -import { toDecimalHours, mehrarbeit, weekdayName, formatHours, hatZeiten } from '@/lib/calc'; +import { + toDecimalHours, + mehrarbeit, + weekdayName, + formatHours, + hatZeiten, + vertriebEditierbar, +} from '@/lib/calc'; import CustomSelect from './CustomSelect'; import TimeInput from './TimeInput'; @@ -25,6 +32,11 @@ function toNum(raw: string): number { return isNaN(n) ? 0 : n; } +// Zahl als deutscher Dezimalstring (Komma) +function numToStr(n: number): string { + return String(n).replace('.', ','); +} + export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }: Props) { // Diese Komponente wird beim Wechsel von editEntry über `key` neu gemountet, // daher genügt eine einmalige Initialisierung aus editEntry (kein Sync-Effekt nötig). @@ -32,8 +44,8 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }: const [ort, setOrt] = useState(editEntry?.ort ?? ORTE[0]); const [beginn, setBeginn] = useState(editEntry?.beginn ?? ''); const [ende, setEnde] = useState(editEntry?.ende ?? ''); - const [reisezeit, setReisezeit] = useState(editEntry ? String(editEntry.reisezeit) : '0'); - const [vertrieb, setVertrieb] = useState(editEntry ? String(editEntry.vertriebsunterstuetzung) : '2'); + const [reisezeit, setReisezeit] = useState(editEntry ? numToStr(editEntry.reisezeit) : '0'); + const [vertrieb, setVertrieb] = useState(editEntry ? numToStr(editEntry.vertriebsunterstuetzung) : '0'); const [kommentar, setKommentar] = useState(editEntry?.kommentar ?? ''); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); @@ -42,6 +54,19 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }: const zeiten = hatZeiten(beginn, ende); const arbeitszeit = toDecimalHours(beginn, ende); const mehr = mehrarbeit(arbeitszeit, pause); + const vertriebFrei = vertriebEditierbar(toNum(reisezeit)); + + // Vertriebsunterstützung an die Reisezeit koppeln (siehe applyVertriebsregel): + // =0 ⇒ 0, <2 ⇒ = Reisezeit, ≥2 ⇒ 2 beim Überschreiten der Schwelle, danach frei änderbar. + function handleReisezeit(raw: string) { + const alt = toNum(reisezeit); + const neu = toNum(raw); + setReisezeit(raw); + if (neu <= 0) setVertrieb('0'); + else if (neu < 2) setVertrieb(numToStr(neu)); + else if (alt < 2) setVertrieb('2'); // Schwelle 2h überschritten → Standard 2h + // neu ≥ 2 und alt ≥ 2: bestehenden (ggf. geänderten) Wert beibehalten + } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); @@ -145,7 +170,7 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }: type="text" inputMode="decimal" value={reisezeit} - onChange={(e) => setReisezeit(e.target.value)} + onChange={(e) => handleReisezeit(e.target.value)} className={numCls} /> @@ -156,7 +181,9 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }: inputMode="decimal" value={vertrieb} onChange={(e) => setVertrieb(e.target.value)} - className={numCls} + disabled={!vertriebFrei} + title={vertriebFrei ? undefined : 'Wird automatisch aus der Reisezeit berechnet (< 2 h)'} + className={`${numCls} disabled:bg-gray-100 disabled:text-gray-500`} /> diff --git a/lib/calc.ts b/lib/calc.ts index 140b215..37e20fc 100644 --- a/lib/calc.ts +++ b/lib/calc.ts @@ -43,6 +43,23 @@ export function hatZeiten(beginn: string, ende: string): boolean { return parseHM(beginn) !== null && parseHM(ende) !== null; } +/** True, wenn die Vertriebsunterstützung bei dieser Reisezeit frei änderbar ist (Reisezeit ≥ 2h). */ +export function vertriebEditierbar(reisezeit: number): boolean { + return reisezeit >= 2; +} + +/** + * Vertriebsunterstützung an die Reisezeit koppeln: + * Reisezeit = 0 → 0 + * 0 < Reisezeit < 2 → = Reisezeit + * Reisezeit ≥ 2 → vorhandener Wert (änderbar), Standard 2 + */ +export function applyVertriebsregel(reisezeit: number, vorhanden: number): number { + if (reisezeit <= 0) return 0; + if (reisezeit < 2) return reisezeit; + return vorhanden; +} + /** Deutscher Wochentagsname für ein "YYYY-MM-DD"-Datum, oder "" bei ungültig. */ export function weekdayName(datum: string): string { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datum ?? ''); diff --git a/lib/validate.ts b/lib/validate.ts index 2bdc5f6..a1d0524 100644 --- a/lib/validate.ts +++ b/lib/validate.ts @@ -1,5 +1,5 @@ import { ORTE, type ArbeitszeitInput, type Ort } from '@/types/arbeitszeit'; -import { parseHM } from './calc'; +import { parseHM, applyVertriebsregel } from './calc'; /** * Validiert und normalisiert einen JSON-Body zu einem ArbeitszeitInput. @@ -25,10 +25,13 @@ export function parseArbeitszeitInput(body: unknown): ArbeitszeitInput { } const reisezeit = toNumber(b.reisezeit, 0); - const vertriebsunterstuetzung = toNumber(b.vertriebsunterstuetzung, 2); + let vertriebsunterstuetzung = toNumber(b.vertriebsunterstuetzung, 2); if (reisezeit < 0 || vertriebsunterstuetzung < 0) { throw new Error('Zeitwerte dürfen nicht negativ sein.'); } + // Regel: Vertriebsunterstützung an die Reisezeit koppeln. + // 0h ⇒ 0; <2h ⇒ = Reisezeit; ≥2h ⇒ bereitgestellter Wert (änderbar, default 2). + vertriebsunterstuetzung = applyVertriebsregel(reisezeit, vertriebsunterstuetzung); const kommentar = String(b.kommentar ?? '').slice(0, 500);