import { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient, useQuery } 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 { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { apiRequest } from "@/lib/queryClient"; import { insertTaskSchema, Task, Group } from "@shared/schema"; import { z } from "zod"; import { CalendarIcon } from "lucide-react"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; // Fonction utilitaire cn intégrée pour éviter les erreurs de dépendance en production function cn(...classes: (string | undefined | null | false)[]): string { return classes.filter(Boolean).join(' '); } // Schéma robuste avec dates de début et d'échéance pour production const taskFormSchema = z.object({ title: z.string().min(1, "Le titre est requis"), description: z.string().optional(), priority: z.enum(["low", "medium", "high"]).default("medium"), status: z.enum(["pending", "completed"]).default("pending"), assignedTo: z.string().min(1, "L'assignation est requise"), startDate: z.date().optional(), // Date de début dueDate: z.date().optional(), // Date d'échéance }).refine((data) => { // Validation : la date d'échéance ne peut pas être antérieure à la date de début if (data.startDate && data.dueDate) { return data.dueDate >= data.startDate; } return true; }, { message: "La date d'échéance ne peut pas être antérieure à la date de début", path: ["dueDate"] }); type TaskFormData = z.infer; 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; }; }; interface TaskFormProps { task?: TaskWithRelations; onClose: () => void; } export default function TaskForm({ task, onClose }: TaskFormProps) { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const { toast } = useToast(); const queryClient = useQueryClient(); // État local pour gérer le magasin sélectionné const [localSelectedStoreId, setLocalSelectedStoreId] = useState(null); // Récupération sécurisée des magasins const { data: groupsData = [], error: groupsError } = useQuery({ queryKey: ['/api/groups'], enabled: !!user, }); // Log des erreurs de groupes si nécessaire if (groupsError) { console.warn('Erreur lors du chargement des magasins:', groupsError); } const groups = Array.isArray(groupsData) ? groupsData.filter(g => g && typeof g === 'object' && g.id) : []; // Auto-sélection du magasin useEffect(() => { if (groups.length > 0 && !localSelectedStoreId) { let defaultStoreId: number | null = null; if (user?.role === 'admin') { if (selectedStoreId && groups.find(g => g.id === selectedStoreId)) { defaultStoreId = selectedStoreId; } else { defaultStoreId = groups[0].id; } } else { defaultStoreId = groups[0].id; } if (defaultStoreId) { setLocalSelectedStoreId(defaultStoreId); } } }, [groups, selectedStoreId, user?.role, localSelectedStoreId]); const form = useForm({ resolver: zodResolver(taskFormSchema), defaultValues: { title: task?.title || "", description: task?.description || "", priority: (task?.priority as "low" | "medium" | "high") || "medium", status: (task?.status as "pending" | "completed") || "pending", assignedTo: task?.assignedTo || "", startDate: task?.startDate ? new Date(task.startDate) : undefined, dueDate: task?.dueDate ? new Date(task.dueDate) : undefined, }, }); // Mutations sécurisées const createMutation = useMutation({ mutationFn: (data: any) => { const taskData = { ...data, groupId: localSelectedStoreId, createdBy: user?.id, startDate: data.startDate || null, dueDate: data.dueDate || null }; return apiRequest("/api/tasks", "POST", taskData); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); toast({ title: "Succès", description: "Tâche créée avec succès", }); onClose(); }, onError: (error: any) => { console.error("Error creating task:", error); toast({ title: "Erreur", description: "Impossible de créer la tâche", variant: "destructive", }); }, }); const updateMutation = useMutation({ mutationFn: (data: any) => { return apiRequest(`/api/tasks/${task?.id}`, "PUT", data); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/tasks"] }); toast({ title: "Succès", description: "Tâche modifiée avec succès", }); onClose(); }, onError: (error: any) => { console.error("Error updating task:", error); toast({ title: "Erreur", description: "Impossible de modifier la tâche", variant: "destructive", }); }, }); const onSubmit = (data: TaskFormData) => { if (!localSelectedStoreId) { toast({ title: "Erreur", description: "Aucun magasin disponible", variant: "destructive", }); return; } if (task) { updateMutation.mutate(data); } else { createMutation.mutate(data); } }; return (

{task ? "Modifier la tâche" : "Nouvelle tâche"}

📅 Date de début = Quand la tâche devient visible
⏰ Date d'échéance = Quand la tâche doit être terminée

{/* Titre */} ( Titre * )} /> {/* Description */} ( Description