diff --git a/attached_assets/{825C5BBD-18CA-407E-A113-69924A7CBC2F}_1755596717328.png b/attached_assets/{825C5BBD-18CA-407E-A113-69924A7CBC2F}_1755596717328.png new file mode 100644 index 0000000..7bcab33 Binary files /dev/null and b/attached_assets/{825C5BBD-18CA-407E-A113-69924A7CBC2F}_1755596717328.png differ diff --git a/client/src/components/tasks/TaskForm.tsx b/client/src/components/tasks/TaskForm.tsx index b12e54f..7c37f16 100644 --- a/client/src/components/tasks/TaskForm.tsx +++ b/client/src/components/tasks/TaskForm.tsx @@ -1,2 +1,2 @@ // Version ULTRA PROPRE pour production - ZÉRO dépendance externe -export { default } from "./TaskFormClean"; \ No newline at end of file +export { default } from "./TaskFormProduction"; \ No newline at end of file diff --git a/client/src/components/tasks/TaskFormProduction.tsx b/client/src/components/tasks/TaskFormProduction.tsx index 9d82a7f..1f63d01 100644 --- a/client/src/components/tasks/TaskFormProduction.tsx +++ b/client/src/components/tasks/TaskFormProduction.tsx @@ -1,179 +1,99 @@ -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 { useState } from "react"; 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"; -// 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"] -}); +// Version PRODUCTION - Zero dépendance externe problématique +export default function TaskFormProduction({ task, onClose }: any) { + const [title, setTitle] = useState(task?.title || ""); + const [description, setDescription] = useState(task?.description || ""); + const [priority, setPriority] = useState(task?.priority || "medium"); + const [status, setStatus] = useState(task?.status || "pending"); + const [assignedTo, setAssignedTo] = useState(task?.assignedTo || "admin"); + const [startDate, setStartDate] = useState( + task?.startDate ? new Date(task.startDate) : undefined + ); + const [dueDate, setDueDate] = useState( + task?.dueDate ? new Date(task.dueDate) : undefined + ); + const [isSubmitting, setIsSubmitting] = useState(false); -type TaskFormData = z.infer; + const formatDate = (date: Date) => { + return date.toLocaleDateString('fr-FR', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }); + }; -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) => { - console.error('Erreur création tâche:', error); - toast({ - title: "Erreur", - description: "Erreur lors de la création de la tâche", - variant: "destructive", - }); - }, - }); - - const updateMutation = useMutation({ - mutationFn: (data: any) => { - return apiRequest(`/api/tasks/${task?.id}`, "PATCH", data); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['/api/tasks'] }); - toast({ - title: "Succès", - description: "Tâche modifiée avec succès", - }); - onClose(); - }, - onError: (error) => { - console.error('Erreur modification tâche:', error); - toast({ - title: "Erreur", - description: "Erreur lors de la modification de la tâche", - variant: "destructive", - }); - }, - }); - - const onSubmit = (data: TaskFormData) => { - if (!localSelectedStoreId) { - toast({ - title: "Erreur", - description: "Aucun magasin disponible", - variant: "destructive", - }); + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!title.trim()) { + alert("Le titre est requis"); return; } - if (task) { - updateMutation.mutate(data); - } else { - createMutation.mutate(data); + if (!assignedTo.trim()) { + alert("L'assignation est requise"); + return; + } + + if (startDate && dueDate && dueDate < startDate) { + alert("La date d'échéance ne peut pas être antérieure à la date de début"); + return; + } + + setIsSubmitting(true); + + try { + const taskData = { + title: title.trim(), + description: description.trim(), + priority, + status, + assignedTo: assignedTo.trim(), + startDate: startDate ? startDate.toISOString() : null, + dueDate: dueDate ? dueDate.toISOString() : null, + }; + + const url = task ? `/api/tasks/${task.id}` : '/api/tasks'; + const method = task ? 'PATCH' : 'POST'; + + if (!task) { + // Créer nouvelle tâche + (taskData as any).groupId = 1; // Store ID par défaut + (taskData as any).createdBy = 'admin'; // User ID par défaut + } + + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(taskData), + }); + + if (!response.ok) { + throw new Error(`Erreur ${response.status}`); + } + + // Succès + alert(task ? "Tâche modifiée avec succès" : "Tâche créée avec succès"); + + // Force reload de la page pour actualiser les données + window.location.reload(); + + } catch (error) { + console.error('Erreur:', error); + alert("Erreur lors de l'opération"); + } finally { + setIsSubmitting(false); } }; @@ -181,10 +101,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
-

- {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

@@ -194,212 +111,137 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
-
- - {/* Titre */} - ( - - Titre * - - - - - - )} + +
+ + setTitle(e.target.value)} + placeholder="Titre de la tâche" + required /> +
- {/* Description */} - ( - - Description - -