'use client'; import { useState } from 'react'; import { ORTE, ARBEITSZEIT_KUNDE_FIX, type ArbeitszeitEintrag } from '@/types/arbeitszeit'; import { toDecimalHours, mehrarbeit, weekdayName, formatHours, hatZeiten, vertriebEditierbar, } from '@/lib/calc'; import CustomSelect from './CustomSelect'; import TimeInput from './TimeInput'; interface Props { editEntry?: ArbeitszeitEintrag | null; pause: number; onSaved: () => void; onCancel: () => void; } function todayDate(): string { const now = new Date(); const pad = (n: number) => String(n).padStart(2, '0'); return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`; } // Komma als Dezimaltrenner erlauben function toNum(raw: string): number { const n = parseFloat(raw.replace(',', '.')); 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). const [datum, setDatum] = useState(editEntry?.datum ?? todayDate()); const [ort, setOrt] = useState(editEntry?.ort ?? ORTE[0]); const [beginn, setBeginn] = useState(editEntry?.beginn ?? ''); const [ende, setEnde] = useState(editEntry?.ende ?? ''); // Pause pro Eintrag in Minuten; Standard ist die globale Einstellung (Stunden → Minuten). const [pauseMin, setPauseMin] = useState( String(Math.round((editEntry?.pause ?? pause) * 60)) ); const [reiseBeginn, setReiseBeginn] = useState(editEntry?.reise_beginn ?? ''); const [reiseEnde, setReiseEnde] = useState(editEntry?.reise_ende ?? ''); const [vertrieb, setVertrieb] = useState(editEntry ? numToStr(editEntry.vertriebsunterstuetzung) : '0'); const [kommentar, setKommentar] = useState(editEntry?.kommentar ?? ''); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); const zeiten = hatZeiten(beginn, ende); const arbeitszeit = toDecimalHours(beginn, ende); const pauseStd = Math.max(0, toNum(pauseMin)) / 60; const mehr = mehrarbeit(arbeitszeit, pauseStd); const reisezeit = toDecimalHours(reiseBeginn, reiseEnde); const reiseZeiten = hatZeiten(reiseBeginn, reiseEnde); const vertriebFrei = vertriebEditierbar(reisezeit); // Vertriebsunterstützung an die (aus den Reise-Zeiten berechnete) Reisezeit koppeln // (siehe applyVertriebsregel): // =0 ⇒ 0, <2 ⇒ = Reisezeit, ≥2 ⇒ 2 beim Überschreiten der Schwelle, danach frei änderbar. function koppleVertrieb(altReise: number, neuReise: number) { if (neuReise <= 0) setVertrieb('0'); else if (neuReise < 2) setVertrieb(numToStr(neuReise)); else if (altReise < 2) setVertrieb('2'); // Schwelle 2h überschritten → Standard 2h // neu ≥ 2 und alt ≥ 2: bestehenden (ggf. geänderten) Wert beibehalten } function handleReiseBeginn(raw: string) { koppleVertrieb(reisezeit, toDecimalHours(raw, reiseEnde)); setReiseBeginn(raw); } function handleReiseEnde(raw: string) { koppleVertrieb(reisezeit, toDecimalHours(reiseBeginn, raw)); setReiseEnde(raw); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(''); setSuccess(false); const beginnLeer = beginn === ''; const endeLeer = ende === ''; if (beginnLeer !== endeLeer) { setError('Bitte Start- und Endzeit gemeinsam angeben oder beide leer lassen.'); return; } if (!beginnLeer && !endeLeer && arbeitszeit <= 0) { setError('Endzeit muss nach der Startzeit liegen.'); return; } const reiseBeginnLeer = reiseBeginn === ''; const reiseEndeLeer = reiseEnde === ''; if (reiseBeginnLeer !== reiseEndeLeer) { setError('Bitte Reise-Start und -Ende gemeinsam angeben oder beide leer lassen.'); return; } if (!reiseBeginnLeer && !reiseEndeLeer && reisezeit <= 0) { setError('Reise-Ende muss nach dem Reise-Start liegen.'); return; } setSaving(true); const body = { datum, ort, beginn, ende, reise_beginn: reiseBeginn, reise_ende: reiseEnde, pause: pauseStd, vertriebsunterstuetzung: toNum(vertrieb), kommentar, }; const url = editEntry ? `/api/arbeitszeit/${editEntry.id}` : '/api/arbeitszeit'; const method = editEntry ? 'PUT' : 'POST'; try { const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || 'Fehler beim Speichern.'); } setSuccess(true); setTimeout(() => setSuccess(false), 4000); // Beim Anlegen die Eingabefelder wieder auf die Startwerte zurücksetzen. if (!editEntry) { setDatum(todayDate()); setOrt(ORTE[0]); setBeginn(''); setEnde(''); setPauseMin(String(Math.round(pause * 60))); setReiseBeginn(''); setReiseEnde(''); setVertrieb('0'); setKommentar(''); } onSaved(); } catch (err) { setError(err instanceof Error ? err.message : 'Fehler beim Speichern.'); } finally { setSaving(false); } } const labelCls = 'block text-xs font-bold text-gray-700 mb-0.5'; const numCls = 'w-24 px-2 py-1 border-2 border-gray-400 rounded-lg bg-white text-gray-900 text-sm focus:border-green-600 focus:outline-none'; const wt = weekdayName(datum); return (
{/* Datum + Wochentag / Ort */}
e.target.value && setDatum(e.target.value)} required className="px-2 py-1 border-2 border-gray-400 rounded-lg bg-white text-sm text-gray-900 focus:border-green-600 focus:outline-none" /> {wt}
({ value: o, label: o }))} onChange={setOrt} />
{/* Beginn / Ende / Reisezeit / Vertrieb */}
setPauseMin(e.target.value.replace(/[^0-9]/g, ''))} className={numCls} />
setVertrieb(e.target.value)} disabled={!vertriebFrei} title={vertriebFrei ? undefined : 'Wird automatisch aus der Reisezeit berechnet (< 2 h)'} className={`${numCls} disabled:bg-gray-100 disabled:text-gray-500`} />
{/* Kommentar */}