From be4010a22b9810de8008c2c5d7126f00eb0f2549 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 14 May 2026 02:53:44 +0000 Subject: [PATCH 1/5] fix(ux): resolve critical usability blockers - Wire up DeleteConfirmationDialog in TaskItem so deletes require explicit confirmation instead of firing immediately - Add aria-label to TaskItem delete/edit buttons for screen readers - Replace browser alert() in TaskEditDialog with a destructive toast notification so validation errors don't interrupt workflow - Add isSaving loading state to CategoryManagement that disables buttons and shows progress text during forceSyncToDatabase() - Show success toasts after category add/update/delete operations - Use Alert variant="destructive" for auth errors and distinct green styling for auth success messages https://claude.ai/code/session_01WXAUpbzPAbGBi4bv6gxz4w --- src/components/AuthDialog.tsx | 6 +++--- src/components/CategoryManagement.tsx | 31 +++++++++++++++++++++------ src/components/TaskEditDialog.tsx | 7 +++++- src/components/TaskItem.tsx | 16 +++++++++++++- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/components/AuthDialog.tsx b/src/components/AuthDialog.tsx index f4c2cf9..362022c 100644 --- a/src/components/AuthDialog.tsx +++ b/src/components/AuthDialog.tsx @@ -107,14 +107,14 @@ export const AuthDialog: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ {error && ( - + {error} )} {success && ( - - + + {success} diff --git a/src/components/CategoryManagement.tsx b/src/components/CategoryManagement.tsx index 34e35b4..65a2047 100644 --- a/src/components/CategoryManagement.tsx +++ b/src/components/CategoryManagement.tsx @@ -24,6 +24,7 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Plus, Edit, Trash2, Tag } from "lucide-react"; import { TaskCategory } from "@/config/categories"; import { useTimeTracking } from "@/hooks/useTimeTracking"; +import { toast } from "@/hooks/use-toast"; interface CategoryManagementProps { isOpen: boolean; @@ -41,6 +42,7 @@ export const CategoryManagement: React.FC = ({ ); const [isAddingNew, setIsAddingNew] = useState(false); const [deleteTargetId, setDeleteTargetId] = useState(null); + const [isSaving, setIsSaving] = useState(false); const [formData, setFormData] = useState({ name: '', description: '', @@ -61,6 +63,7 @@ export const CategoryManagement: React.FC = ({ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + setIsSaving(true); const categoryData = { name: formData.name.trim(), @@ -69,16 +72,23 @@ export const CategoryManagement: React.FC = ({ isBillable: formData.isBillable }; - if (editingCategory) { - updateCategory(editingCategory.id, categoryData); + const isEditing = !!editingCategory; + if (isEditing) { + updateCategory(editingCategory!.id, categoryData); } else { addCategory(categoryData); } - // Save changes to database await forceSyncToDatabase(); + toast({ + title: isEditing ? "Category updated" : "Category added", + description: isEditing + ? `"${categoryData.name}" has been updated.` + : `"${categoryData.name}" has been added.` + }); resetForm(); + setIsSaving(false); }; const handleEdit = (category: TaskCategory) => { @@ -94,9 +104,16 @@ export const CategoryManagement: React.FC = ({ const handleDeleteConfirm = async () => { if (!deleteTargetId) return; + setIsSaving(true); + const deletedName = categories.find((c) => c.id === deleteTargetId)?.name; deleteCategory(deleteTargetId); await forceSyncToDatabase(); + toast({ + title: "Category deleted", + description: deletedName ? `"${deletedName}" has been removed.` : undefined + }); setDeleteTargetId(null); + setIsSaving(false); }; const predefinedColors = [ @@ -246,10 +263,12 @@ export const CategoryManagement: React.FC = ({
- -
diff --git a/src/components/TaskEditDialog.tsx b/src/components/TaskEditDialog.tsx index 0a2f364..7e4fdac 100644 --- a/src/components/TaskEditDialog.tsx +++ b/src/components/TaskEditDialog.tsx @@ -24,6 +24,7 @@ import { Clock, Save } from 'lucide-react'; import { Task } from '@/contexts/TimeTrackingContext'; import { useTimeTracking } from '@/hooks/useTimeTracking'; import { formatTime, formatDate } from '@/utils/timeUtil'; +import { toast } from '@/hooks/use-toast'; interface TaskEditDialogProps { task: Task; @@ -150,7 +151,11 @@ export const TaskEditDialog: React.FC = ({ const handleSave = () => { // Validate required fields if (!formData.title.trim()) { - alert('Task title is required'); + toast({ + title: 'Title required', + description: 'Please enter a task title before saving.', + variant: 'destructive' + }); return; } diff --git a/src/components/TaskItem.tsx b/src/components/TaskItem.tsx index c7edfd0..d0a765f 100644 --- a/src/components/TaskItem.tsx +++ b/src/components/TaskItem.tsx @@ -4,6 +4,7 @@ import { useTimeTracking } from "@/hooks/useTimeTracking"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { TaskEditDialog } from "@/components/TaskEditDialog"; +import { DeleteConfirmationDialog } from "@/components/DeleteConfirmationDialog"; import { MarkdownDisplay } from "@/components/MarkdownDisplay"; import { Edit, @@ -30,6 +31,7 @@ export const TaskItem: React.FC = ({ }) => { const { categories } = useTimeTracking(); const [showEditDialog, setShowEditDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); const duration = task.duration || (isActive ? currentDuration : 0); const category = categories.find((c) => c.id === task.category); @@ -104,9 +106,10 @@ export const TaskItem: React.FC = ({
-
diff --git a/src/components/ProjectManagement.tsx b/src/components/ProjectManagement.tsx index a8864df..ce8208e 100644 --- a/src/components/ProjectManagement.tsx +++ b/src/components/ProjectManagement.tsx @@ -22,6 +22,7 @@ import { Label } from "@/components/ui/label"; import { Plus, Edit, Trash2, Briefcase, RotateCcw } from "lucide-react"; import { Project } from "@/contexts/TimeTrackingContext"; import { useTimeTracking } from "@/hooks/useTimeTracking"; +import { toast } from "@/hooks/use-toast"; interface ProjectManagementProps { isOpen: boolean; @@ -75,8 +76,16 @@ export const ProjectManagement: React.FC = ({ if (editingProject) { updateProject(editingProject.id, projectData); + toast({ + title: "Project updated", + description: `"${projectData.name}" has been updated.` + }); } else { addProject(projectData); + toast({ + title: "Project added", + description: `"${projectData.name}" has been added.` + }); } resetForm(); @@ -95,12 +104,18 @@ export const ProjectManagement: React.FC = ({ const handleDeleteConfirm = () => { if (!deleteTargetId) return; + const deletedName = projects.find((p) => p.id === deleteTargetId)?.name; deleteProject(deleteTargetId); + toast({ + title: "Project deleted", + description: deletedName ? `"${deletedName}" has been removed.` : undefined + }); setDeleteTargetId(null); }; const handleResetConfirm = () => { resetProjectsToDefaults(); + toast({ title: "Projects reset", description: "Projects have been restored to defaults." }); setShowResetDialog(false); }; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 94ea7c0..8c77fe0 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -154,7 +154,7 @@ const TimeTrackerContent = () => { )}

{tasks.length === 0 - ? "No tasks tracked yet." + ? "No tasks tracked yet — go to Tasks to start your first task." : `${tasks.length} task${tasks.length === 1 ? "" : "s"} tracked today.`}