diff --git a/apps/web/src/components/time/TimerPill.tsx b/apps/web/src/components/time/TimerPill.tsx index 09fcf07..c01bde3 100644 --- a/apps/web/src/components/time/TimerPill.tsx +++ b/apps/web/src/components/time/TimerPill.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { Play, Square, Plus } from 'lucide-react'; +import { Play, Square, Plus, Pause } from 'lucide-react'; import { toast } from 'sonner'; import { trpc } from '../../lib/trpc'; import { formatDuration } from '../../lib/format-duration'; @@ -14,6 +14,7 @@ export function TimerPill() { const nowQuery = trpc.time.now.useQuery(undefined, { refetchInterval: 30_000 }); const todayQuery = trpc.dayEntry.today.useQuery(); const [pickerOpen, setPickerOpen] = useState(false); + const [pausePanelOpen, setPausePanelOpen] = useState(false); const [elapsed, setElapsed] = useState(0); const pickerRef = useRef(null); @@ -31,14 +32,18 @@ export function TimerPill() { }, [nowQuery.data]); useEffect(() => { - if (!pickerOpen) return undefined; + if (!pickerOpen && !pausePanelOpen) return undefined; function onClick(e: MouseEvent) { if (pickerRef.current && !pickerRef.current.contains(e.target as Node)) { setPickerOpen(false); + setPausePanelOpen(false); } } function onKey(e: KeyboardEvent) { - if (e.key === 'Escape') setPickerOpen(false); + if (e.key === 'Escape') { + setPickerOpen(false); + setPausePanelOpen(false); + } } document.addEventListener('mousedown', onClick); document.addEventListener('keydown', onKey); @@ -46,13 +51,12 @@ export function TimerPill() { document.removeEventListener('mousedown', onClick); document.removeEventListener('keydown', onKey); }; - }, [pickerOpen]); + }, [pickerOpen, pausePanelOpen]); const stopMutation = trpc.time.stop.useMutation({ onSuccess: async () => { await utils.time.now.invalidate(); await utils.time.listToday.invalidate(); - toast('Timer gestoppt.'); }, }); const startMutation = trpc.time.start.useMutation({ @@ -63,8 +67,17 @@ export function TimerPill() { toast('Timer läuft.'); }, }); + // Pause = break-Eintrag; stoppt automatisch einen laufenden Task. + const pauseMutation = trpc.time.start.useMutation({ + onSuccess: async () => { + await utils.time.now.invalidate(); + await utils.time.listToday.invalidate(); + toast('Pause läuft.'); + }, + }); const active = nowQuery.data; + const isPause = active?.category === 'break'; const title = active ? active.description || (active.todoId @@ -72,38 +85,66 @@ export function TimerPill() { : CATEGORY_LABEL[active.category] ?? 'Timer') : null; + function startPause() { + setPickerOpen(false); + pauseMutation.mutate({ category: 'break' }); + } + return ( -
- {active ? ( +
+ {active && isPause ? ( + // --- Pause läuft --- + ) : active ? ( + // --- Task läuft --- + <> + + + ) : ( - + // --- Idle --- + <> + + + )} - {pickerOpen && !active && ( + {pickerOpen && (!active || isPause) && ( t.status === 'open')} onClose={() => setPickerOpen(false)} @@ -111,6 +152,108 @@ export function TimerPill() { pending={startMutation.isPending} /> )} + + {pausePanelOpen && active && isPause && ( + { + setPausePanelOpen(false); + setPickerOpen(true); + }} + onEnd={() => { + setPausePanelOpen(false); + stopMutation.mutate(undefined, { onSuccess: () => toast('Pause beendet.') }); + }} + onSavedGrund={() => setPausePanelOpen(false)} + /> + )} +
+ ); +} + +function PauseButton({ onClick, pending }: { onClick: () => void; pending: boolean }) { + return ( + + ); +} + +function PausePanel({ + entryId, + currentGrund, + onResume, + onEnd, + onSavedGrund, +}: { + entryId: string; + currentGrund: string; + onResume: () => void; + onEnd: () => void; + onSavedGrund: () => void; +}) { + const utils = trpc.useUtils(); + const [grund, setGrund] = useState(currentGrund); + const update = trpc.time.update.useMutation({ + onSuccess: async () => { + await utils.time.now.invalidate(); + await utils.time.listToday.invalidate(); + onSavedGrund(); + }, + }); + return ( +
+

Pause läuft

+ +
+ setGrund(e.target.value)} + placeholder="z. B. Bürofahrt, Essen …" + maxLength={200} + className="flex-1 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-white px-2 py-1.5 text-sm" + /> + +
+ +
+ + +
); }