Add cancel (discard) timer feature
Allows users to discard a running timer without creating a time entry.
A trash icon in the timer widget reveals a confirmation step ('Discard / Keep')
to prevent accidental data loss. Backend exposes a new DELETE /api/timer
endpoint that simply deletes the ongoingTimer row.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { Play, Square, ChevronDown, Pencil, Check, X } from "lucide-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";
|
||||
@@ -49,12 +49,14 @@ export function TimerWidget() {
|
||||
elapsedSeconds,
|
||||
startTimer,
|
||||
stopTimer,
|
||||
cancelTimer,
|
||||
updateTimerProject,
|
||||
updateTimerStartTime,
|
||||
} = useTimer();
|
||||
const { projects } = useProjects();
|
||||
const [showProjectSelect, setShowProjectSelect] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [confirmCancel, setConfirmCancel] = useState(false);
|
||||
|
||||
// Start time editing state
|
||||
const [editingStartTime, setEditingStartTime] = useState(false);
|
||||
@@ -72,6 +74,7 @@ export function TimerWidget() {
|
||||
|
||||
const handleStop = async () => {
|
||||
setError(null);
|
||||
setConfirmCancel(false);
|
||||
try {
|
||||
await stopTimer();
|
||||
} catch (err) {
|
||||
@@ -79,6 +82,16 @@ export function TimerWidget() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelTimer = async () => {
|
||||
setError(null);
|
||||
try {
|
||||
await cancelTimer();
|
||||
setConfirmCancel(false);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to cancel timer");
|
||||
}
|
||||
};
|
||||
|
||||
const handleProjectChange = async (projectId: string) => {
|
||||
setError(null);
|
||||
try {
|
||||
@@ -195,14 +208,44 @@ export function TimerWidget() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stop Button */}
|
||||
<button
|
||||
onClick={handleStop}
|
||||
className="flex items-center space-x-2 px-6 py-3 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors shrink-0 sm:order-last"
|
||||
>
|
||||
<Square className="h-5 w-5 fill-current" />
|
||||
<span>Stop</span>
|
||||
</button>
|
||||
{/* Stop + Cancel Buttons */}
|
||||
<div className="flex items-center space-x-2 shrink-0 sm:order-last">
|
||||
{confirmCancel ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-gray-600">Discard timer?</span>
|
||||
<button
|
||||
onClick={() => void handleCancelTimer()}
|
||||
className="flex items-center space-x-1 px-3 py-2 bg-gray-700 text-white rounded-lg font-medium hover:bg-gray-800 transition-colors text-sm"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<span>Discard</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setConfirmCancel(false)}
|
||||
className="px-3 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-lg transition-colors text-sm"
|
||||
>
|
||||
Keep
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setConfirmCancel(true)}
|
||||
title="Discard timer"
|
||||
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<Trash2 className="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleStop}
|
||||
className="flex items-center space-x-2 px-6 py-3 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition-colors"
|
||||
>
|
||||
<Square className="h-5 w-5 fill-current" />
|
||||
<span>Stop</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Project Selector — full width on mobile, auto on desktop */}
|
||||
|
||||
Reference in New Issue
Block a user