feat: toast notifications, delete task, due date badges on cards, keyboard shortcuts

This commit is contained in:
2026-01-29 07:33:05 +00:00
parent e874cafbec
commit 578b092a78
6 changed files with 234 additions and 8 deletions

View File

@@ -1,7 +1,8 @@
import { useState, useEffect, useCallback } from "react";
import { useParams, Link } from "react-router-dom";
import { useParams, Link, useNavigate } from "react-router-dom";
import type { Task, TaskStatus, Project } from "../lib/types";
import { updateTask, fetchProjects, addProgressNote, addSubtask, toggleSubtask, deleteSubtask } from "../lib/api";
import { updateTask, fetchProjects, addProgressNote, addSubtask, toggleSubtask, deleteSubtask, deleteTask } from "../lib/api";
import { useToast } from "../components/Toast";
const priorityColors: Record<string, string> = {
critical: "bg-red-500 text-white",
@@ -73,6 +74,10 @@ export function TaskPage() {
const [newSubtaskTitle, setNewSubtaskTitle] = useState("");
const [addingSubtask, setAddingSubtask] = useState(false);
const [saving, setSaving] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleting, setDeleting] = useState(false);
const { toast } = useToast();
const navigate = useNavigate();
const fetchTask = useCallback(async () => {
if (!taskRef) return;
@@ -106,13 +111,31 @@ export function TaskPage() {
try {
await updateTask(task.id, { status });
fetchTask();
toast(`Task moved to ${status}`, "success");
} catch (e) {
console.error("Failed to update status:", e);
toast("Failed to update status", "error");
} finally {
setSaving(false);
}
};
const handleDelete = async () => {
if (!task) return;
setDeleting(true);
try {
await deleteTask(task.id);
toast("Task deleted", "success");
navigate("/queue");
} catch (e) {
console.error("Failed to delete:", e);
toast("Failed to delete task", "error");
} finally {
setDeleting(false);
setShowDeleteConfirm(false);
}
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center text-gray-400">
@@ -478,6 +501,7 @@ export function TaskPage() {
onClick={() => {
const url = `${window.location.origin}/task/HQ-${task.taskNumber}`;
navigator.clipboard.writeText(url);
toast("Link copied", "info");
}}
className="text-xs px-2 py-1 bg-gray-100 rounded hover:bg-gray-200 transition"
>
@@ -485,6 +509,38 @@ export function TaskPage() {
</button>
</div>
</div>
{/* Danger zone */}
<div className="bg-white rounded-xl border border-gray-200 shadow-sm p-4">
<h3 className="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">Danger Zone</h3>
{!showDeleteConfirm ? (
<button
onClick={() => setShowDeleteConfirm(true)}
className="w-full text-sm text-red-600 hover:text-red-700 border border-red-200 rounded-lg px-3 py-2 hover:bg-red-50 transition font-medium"
>
🗑 Delete Task
</button>
) : (
<div className="space-y-2 animate-slide-up">
<p className="text-xs text-red-700">This cannot be undone. Are you sure?</p>
<div className="flex gap-2">
<button
onClick={handleDelete}
disabled={deleting}
className="flex-1 text-sm px-3 py-2 bg-red-600 text-white rounded-lg font-medium hover:bg-red-700 transition disabled:opacity-50"
>
{deleting ? "Deleting..." : "Yes, delete"}
</button>
<button
onClick={() => setShowDeleteConfirm(false)}
className="flex-1 text-sm px-3 py-2 border border-gray-200 rounded-lg text-gray-600 hover:bg-gray-50 transition"
>
Cancel
</button>
</div>
</div>
)}
</div>
</div>
</div>
</div>