diff --git a/FINAL_PRODUCTION_FIX.md b/FINAL_PRODUCTION_FIX.md new file mode 100644 index 0000000..01f3998 --- /dev/null +++ b/FINAL_PRODUCTION_FIX.md @@ -0,0 +1,39 @@ +# FIX FINAL PRODUCTION - ERREUR CN (19 Août 2025) + +## 🚫 PROBLÈME PERSISTANT +L'erreur `ReferenceError: cn is not defined` persiste malgré les corrections précédentes. + +## ✅ SOLUTION FINALE + +### Nouveau fichier créé : `TaskFormProduction.tsx` +- ✅ **ZÉRO utilisation de la fonction `cn`** +- ✅ Classes CSS écrites directement avec conditions ternaires +- ✅ Toutes les fonctionnalités conservées (dates de début + échéance) +- ✅ Validation robuste des dates +- ✅ Interface complète avec icônes + +### Modification de `TaskForm.tsx` +- ✅ Export maintenant vers `TaskFormProduction` au lieu de `SimpleTaskForm` +- ✅ Garantit que le module tâches utilise la version sans cn + +## 📁 FICHIERS À COPIER EN PRODUCTION +1. `client/src/components/tasks/TaskForm.tsx` (modifié) +2. `client/src/components/tasks/TaskFormProduction.tsx` (nouveau) + +## 🎯 AVANTAGES +- **100% sans dépendance cn** : Aucune utilisation de la fonction problématique +- **Classes directes** : `className={field.value ? "classe1" : "classe2"}` +- **Même interface** : Toutes les fonctionnalités préservées +- **Production ready** : Conçu spécifiquement pour éviter les erreurs + +## ⚡ DÉPLOIEMENT +```bash +# Copier les 2 fichiers sur votre serveur +# Redémarrer +docker-compose restart +``` + +## ✅ RÉSULTAT ATTENDU +- ✅ Module tâches se charge sans erreur +- ✅ Formulaire avec dates de début et d'échéance +- ✅ Plus jamais d'erreur `cn is not defined` \ No newline at end of file diff --git a/client/src/components/tasks/SimpleTaskForm.tsx b/client/src/components/tasks/SimpleTaskForm.tsx index 22554cb..bdbed09 100644 --- a/client/src/components/tasks/SimpleTaskForm.tsx +++ b/client/src/components/tasks/SimpleTaskForm.tsx @@ -18,10 +18,7 @@ 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(' '); -} +// Supprimer complètement l'utilisation de cn pour éviter toute erreur de production // Schéma robuste avec dates de début et d'échéance pour production const taskFormSchema = z.object({ diff --git a/client/src/components/tasks/TaskForm.tsx b/client/src/components/tasks/TaskForm.tsx index 1850932..1226b72 100644 --- a/client/src/components/tasks/TaskForm.tsx +++ b/client/src/components/tasks/TaskForm.tsx @@ -1,3 +1,2 @@ -// Re-export du nouveau formulaire de tâches avec dates de départ et d'échéance -// Version corrigée avec dates de début et d'échéance pour production -export { default } from "./SimpleTaskForm"; \ No newline at end of file +// Version SANS CN pour production - élimine toutes les erreurs de dépendance +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 new file mode 100644 index 0000000..13ea11d --- /dev/null +++ b/client/src/components/tasks/TaskFormProduction.tsx @@ -0,0 +1,411 @@ +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"; + +// 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", { + method: "POST", + body: JSON.stringify(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}`, { + method: "PATCH", + body: JSON.stringify(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", + }); + 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 + +