Files
logbuch/components/TimePicker5.tsx
Reinhard X. Fürst 64acfdda6f Fix iOS text color, viewport meta tag, and security improvements
- Add viewport meta tag to prevent iOS zoom/scaling issues
- Fix text color on iOS Safari (explicit text-gray-900 on buttons, inputs, TimePicker5)
- Add session checks to /api/beos, /api/objekte, /api/wetter
- Revert iframe embedding (X-Frame-Options: DENY, SameSite: lax)
- docker-compose.prod.yml: fix DB_PORT=3306 for production
- Add docker-compose.prod.yml, .env.prod.example, dump/import scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 17:38:08 +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 text-gray-900">{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>
);
}