Start-/Endzeit optional: ohne Zeiten Arbeitszeit Kunde & Mehrarbeit leer
- Start- und Endzeit nicht mehr vorbelegt (leeres Feld, Platzhalter HH:MM) - TimeInput erlaubt Leeren eines Feldes - Validierung: beide Zeiten zusammen angeben oder beide leer lassen - Ohne Zeiten zeigen Vorschau und Liste "—" für Arbeitszeit Kunde und Mehrarbeit (Eintrag mit nur Reisezeit/Vertriebsunterstützung möglich) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { ORTE, ARBEITSZEIT_KUNDE_FIX, type ArbeitszeitEintrag } from '@/types/arbeitszeit';
|
||||
import { toDecimalHours, mehrarbeit, weekdayName, formatHours } from '@/lib/calc';
|
||||
import { toDecimalHours, mehrarbeit, weekdayName, formatHours, hatZeiten } from '@/lib/calc';
|
||||
import CustomSelect from './CustomSelect';
|
||||
import TimeInput from './TimeInput';
|
||||
|
||||
@@ -30,8 +30,8 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
// daher genügt eine einmalige Initialisierung aus editEntry (kein Sync-Effekt nötig).
|
||||
const [datum, setDatum] = useState(editEntry?.datum ?? todayDate());
|
||||
const [ort, setOrt] = useState<string>(editEntry?.ort ?? ORTE[0]);
|
||||
const [beginn, setBeginn] = useState(editEntry?.beginn ?? '09:00');
|
||||
const [ende, setEnde] = useState(editEntry?.ende ?? '17:00');
|
||||
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 [kommentar, setKommentar] = useState(editEntry?.kommentar ?? '');
|
||||
@@ -39,6 +39,7 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const zeiten = hatZeiten(beginn, ende);
|
||||
const arbeitszeit = toDecimalHours(beginn, ende);
|
||||
const mehr = mehrarbeit(arbeitszeit, pause);
|
||||
|
||||
@@ -47,7 +48,13 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
setError('');
|
||||
setSuccess(false);
|
||||
|
||||
if (arbeitszeit <= 0) {
|
||||
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;
|
||||
}
|
||||
@@ -172,16 +179,19 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
{/* Berechnungs-Vorschau */}
|
||||
<div className="flex flex-wrap gap-x-6 gap-y-1 text-sm bg-[#EEF7EE] border border-green-200 rounded-lg px-3 py-2">
|
||||
<span>
|
||||
Arbeitszeit: <strong>{formatHours(arbeitszeit)} h</strong>
|
||||
Arbeitszeit: <strong>{zeiten ? `${formatHours(arbeitszeit)} h` : '—'}</strong>
|
||||
</span>
|
||||
<span>
|
||||
Arbeitszeit Kunde: <strong>{formatHours(ARBEITSZEIT_KUNDE_FIX)} h</strong>
|
||||
Arbeitszeit Kunde: <strong>{zeiten ? `${formatHours(ARBEITSZEIT_KUNDE_FIX)} h` : '—'}</strong>
|
||||
</span>
|
||||
<span>
|
||||
Pause: <strong>{formatHours(pause)} h</strong>
|
||||
</span>
|
||||
<span>
|
||||
Mehrarbeit: <strong className={mehr < 0 ? 'text-red-600' : 'text-green-800'}>{formatHours(mehr)} h</strong>
|
||||
Mehrarbeit:{' '}
|
||||
<strong className={zeiten ? (mehr < 0 ? 'text-red-600' : 'text-green-800') : ''}>
|
||||
{zeiten ? `${formatHours(mehr)} h` : '—'}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ARBEITSZEIT_KUNDE_FIX, type ArbeitszeitEintrag } from '@/types/arbeitszeit';
|
||||
import { toDecimalHours, mehrarbeit, weekdayName, formatHours } from '@/lib/calc';
|
||||
import { toDecimalHours, mehrarbeit, weekdayName, formatHours, hatZeiten } from '@/lib/calc';
|
||||
|
||||
interface Props {
|
||||
pause: number;
|
||||
@@ -131,14 +131,15 @@ export default function ArbeitszeitList({ pause, refreshKey, onEdit, limit = 15,
|
||||
</td>
|
||||
</tr>
|
||||
) : entries.map((e) => {
|
||||
const zeiten = hatZeiten(e.beginn, e.ende);
|
||||
const mehr = mehrarbeit(toDecimalHours(e.beginn, e.ende), pause);
|
||||
return (
|
||||
<tr key={e.id} className="hover:bg-gray-50">
|
||||
<td className={`${cell} whitespace-nowrap`}>{formatDate(e.datum)}</td>
|
||||
<td className={cell}>{weekdayName(e.datum)}</td>
|
||||
<td className={cell}>{e.ort}</td>
|
||||
<td className={`${cell} text-right`}>{formatHours(ARBEITSZEIT_KUNDE_FIX)}</td>
|
||||
<td className={`${cell} text-right ${mehr < 0 ? 'text-red-600' : ''}`}>{formatHours(mehr)}</td>
|
||||
<td className={`${cell} text-right`}>{zeiten ? formatHours(ARBEITSZEIT_KUNDE_FIX) : '—'}</td>
|
||||
<td className={`${cell} text-right ${zeiten && mehr < 0 ? 'text-red-600' : ''}`}>{zeiten ? formatHours(mehr) : '—'}</td>
|
||||
<td className={`${cell} text-right`}>{formatHours(e.reisezeit)}</td>
|
||||
<td className={`${cell} text-right`}>{formatHours(e.vertriebsunterstuetzung)}</td>
|
||||
<td className={cell}>{e.kommentar}</td>
|
||||
|
||||
@@ -52,8 +52,9 @@ export default function TimeInput({ value, onChange, className = '', clearOnFocu
|
||||
|
||||
function handleBlur() {
|
||||
if (local === '') {
|
||||
setLocal(value);
|
||||
// Leeres Feld ist erlaubt (keine Uhrzeit) — leeren Wert übernehmen.
|
||||
setError(false);
|
||||
if (value !== '') onChange('');
|
||||
return;
|
||||
}
|
||||
const expanded = /^\d{1,2}:?$/.test(local) ? local.replace(/:$/, '') + ':00' : local;
|
||||
|
||||
@@ -38,6 +38,11 @@ export function mehrarbeit(arbeitszeitStd: number, pauseStd: number): number {
|
||||
return arbeitszeitStd - 8 - pauseStd;
|
||||
}
|
||||
|
||||
/** True, wenn sowohl Beginn als auch Ende gültige Uhrzeiten sind (beide angegeben). */
|
||||
export function hatZeiten(beginn: string, ende: string): boolean {
|
||||
return parseHM(beginn) !== null && parseHM(ende) !== null;
|
||||
}
|
||||
|
||||
/** 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 ?? '');
|
||||
|
||||
+7
-2
@@ -14,10 +14,15 @@ export function parseArbeitszeitInput(body: unknown): ArbeitszeitInput {
|
||||
const ort = String(b.ort ?? '') as Ort;
|
||||
if (!ORTE.includes(ort)) throw new Error('Ungültiger Ort.');
|
||||
|
||||
// Beginn/Ende sind optional. Wenn angegeben, müssen sie gültig sein;
|
||||
// entweder beide leer oder beide gesetzt.
|
||||
const beginn = String(b.beginn ?? '');
|
||||
const ende = String(b.ende ?? '');
|
||||
if (parseHM(beginn) === null) throw new Error('Ungültige Startzeit.');
|
||||
if (parseHM(ende) === null) throw new Error('Ungültige Endzeit.');
|
||||
if (beginn !== '' && parseHM(beginn) === null) throw new Error('Ungültige Startzeit.');
|
||||
if (ende !== '' && parseHM(ende) === null) throw new Error('Ungültige Endzeit.');
|
||||
if ((beginn === '') !== (ende === '')) {
|
||||
throw new Error('Bitte Start- und Endzeit gemeinsam angeben oder beide leer lassen.');
|
||||
}
|
||||
|
||||
const reisezeit = toNumber(b.reisezeit, 0);
|
||||
const vertriebsunterstuetzung = toNumber(b.vertriebsunterstuetzung, 2);
|
||||
|
||||
Reference in New Issue
Block a user