8bff795247
- Sonderführung: neues Feld 'Name/Gruppe' (DB-Spalte SonderName), in Liste sichtbar - Wetter: Race-Condition behoben (API überschreibt DB-Werte beim Bearbeiten nicht mehr) - Zeiterfassung: TimePicker5 ersetzt durch freie Texteingabe (TimeInput) mit Validierung - Enter-Taste: navigiert zum nächsten Feld statt die Form abzuschicken; Luftdruck → zurück zu Art; Bemerkungen bleibt normal - Objektsuche: Freitext-Suche im ObjektSelector, filtert nach Präfix (case-insensitive) - UI-Anpassungen: kompakteres Layout (space-y-2, kleinere Abstände) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
62 lines
1.6 KiB
TypeScript
62 lines
1.6 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useState } from 'react';
|
||
|
||
interface Props {
|
||
value: string; // "HH:MM"
|
||
onChange: (value: string) => void;
|
||
className?: string;
|
||
}
|
||
|
||
function isValid(t: string): boolean {
|
||
if (!/^\d{1,2}:\d{2}$/.test(t)) return false;
|
||
const [h, m] = t.split(':').map(Number);
|
||
return h >= 0 && h <= 23 && m >= 0 && m <= 59;
|
||
}
|
||
|
||
function normalize(t: string): string {
|
||
const [h, m] = t.split(':').map(Number);
|
||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||
}
|
||
|
||
export default function TimeInput({ value, onChange, className = '' }: Props) {
|
||
const [local, setLocal] = useState(value);
|
||
const [error, setError] = useState(false);
|
||
|
||
useEffect(() => {
|
||
setLocal(value);
|
||
setError(false);
|
||
}, [value]);
|
||
|
||
function handleBlur() {
|
||
if (isValid(local)) {
|
||
const norm = normalize(local);
|
||
setLocal(norm);
|
||
setError(false);
|
||
onChange(norm);
|
||
} else {
|
||
setError(true);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className={`relative ${className}`}>
|
||
<input
|
||
type="text"
|
||
value={local}
|
||
onChange={(e) => { setLocal(e.target.value); setError(false); }}
|
||
onBlur={handleBlur}
|
||
placeholder="HH:MM"
|
||
className={`w-full px-2 py-1 border-2 rounded-lg bg-white text-sm text-gray-900 font-mono text-center focus:outline-none ${
|
||
error ? 'border-red-500 focus:border-red-500' : 'border-gray-400 focus:border-blue-500'
|
||
}`}
|
||
/>
|
||
{error && (
|
||
<p className="absolute left-0 top-full mt-0.5 text-xs text-red-600 whitespace-nowrap z-10">
|
||
Ungültig (00:00 – 23:59)
|
||
</p>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|