import { useState, useMemo, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import { useVoiceIntegration } from "@/hooks/useVoiceIntegration"; import { VoiceShortcuts } from "@/components/voice/VoiceShortcuts"; import { ProjectManager } from "@/components/tasks/ProjectManager"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/hooks/use-toast"; import Sidebar from "@/components/layout/Sidebar"; import { Plus, Calendar, Clock, Search, Filter, Trash2, CheckCircle2, PlayCircle, MoreHorizontal, Edit, ArrowUpDown, AlertTriangle, FolderOpen } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator } from "@/components/ui/dropdown-menu"; import { apiRequest } from "@/lib/queryClient"; interface Task { id: number; title: string; description?: string; priority: "low" | "medium" | "high"; status: "pending" | "in_progress" | "completed"; dueDate?: string; createdAt: string; updatedAt: string; } export default function TasksPage() { const { toast } = useToast(); const queryClient = useQueryClient(); const { speak } = useVoiceIntegration(); const [open, setOpen] = useState(false); const [editingTask, setEditingTask] = useState(null); const [selectedTasks, setSelectedTasks] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [priorityFilter, setPriorityFilter] = useState("all"); const [sortBy, setSortBy] = useState("createdAt"); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); const [newTask, setNewTask] = useState({ title: "", description: "", priority: "medium" as const, dueDate: "", }); const { data: tasksResponse, isLoading } = useQuery({ queryKey: ["/api/tasks"], }); const allTasks: Task[] = (tasksResponse as any)?.tasks || []; // Voice announcement when page loads useEffect(() => { if (allTasks.length > 0) { const timer = setTimeout(() => { speak(`Tasks page loaded. You have ${allTasks.length} tasks. Say "create task" followed by a task name to add a new task.`); }, 1000); return () => clearTimeout(timer); } }, [allTasks.length, speak]); // Enhanced filtering and sorting const filteredAndSortedTasks = useMemo(() => { let filtered = allTasks.filter((task) => { const matchesSearch = task.title.toLowerCase().includes(searchQuery.toLowerCase()) || (task.description?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false); const matchesStatus = statusFilter === "all" || task.status === statusFilter; const matchesPriority = priorityFilter === "all" || task.priority === priorityFilter; return matchesSearch && matchesStatus && matchesPriority; }); filtered.sort((a, b) => { let aValue, bValue; switch (sortBy) { case "title": aValue = a.title.toLowerCase(); bValue = b.title.toLowerCase(); break; case "priority": const priorityOrder = { low: 1, medium: 2, high: 3 }; aValue = priorityOrder[a.priority]; bValue = priorityOrder[b.priority]; break; case "dueDate": aValue = a.dueDate ? new Date(a.dueDate).getTime() : Infinity; bValue = b.dueDate ? new Date(b.dueDate).getTime() : Infinity; break; default: aValue = new Date(a.createdAt).getTime(); bValue = new Date(b.createdAt).getTime(); } if (sortOrder === "asc") { return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; } else { return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; } }); return filtered; }, [allTasks, searchQuery, statusFilter, priorityFilter, sortBy, sortOrder]); const createTaskMutation = useMutation({ mutationFn: async (task: typeof newTask) => { const response = await fetch("/api/tasks", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(task), credentials: "include", }); if (!response.ok) throw new Error("Failed to create task"); return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); setOpen(false); setNewTask({ title: "", description: "", priority: "medium", dueDate: "" }); toast({ title: "Task created", description: "Your task has been successfully created.", }); }, onError: () => { toast({ title: "Error", description: "Failed to create task. Please try again.", variant: "destructive", }); }, }); const updateTaskMutation = useMutation({ mutationFn: async ({ id, updates }: { id: number; updates: Partial }) => { const response = await fetch(`/api/tasks/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify(updates), credentials: "include", }); if (!response.ok) throw new Error("Failed to update task"); return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); setEditingTask(null); toast({ title: "Task updated", description: "Task has been successfully updated.", }); }, }); const deleteTaskMutation = useMutation({ mutationFn: async (id: number) => { const response = await fetch(`/api/tasks/${id}`, { method: "DELETE", credentials: "include", }); if (!response.ok) throw new Error("Failed to delete task"); return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); toast({ title: "Task deleted", description: "Task has been successfully deleted.", }); }, }); const bulkUpdateMutation = useMutation({ mutationFn: async ({ ids, updates }: { ids: number[]; updates: Partial }) => { await Promise.all( ids.map(id => fetch(`/api/tasks/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify(updates), credentials: "include", }) ) ); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); setSelectedTasks([]); toast({ title: "Tasks updated", description: `${selectedTasks.length} tasks have been updated.`, }); }, }); const handleCreateTask = (e: React.FormEvent) => { e.preventDefault(); createTaskMutation.mutate(newTask); speak(`Creating task "${newTask.title}"`); }; const handleEditTask = (e: React.FormEvent) => { e.preventDefault(); if (editingTask) { updateTaskMutation.mutate({ id: editingTask.id, updates: editingTask }); } }; const handleSelectTask = (taskId: number, checked: boolean) => { if (checked) { setSelectedTasks([...selectedTasks, taskId]); } else { setSelectedTasks(selectedTasks.filter(id => id !== taskId)); } }; const handleSelectAll = (checked: boolean) => { if (checked) { setSelectedTasks(filteredAndSortedTasks.map(task => task.id)); } else { setSelectedTasks([]); } }; const getPriorityColor = (priority: string) => { switch (priority) { case "high": return "destructive"; case "medium": return "default"; case "low": return "secondary"; default: return "default"; } }; const getStatusColor = (status: string) => { switch (status) { case "completed": return "secondary"; case "in_progress": return "default"; case "pending": return "outline"; default: return "outline"; } }; const isOverdue = (dueDate: string) => { return new Date(dueDate) < new Date(); }; // Task statistics const taskStats = useMemo(() => { return { total: allTasks.length, pending: allTasks.filter(t => t.status === "pending").length, inProgress: allTasks.filter(t => t.status === "in_progress").length, completed: allTasks.filter(t => t.status === "completed").length, overdue: allTasks.filter(t => t.dueDate && isOverdue(t.dueDate) && t.status !== "completed").length, }; }, [allTasks]); if (isLoading) { return (
{[1, 2, 3].map((i) => (
))}
); } return (
{/* Header with Stats */}

Tasks

Manage your tasks and track your progress

Create New Task Add a new task to your todo list.
setNewTask({ ...newTask, title: e.target.value })} required />