diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4a54b0..2513808 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,8 @@ on: pull_request: branches: ["main", "master"] push: - branches: ["main", "master"] + branches: ["**"] + workflow_dispatch: jobs: test: 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..d04997e 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 = [ @@ -146,9 +163,12 @@ export const CategoryManagement: React.FC = ({
- + setFormData((prev) => ({ @@ -193,7 +213,7 @@ export const CategoryManagement: React.FC = ({ } className="w-16 h-10" /> - + {formData.color}
= ({ {/* Predefined Colors */}
- + Quick colors: {predefinedColors.map((color) => ( @@ -240,16 +260,18 @@ export const CategoryManagement: React.FC = ({ - + (Tasks in this category can generate revenue)
- -
@@ -267,8 +289,8 @@ export const CategoryManagement: React.FC = ({ {categories.length === 0 ? ( - -

+ +

No categories yet. Add your first category to get started!

@@ -291,19 +313,19 @@ export const CategoryManagement: React.FC = ({ />
-

+

{category.name}

{category.isBillable !== false ? 'Billable' : 'Non-billable'}
{category.description && ( -

+

{category.description}

)} diff --git a/src/components/DaySummary.tsx b/src/components/DaySummary.tsx index 7a09b6f..bd08e70 100644 --- a/src/components/DaySummary.tsx +++ b/src/components/DaySummary.tsx @@ -29,7 +29,7 @@ export const DaySummary: React.FC = ({
-
+

{formatDate(dayStartTime)}

Started at {formatTime(dayStartTime)}

@@ -39,16 +39,16 @@ export const DaySummary: React.FC = ({ {formatDuration(totalDuration)} - total time + total time
-

Tasks completed:

+

Tasks completed:

{tasks.map((task) => (
- {task.title} - + {task.title} + {formatDuration(task.duration || 0)}
diff --git a/src/components/ExportDialog.tsx b/src/components/ExportDialog.tsx index 1fb4959..06160dc 100644 --- a/src/components/ExportDialog.tsx +++ b/src/components/ExportDialog.tsx @@ -408,7 +408,7 @@ export const ExportDialog: React.FC = ({
-

+

Click to select a CSV file

{categories.length > 0 && ( +
+ +
)} {projects.length > 0 && ( +
+ +
)}
@@ -165,6 +197,7 @@ export const NewTaskForm: React.FC = ({ onSubmit, defaultOpen onClick={() => { setIsOpen(false); setTitle(""); + setTitleTouched(false); setDescription(""); setSelectedProject(""); setSelectedCategory(""); @@ -173,7 +206,7 @@ export const NewTaskForm: React.FC = ({ onSubmit, defaultOpen > Cancel -
diff --git a/src/components/ProjectManagement.tsx b/src/components/ProjectManagement.tsx index a8864df..c14e86a 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); }; @@ -151,7 +166,9 @@ export const ProjectManagement: React.FC = ({
- + = ({ })) } placeholder="Enter project name" + aria-required="true" required />
- + = ({ })) } placeholder="Enter client name" + aria-required="true" required />
@@ -214,7 +235,7 @@ export const ProjectManagement: React.FC = ({ } className="w-16 h-10" /> - + {formData.color}
diff --git a/src/components/SyncStatus.tsx b/src/components/SyncStatus.tsx index 3175265..b8f5003 100644 --- a/src/components/SyncStatus.tsx +++ b/src/components/SyncStatus.tsx @@ -39,7 +39,7 @@ export const SyncStatus = memo(function SyncStatus({ if (!isAuthenticated) { return ( -
+
diff --git a/src/components/TaskEditDialog.tsx b/src/components/TaskEditDialog.tsx index 0a2f364..32bba35 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; } @@ -225,7 +230,7 @@ export const TaskEditDialog: React.FC = ({
= ({ })) } placeholder="Enter task title" + aria-required="true" />
- + Edit @@ -347,7 +356,7 @@ export const TaskEditDialog: React.FC = ({ )?.name } - + { projects.find( p => p.id === formData.project @@ -366,7 +375,7 @@ export const TaskEditDialog: React.FC = ({
{project.name} - + {project.client}
@@ -384,13 +393,13 @@ export const TaskEditDialog: React.FC = ({ Time Adjustment -

+

Times are automatically rounded to the nearest 15-minute interval

-
+

Date: {formatDate(task.startTime)}

diff --git a/src/components/TaskItem.tsx b/src/components/TaskItem.tsx index c7edfd0..e1a4b19 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); @@ -53,7 +55,7 @@ export const TaskItem: React.FC = ({ title={category.name} /> )} */} -

+

{task.title}

{isActive && ( @@ -69,7 +71,7 @@ export const TaskItem: React.FC = ({
)} -
+
Started: {formatTime(task.startTime)} @@ -104,10 +106,11 @@ export const TaskItem: React.FC = ({