Files
logbuch/components/TimePicker5.tsx
Reinhard X. Fürst a0fb6d8089 Various UX improvements and bug fixes
- Fix mustChangePassword session flag for users with pw=NULL
- Add PrF (Private Führung) as new ArtFuehrung type
- Split datetime-local into separate date + TimePicker5 (5-min steps, auto-repeat)
- Responsive Beginn/Ende layout: stacked on mobile, inline on desktop
- Sort BEOs alphabetically by Kürzel in selector
- Title shows active kuppel; hide user display in header
- Selected BEOs show Kürzel only (name stays in dropdown)
- Session timeout reduced to 1 hour
- Add CLAUDE.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 18:02:47 +02:00

69 lines
2.5 KiB
TypeScript

'use client';
import { useRef } from 'react';
interface Props {
value: string; // "HH:MM"
onChange: (value: string) => void;
className?: string;
}
function addMinutes(time: string, delta: number): string {
const [h, m] = time.split(':').map(Number);
const total = ((h * 60 + m + delta) % (24 * 60) + 24 * 60) % (24 * 60);
const pad = (n: number) => String(n).padStart(2, '0');
return `${pad(Math.floor(total / 60))}:${pad(total % 60)}`;
}
export default function TimePicker5({ value, onChange, className = '' }: Props) {
const valueRef = useRef(value);
valueRef.current = value;
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
function startRepeat(delta: number) {
onChange(addMinutes(valueRef.current, delta));
timeoutRef.current = setTimeout(() => {
const hourDelta = delta > 0 ? 60 : -60;
intervalRef.current = setInterval(() => {
onChange(addMinutes(valueRef.current, hourDelta));
}, 350);
}, 400);
}
function stopRepeat() {
if (timeoutRef.current !== null) { clearTimeout(timeoutRef.current); timeoutRef.current = null; }
if (intervalRef.current !== null) { clearInterval(intervalRef.current); intervalRef.current = null; }
}
function buttonProps(delta: number) {
return {
type: 'button' as const,
tabIndex: -1,
onMouseDown: () => startRepeat(delta),
onMouseUp: stopRepeat,
onMouseLeave: stopRepeat,
onTouchStart: (e: React.TouchEvent) => { e.preventDefault(); startRepeat(delta); },
onTouchEnd: stopRepeat,
};
}
return (
<div
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'ArrowUp') { e.preventDefault(); onChange(addMinutes(value, 5)); }
if (e.key === 'ArrowDown') { e.preventDefault(); onChange(addMinutes(value, -5)); }
}}
className={`flex items-center border-2 border-gray-400 rounded-lg bg-white focus:border-blue-500 focus:outline-none select-none ${className}`}
>
<span className="flex-1 px-3 py-2 text-sm font-mono text-center">{value}</span>
<div className="flex flex-col border-l border-gray-300 shrink-0">
<button {...buttonProps(5)} className="px-2 pt-1 pb-0.5 hover:bg-gray-100 text-gray-500 text-xs leading-none"></button>
<button {...buttonProps(-5)} className="px-2 pt-0.5 pb-1 hover:bg-gray-100 text-gray-500 text-xs leading-none"></button>
</div>
</div>
);
}