import { useState, useRef } from "react"; import { Play, Square, ChevronDown, Pencil, Check, X, Trash2 } from "lucide-react"; import { useTimer } from "@/contexts/TimerContext"; import { useProjects } from "@/hooks/useProjects"; import { ProjectColorDot } from "@/components/ProjectColorDot"; function TimerDisplay({ totalSeconds }: { totalSeconds: number }) { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; const pad = (n: number) => n.toString().padStart(2, "0"); return ( {hours > 0 && ( <> {pad(hours)} h )} {pad(minutes)} m {pad(seconds)} s ); } /** Converts a HH:mm string to an ISO datetime, inferring the correct date. * If the resulting time would be in the future, it is assumed to belong to the previous day. */ function timeInputToIso(timeValue: string): string { const [hours, minutes] = timeValue.split(":").map(Number); const now = new Date(); const candidate = new Date(now); candidate.setHours(hours, minutes, 0, 0); // If the candidate is in the future, roll back one day if (candidate > now) { candidate.setDate(candidate.getDate() - 1); } return candidate.toISOString(); } export function TimerWidget() { const { ongoingTimer, isLoading, elapsedSeconds, startTimer, stopTimer, cancelTimer, updateTimerProject, updateTimerStartTime, } = useTimer(); const { projects } = useProjects(); const [showProjectSelect, setShowProjectSelect] = useState(false); const [error, setError] = useState(null); // Start time editing state const [editingStartTime, setEditingStartTime] = useState(false); const [startTimeInput, setStartTimeInput] = useState(""); const startTimeInputRef = useRef(null); const handleStart = async () => { setError(null); try { await startTimer(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to start timer"); } }; const handleStop = async () => { setError(null); try { await stopTimer(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to stop timer"); } }; const handleCancelTimer = async () => { setError(null); try { await cancelTimer(); } catch (err) { setError(err instanceof Error ? err.message : "Failed to cancel timer"); } }; const handleProjectChange = async (projectId: string) => { setError(null); try { await updateTimerProject(projectId); setShowProjectSelect(false); } catch (err) { setError(err instanceof Error ? err.message : "Failed to update project"); } }; const handleClearProject = async () => { setError(null); try { await updateTimerProject(null); setShowProjectSelect(false); } catch (err) { setError(err instanceof Error ? err.message : "Failed to clear project"); } }; const handleStartEditStartTime = () => { if (!ongoingTimer) return; const start = new Date(ongoingTimer.startTime); const hh = start.getHours().toString().padStart(2, "0"); const mm = start.getMinutes().toString().padStart(2, "0"); setStartTimeInput(`${hh}:${mm}`); setEditingStartTime(true); // Focus the input on next render setTimeout(() => startTimeInputRef.current?.focus(), 0); }; const handleCancelEditStartTime = () => { setEditingStartTime(false); setError(null); }; const handleConfirmStartTime = async () => { if (!startTimeInput) return; setError(null); try { const iso = timeInputToIso(startTimeInput); await updateTimerStartTime(iso); setEditingStartTime(false); } catch (err) { setError(err instanceof Error ? err.message : "Failed to update start time"); } }; const handleStartTimeKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { void handleConfirmStartTime(); } else if (e.key === "Escape") { handleCancelEditStartTime(); } }; if (isLoading) { return (
); } return (
{ongoingTimer ? ( <> {/* Row 1 (mobile): timer + stop side by side. On sm+ dissolves into the parent flex row via contents. */}
{/* Timer Display + Start Time Editor */}
{editingStartTime ? (
Started at setStartTimeInput(e.target.value)} onKeyDown={handleStartTimeKeyDown} className="font-mono text-lg font-bold text-gray-900 border border-primary-400 rounded px-2 py-0.5 focus:outline-none focus:ring-2 focus:ring-primary-500 w-28" />
) : (
)}
{/* Stop + Cancel Buttons */}
{/* Project Selector — full width on mobile, auto on desktop */}
{showProjectSelect && (
{projects?.map((project) => ( ))}
)}
) : (
{/* Stopped Timer Display */}
Ready to track time
{/* Start Button */}
)}
{error && (

{error}

)}
); }