import { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/components/Layout"; import { useToast } from "@/hooks/use-toast"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { Pagination, usePagination } from "@/components/ui/pagination"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ListTodo, Plus, Search, Circle, Clock, AlertTriangle, CheckCircle, Edit, Trash2, Calendar, CalendarX, CalendarClock, Kanban } from "lucide-react"; import { format, isToday, isThisWeek, isPast, differenceInDays } from "date-fns"; import { fr } from "date-fns/locale"; import TaskForm from "@/components/tasks/TaskForm"; import { Task } from "@shared/schema"; type TaskWithRelations = Task & { assignedUser?: { id: string; username: string; firstName?: string; lastName?: string; }; creator?: { id: string; username: string; firstName?: string; lastName?: string; }; group?: { id: number; name: string; color: string; }; isFutureTask?: boolean; // Pour les tâches futures (admin/directeur uniquement) }; export default function Tasks() { const { user } = useAuthUnified(); const { selectedStoreId, storeInitialized } = useStore(); const { toast } = useToast(); const queryClient = useQueryClient(); // États locaux const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [priorityFilter, setPriorityFilter] = useState("all"); const [dueDateFilter, setDueDateFilter] = useState("all"); const [viewMode, setViewMode] = useState("list"); // Modals const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [selectedTask, setSelectedTask] = useState(null); const [taskToDelete, setTaskToDelete] = useState(null); // Fetch tasks - attendre que l'initialisation des stores soit terminée pour les admins const { data: tasks = [], isLoading, error } = useQuery({ queryKey: ["/api/tasks", selectedStoreId], queryFn: async () => { try { const params = new URLSearchParams(); if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } console.log('📋 TASKS QUERY - Fetching with params:', { selectedStoreId, userRole: user?.role, storeInitialized, url: `/api/tasks?${params.toString()}` }); const response = await fetch(`/api/tasks?${params.toString()}`, { credentials: 'include' }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('📋 TASKS QUERY - Response received:', { dataType: typeof data, isArray: Array.isArray(data), length: data?.length, firstTask: data?.[0] ? { id: data[0].id, title: data[0].title, hasStartDate: !!data[0].startDate, hasGroup: !!data[0].group } : null }); // Valider et nettoyer les données reçues if (!Array.isArray(data)) { console.error('Tasks API returned non-array data:', data); return []; } return data.filter(task => task && typeof task === 'object' && task.id); } catch (error) { console.error('Error fetching tasks:', error); throw error; } }, enabled: !!user && (user.role !== 'admin' || storeInitialized), }); // Fetch users for task assignment - seulement pour admin/manager/directeur const { data: users = [] } = useQuery({ queryKey: ["/api/users"], queryFn: () => fetch('/api/users', { credentials: 'include' }).then(res => { if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } return res.json(); }), enabled: !!user && (user.role === 'admin' || user.role === 'manager' || user.role === 'directeur'), }); const handleEditTask = (task: TaskWithRelations) => { setSelectedTask(task); setShowEditModal(true); }; const handleCompleteTask = async (taskId: number) => { try { const response = await fetch(`/api/tasks/${taskId}/complete`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({}), }); if (!response.ok) { throw new Error('Erreur lors de la completion de la tâche'); } await queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); toast({ title: "Succès", description: "Tâche marquée comme terminée", }); } catch (error) { console.error("Error completing task:", error); toast({ title: "Erreur", description: "Impossible de terminer la tâche", variant: "destructive", }); } }; const handleDeleteClick = (task: TaskWithRelations) => { setTaskToDelete(task); setShowDeleteModal(true); }; const handleConfirmDelete = async () => { if (!taskToDelete) return; try { const response = await fetch(`/api/tasks/${taskToDelete.id}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { throw new Error('Erreur lors de la suppression'); } await queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); toast({ title: "Succès", description: "Tâche supprimée avec succès", }); setShowDeleteModal(false); setTaskToDelete(null); } catch (error) { console.error("Error deleting task:", error); toast({ title: "Erreur", description: "Impossible de supprimer la tâche", variant: "destructive", }); } }; // Filtrer et trier les tâches const filteredTasks = tasks .filter((task: TaskWithRelations) => { // Filtre par recherche if (searchTerm && !task.title.toLowerCase().includes(searchTerm.toLowerCase()) && !task.description?.toLowerCase().includes(searchTerm.toLowerCase())) { return false; } // Filtre par statut if (statusFilter !== "all" && task.status !== statusFilter) { return false; } // Filtre par priorité if (priorityFilter !== "all" && task.priority !== priorityFilter) { return false; } // Filtre par date d'échéance if (dueDateFilter !== "all" && task.dueDate) { const dueDate = new Date(task.dueDate); switch (dueDateFilter) { case "today": if (!isToday(dueDate)) return false; break; case "this_week": if (!isThisWeek(dueDate)) return false; break; case "overdue": if (!isPast(dueDate) || task.status === 'completed') return false; break; case "no_due_date": if (task.dueDate) return false; break; } } else if (dueDateFilter === "no_due_date" && task.dueDate) { return false; } return true; }) .sort((a: TaskWithRelations, b: TaskWithRelations) => { // Faire remonter les tâches non validées (pending) en premier if (a.status === 'pending' && b.status === 'completed') return -1; if (a.status === 'completed' && b.status === 'pending') return 1; // Pour les tâches de même statut, trier par priorité (high > medium > low) const priorityOrder = { 'high': 3, 'medium': 2, 'low': 1 }; const aPriority = priorityOrder[a.priority as keyof typeof priorityOrder] || 2; const bPriority = priorityOrder[b.priority as keyof typeof priorityOrder] || 2; if (aPriority !== bPriority) { return bPriority - aPriority; // Ordre décroissant (high en premier) } // Enfin, trier par date de création (plus récent en premier) const aDate = a.createdAt ? new Date(a.createdAt).getTime() : 0; const bDate = b.createdAt ? new Date(b.createdAt).getTime() : 0; return bDate - aDate; }); // Pagination const { currentPage, setCurrentPage, itemsPerPage, setItemsPerPage, totalPages, paginatedData: paginatedTasks, totalItems } = usePagination(filteredTasks, 10); const getPriorityConfig = (priority: string) => { switch (priority) { case 'high': return { color: 'destructive' as const, icon: AlertTriangle, label: 'Élevée' }; case 'medium': return { color: 'default' as const, icon: Clock, label: 'Moyenne' }; case 'low': return { color: 'secondary' as const, icon: Circle, label: 'Faible' }; default: return { color: 'secondary' as const, icon: Circle, label: 'Moyenne' }; } }; const getDueDateStatus = (dueDate: Date | string | null, status: string) => { if (!dueDate || status === 'completed') return null; const due = new Date(dueDate); const now = new Date(); const daysDiff = differenceInDays(due, now); if (isPast(due)) { return { type: 'overdue', color: 'destructive' as const, icon: CalendarX, label: `En retard de ${Math.abs(daysDiff)} jour${Math.abs(daysDiff) > 1 ? 's' : ''}`, text: 'En retard' }; } else if (isToday(due)) { return { type: 'today', color: 'secondary' as const, icon: CalendarClock, label: "Échéance aujourd'hui", text: "Aujourd'hui" }; } else if (daysDiff <= 3) { return { type: 'soon', color: 'default' as const, icon: Calendar, label: `Dans ${daysDiff} jour${daysDiff > 1 ? 's' : ''}`, text: `${daysDiff}j` }; } return { type: 'normal', color: 'outline' as const, icon: Calendar, label: format(due, 'dd/MM/yyyy', { locale: fr }), text: format(due, 'dd/MM', { locale: fr }) }; }; const getStartDateStatus = (startDate: Date | string | null) => { if (!startDate) return null; try { const start = new Date(startDate); if (isNaN(start.getTime())) return null; const now = new Date(); const daysDiff = differenceInDays(start, now); if (isPast(start)) { return { type: 'started', color: 'default' as const, icon: CheckCircle, label: 'Démarrée', text: 'Active' }; } else if (isToday(start)) { return { type: 'today', color: 'secondary' as const, icon: CalendarClock, label: "Démarre aujourd'hui", text: "Aujourd'hui" }; } else { return { type: 'future', color: 'outline' as const, icon: Clock, label: `Démarre dans ${daysDiff} jour${daysDiff > 1 ? 's' : ''}`, text: `+${daysDiff}j` }; } } catch (error) { console.warn('Error parsing start date:', error, startDate); return null; } }; // Fonction pour déterminer si une tâche est future (pas encore commencée) const isTaskFuture = (task: TaskWithRelations) => { if (!task) return false; try { return task.isFutureTask || (task.startDate && !isPast(new Date(task.startDate))); } catch (error) { console.warn('Error checking if task is future:', error, task); return false; } }; const canCreateTasks = user?.role === 'admin' || user?.role === 'manager' || user?.role === 'directeur'; const canEditTasks = user?.role === 'admin' || user?.role === 'manager' || user?.role === 'directeur'; if (isLoading) { return (
); } return (
{/* Header */}

Gestion des Tâches

{totalItems} tâche{totalItems !== 1 ? 's' : ''} trouvée{totalItems !== 1 ? 's' : ''}

{/* Sélecteur de vue */} Liste Kanban {canCreateTasks && ( Créer une nouvelle tâche setShowCreateModal(false)} /> )}
{/* Sidebar avec filtres */}
{/* Filtres */} Filtres {/* Recherche */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Filtre par statut */}
{/* Filtre par priorité */}
{/* Filtre par échéance */}
{/* Zone principale avec les tâches */}
{/* Contenu selon la vue sélectionnée */} {/* Liste des tâches */}
{totalItems === 0 ? (

Aucune tâche

Aucune tâche trouvée avec les filtres sélectionnés.

) : (
{/* Tâches en cours */} {paginatedTasks.filter(task => task.status === 'pending').length > 0 && (

Tâches en cours ({paginatedTasks.filter(task => task.status === 'pending').length})

{paginatedTasks .filter(task => task.status === 'pending') .map((task) => { const priorityConfig = getPriorityConfig(task.priority); const PriorityIcon = priorityConfig.icon; const isFuture = isTaskFuture(task); const startDateStatus = getStartDateStatus(task.startDate); return (
{task.title} {isFuture && " (Programmée)"}
{priorityConfig.label} {isFuture && ( Future )}
{task.description && (

{task.description}

)}
Assigné à: {task.assignedTo} Créée: {format(new Date(task.createdAt), 'dd/MM/yyyy HH:mm', { locale: fr })}
{/* Affichage des dates : départ et échéance */}
{[ // Date de départ startDateStatus && (
📅 {startDateStatus.text} {startDateStatus.label} {task.startDate && ` (${format(new Date(task.startDate), 'dd/MM/yyyy', { locale: fr })})`}
), // Date d'échéance (() => { const dueDateStatus = getDueDateStatus(task.dueDate, task.status); if (dueDateStatus) { const DueDateIcon = dueDateStatus.icon; return (
⏰ {dueDateStatus.text} {dueDateStatus.label}
); } else if (task.dueDate) { return (
⏰ {format(new Date(task.dueDate), 'dd/MM', { locale: fr })}
); } return null; })() ].filter(Boolean)}
{/* Avertissement pour les tâches futures (admin/directeur) */} {isFuture && (
Visible en avance ({user?.role})
)} {canEditTasks && !isFuture && ( )} {canEditTasks && ( )} {canEditTasks && ( )}
); })}
)} {/* Tâches terminées */} {paginatedTasks.filter(task => task.status === 'completed').length > 0 && (

Tâches terminées ({paginatedTasks.filter(task => task.status === 'completed').length})

{paginatedTasks .filter(task => task.status === 'completed') .map((task) => { const priorityConfig = getPriorityConfig(task.priority); const PriorityIcon = priorityConfig.icon; return (
{task.title}
Terminée
{task.description && (

{task.description}

)}
Assigné à: {task.assignedTo} Créée: {format(new Date(task.createdAt), 'dd/MM/yyyy HH:mm', { locale: fr })} {task.completedAt && ( Terminée: {format(new Date(task.completedAt), 'dd/MM/yyyy HH:mm', { locale: fr })} )}
); })}
)}
)} {/* Pagination */} {totalItems > 0 && ( )}
{/* Vue Kanban */}
{/* Colonne En cours */}

En cours ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'pending').length})

{filteredTasks.filter((task: TaskWithRelations) => task.status === 'pending').map((task: TaskWithRelations) => { const priorityConfig = getPriorityConfig(task.priority); const PriorityIcon = priorityConfig.icon; const dueDateStatus = getDueDateStatus(task.dueDate, task.status); const DueDateIcon = dueDateStatus?.icon; return (
{task.title}
{priorityConfig.label}
{task.description && (

{task.description}

)}
Assigné à: {task.assignedTo}
{dueDateStatus && DueDateIcon && (
{dueDateStatus.text}
)}
{canEditTasks && ( <> )}
); })}
{/* Colonne Terminées */}

Terminées ({filteredTasks.filter((task: TaskWithRelations) => task.status === 'completed').length})

{filteredTasks.filter((task: TaskWithRelations) => task.status === 'completed').map((task: TaskWithRelations) => { const priorityConfig = getPriorityConfig(task.priority); const PriorityIcon = priorityConfig.icon; return (
{task.title}
{priorityConfig.label}
Terminée le: {task.completedAt ? format(new Date(task.completedAt), 'dd/MM/yyyy', { locale: fr }) : 'Date inconnue'}
{canEditTasks && ( <> )}
); })}
{/* Modal d'édition */} Modifier la tâche {selectedTask && ( { setShowEditModal(false); setSelectedTask(null); }} /> )} {/* Modal de confirmation de suppression */} Confirmer la suppression

Êtes-vous sûr de vouloir supprimer la tâche "{taskToDelete?.title}" ?

Cette action est irréversible.

); }