Timer integratrion

This commit is contained in:
Marc Wieland 2026-01-23 11:02:51 +01:00
parent d43866615b
commit 36d7821629
3 changed files with 132 additions and 66 deletions

View File

@ -1,93 +1,158 @@
import { useState, useEffect } from 'react';
import { Timer, Play, Pause, RotateCcw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
interface RoundTimerProps {
roundNumber: number;
}
export const RoundTimer = ({ roundNumber }: RoundTimerProps) => {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(true); // Auto-start beim Laden
const [isPaused, setIsPaused] = useState(false);
const [totalSeconds, setTotalSeconds] = useState(300); // 5 Min default
const [remainingSeconds, setRemainingSeconds] = useState(300);
const [isRunning, setIsRunning] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [editMinutes, setEditMinutes] = useState('5');
const [editSeconds, setEditSeconds] = useState('0');
useEffect(() => {
// Reset timer wenn neue Runde startet
setSeconds(0);
setIsRunning(true);
setIsPaused(false);
}, [roundNumber]);
// Reset when round changes
setRemainingSeconds(totalSeconds);
setIsRunning(false);
}, [roundNumber, totalSeconds]);
useEffect(() => {
let interval: NodeJS.Timeout | null = null;
if (isRunning && !isPaused) {
if (isRunning && remainingSeconds > 0) {
interval = setInterval(() => {
setSeconds((prev) => prev + 1);
setRemainingSeconds((prev) => {
if (prev <= 1) {
setIsRunning(false);
return 0;
}
return prev - 1;
});
}, 1000);
}
return () => {
if (interval) clearInterval(interval);
};
}, [isRunning, isPaused]);
}, [isRunning, remainingSeconds]);
const togglePause = () => {
setIsPaused((prev) => !prev);
setIsRunning((prev) => !prev);
};
const reset = () => {
setSeconds(0);
setIsPaused(false);
setRemainingSeconds(totalSeconds);
setIsRunning(false);
};
const formatTime = (totalSeconds: number): string => {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const handleTimeEdit = () => {
const newMinutes = Math.max(0, Math.min(99, parseInt(editMinutes) || 0));
const newSeconds = Math.max(0, Math.min(59, parseInt(editSeconds) || 0));
const newTotal = newMinutes * 60 + newSeconds;
setTotalSeconds(newTotal);
setRemainingSeconds(newTotal);
setIsRunning(false);
setIsEditing(false);
};
const startEditing = () => {
const mins = Math.floor(totalSeconds / 60);
const secs = totalSeconds % 60;
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
setEditMinutes(mins.toString());
setEditSeconds(secs.toString());
setIsEditing(true);
};
const formatTime = (secs: number): string => {
const minutes = Math.floor(secs / 60);
const seconds = secs % 60;
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};
const isTimeExpired = remainingSeconds === 0;
return (
<div className="card-apple p-4 flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="flex items-center gap-3 px-4 py-3 card-apple">
<div className={`p-2 rounded-xl transition-colors ${
isPaused
? 'bg-muted text-muted-foreground'
: 'bg-primary/10 text-primary animate-pulse'
isTimeExpired
? 'bg-destructive/10 text-destructive'
: isRunning
? 'bg-primary/10 text-primary animate-pulse'
: 'bg-muted text-muted-foreground'
}`}>
<Timer className="w-5 h-5" />
</div>
<div>
{isEditing ? (
<div className="flex items-center gap-2">
<Input
type="number"
min="0"
max="99"
value={editMinutes}
onChange={(e) => setEditMinutes(e.target.value)}
className="w-12 h-8 text-center rounded-lg"
placeholder="0"
/>
<span className="font-bold text-foreground">:</span>
<Input
type="number"
min="0"
max="59"
value={editSeconds}
onChange={(e) => setEditSeconds(e.target.value)}
className="w-12 h-8 text-center rounded-lg"
placeholder="0"
/>
<Button
size="sm"
onClick={handleTimeEdit}
className="rounded-lg h-8 px-2 text-xs"
>
OK
</Button>
</div>
) : (
<div
onClick={startEditing}
className="cursor-pointer hover:opacity-80 transition-opacity"
title="Klicke um Zeit zu bearbeiten"
>
<p className="text-sm font-medium text-muted-foreground">Rundenzeit</p>
<p className="text-2xl font-bold text-foreground tabular-nums">
{formatTime(seconds)}
<p className={`text-2xl font-bold tabular-nums transition-colors ${
isTimeExpired ? 'text-destructive' : 'text-foreground'
}`}>
{formatTime(remainingSeconds)}
</p>
</div>
</div>
)}
<div className="flex gap-2">
<div className="flex gap-2 ml-auto">
<Button
variant="outline"
size="icon"
onClick={togglePause}
disabled={isTimeExpired || isEditing}
className="rounded-xl h-9 w-9"
title={isPaused ? 'Fortsetzen' : 'Pausieren'}
title={isRunning ? 'Pausieren' : 'Starten'}
>
{isPaused ? (
<Play className="w-4 h-4" />
) : (
{isRunning ? (
<Pause className="w-4 h-4" />
) : (
<Play className="w-4 h-4" />
)}
</Button>
<Button
variant="outline"
size="icon"
onClick={reset}
disabled={isEditing}
className="rounded-xl h-9 w-9"
title="Zurücksetzen"
>

View File

@ -99,6 +99,8 @@ export const TournamentView = () => {
{currentRound && ` • Runde ${currentRound.roundNumber} aktiv`}
</p>
</div>
<div className="flex flex-col sm:flex-row gap-3 items-start sm:items-center">
{currentRound && <RoundTimer roundNumber={currentRound.roundNumber} />}
<div className="flex gap-2">
<ScoreboardModal />
{!currentRound ? (
@ -122,6 +124,7 @@ export const TournamentView = () => {
)}
</div>
</div>
</div>
{!currentRound && !hasEnoughTeams && (
<div className="card-apple p-6 flex items-center gap-4 border-l-4 border-l-champions">
@ -161,8 +164,6 @@ export const TournamentView = () => {
{currentRound && (
<>
<RoundTimer roundNumber={currentRound.roundNumber} />
<div className="card-apple p-4 bg-primary/5 border-primary/20">
<p className="text-sm text-center text-muted-foreground">
<span className="font-semibold text-primary">Runde {currentRound.roundNumber}</span>

View File

@ -6,7 +6,7 @@ import path from "path";
export default defineConfig(() => ({
server: {
host: "::",
port: 8080,
port: 5173,
hmr: {
overlay: false,
},