diff --git a/README.md b/README.md new file mode 100644 index 0000000..d84330e --- /dev/null +++ b/README.md @@ -0,0 +1,228 @@ +# Plateforme de Gestion Logistique avec Météo Intégrée + +Une plateforme d'entreprise moderne pour la gestion logistique, le suivi des livraisons et l'intégration météorologique, conçue pour optimiser les opérations commerciales avec des insights météorologiques en temps réel. + +## 🌟 Fonctionnalités Principales + +### 📦 Gestion des Commandes et Livraisons +- **Création et suivi des commandes** : Interface complète pour gérer les commandes clients +- **Gestion des livraisons** : Suivi détaillé des livraisons avec statuts en temps réel +- **Liaison commandes-livraisons** : Association automatique et manuelle des commandes aux livraisons +- **Historique complet** : Traçabilité complète de toutes les opérations + +### 🌤️ Widget Météo Intelligent +- **Météo actuelle** : Affichage de la température et conditions météorologiques du jour +- **Comparaison annuelle** : Comparaison avec la même date de l'année précédente +- **Géolocalisation automatique** : Détection automatique de votre localisation +- **Interface bilingue** : Support complet du français +- **Mise à jour automatique** : Données rafraîchies toutes les 30 minutes + +### 🛠️ Service Après-Vente (SAV) +- **Gestion des tickets** : Système complet de tickets de support +- **Historique des interventions** : Suivi détaillé de tous les échanges +- **Statuts personnalisés** : Gestion flexible des états de tickets +- **Notifications** : Alertes automatiques pour les mises à jour + +### 📊 Réconciliation et Facturation +- **Réconciliation BL/Factures** : Rapprochement automatique des bons de livraison et factures +- **Vérification intelligente** : Cache optimisé pour les vérifications répétées +- **Gestion des conflits** : Résolution automatique des incohérences +- **Mise à jour en temps réel** : Interface réactive avec feedback immédiat + +### 👥 Gestion Multi-Tenant +- **Groupes d'utilisateurs** : Organisation par entreprise ou département +- **Permissions granulaires** : Contrôle d'accès fin par fonctionnalité +- **Rôles personnalisés** : Admin, employé, client avec droits spécifiques +- **Isolation des données** : Séparation stricte entre les organisations + +### 💾 Sauvegarde et Sécurité +- **Sauvegardes automatiques** : Backup quotidien programmé à 2h du matin +- **Sauvegardes manuelles** : Possibilité de créer des sauvegardes à la demande +- **Authentification sécurisée** : Sessions chiffrées et gestion des mots de passe +- **Logs détaillés** : Traçabilité complète des opérations + +## 🚀 Installation et Configuration + +### Prérequis +- Node.js 18+ +- PostgreSQL 13+ +- Clé API Visual Crossing (gratuite sur [visualcrossing.com](https://www.visualcrossing.com/)) + +### Installation +```bash +# Cloner le projet +git clone [URL_DU_PROJET] +cd weather-management-platform + +# Installer les dépendances +npm install + +# Configurer la base de données +cp .env.example .env +# Éditer .env avec vos paramètres de base de données + +# Initialiser la base de données +npm run db:push + +# Démarrer l'application +npm run dev +``` + +### Configuration de l'API Météo +1. Créez un compte gratuit sur [Visual Crossing](https://www.visualcrossing.com/) +2. Obtenez votre clé API +3. Connectez-vous avec `admin`/`admin` +4. Allez dans **Utilitaires** → **Paramètres Météo** +5. Entrez votre clé API et votre localisation +6. Testez la connexion et sauvegardez + +## 📱 Utilisation + +### Première Connexion +- **Identifiants par défaut** : `admin` / `admin` +- **Changement obligatoire** : Modifiez le mot de passe lors de la première connexion +- **Configuration météo** : Configurez l'API météo dans les utilitaires + +### Navigation Principale +- **Tableau de bord** : Vue d'ensemble avec météo et statistiques +- **Commandes** : Gestion complète des commandes clients +- **Livraisons** : Suivi et gestion des livraisons +- **SAV** : Service après-vente et tickets de support +- **Réconciliation** : Rapprochement BL/Factures +- **Utilitaires** : Configuration et outils administrateur + +### Fonctionnalités Courantes + +#### Créer une Commande +1. Allez dans **Commandes** → **Nouvelle Commande** +2. Remplissez les informations client +3. Ajoutez les articles et quantités +4. Validez et sauvegardez + +#### Suivre une Livraison +1. Accédez à **Livraisons** +2. Recherchez par numéro ou client +3. Consultez le statut et l'historique +4. Mettez à jour si nécessaire + +#### Gérer un Ticket SAV +1. Ouvrez **SAV** → **Nouveau Ticket** +2. Décrivez le problème +3. Assignez la priorité +4. Suivez les échanges dans l'historique + +## 🛠️ Technologies Utilisées + +### Frontend +- **React 18** : Interface utilisateur moderne +- **TypeScript** : Typage statique pour la robustesse +- **Tailwind CSS** : Design system cohérent +- **Shadcn/UI** : Composants UI professionnels +- **React Query** : Gestion d'état et cache intelligent +- **Wouter** : Routage léger et performant + +### Backend +- **Node.js** : Runtime JavaScript haute performance +- **Express** : Framework web minimaliste +- **TypeScript** : Développement sécurisé côté serveur +- **Drizzle ORM** : ORM moderne et type-safe +- **Zod** : Validation de schémas robuste + +### Base de Données +- **PostgreSQL** : Base de données relationnelle performante +- **Migrations automatiques** : Évolution de schéma sécurisée +- **Backup intégré** : Système de sauvegarde automatique + +### APIs Externes +- **Visual Crossing Weather API** : Données météorologiques fiables +- **OpenStreetMap Nominatim** : Géocodage inverse pour la géolocalisation + +## 📁 Structure du Projet + +``` +├── client/ # Frontend React +│ ├── src/ +│ │ ├── components/ # Composants réutilisables +│ │ ├── pages/ # Pages de l'application +│ │ ├── lib/ # Utilitaires et configuration +│ │ └── hooks/ # Hooks React personnalisés +├── server/ # Backend Express +│ ├── routes.ts # Définition des routes API +│ ├── storage.ts # Interface de stockage +│ ├── weatherService.ts # Service météorologique +│ └── index.ts # Point d'entrée du serveur +├── shared/ # Code partagé +│ └── schema.ts # Schémas de base de données +├── migrations/ # Migrations de base de données +└── backups/ # Dossier des sauvegardes +``` + +## 🔧 Configuration Avancée + +### Variables d'Environnement +```env +# Base de données +DATABASE_URL=postgresql://user:password@localhost:5432/dbname + +# Météo +VISUAL_CROSSING_API_KEY=votre_clé_api_ici + +# Application +NODE_ENV=production +PORT=3000 +``` + +### Personnalisation +- **Thèmes** : Mode sombre/clair automatique +- **Localisation** : Interface entièrement en français +- **Permissions** : Système de rôles configurable +- **Notifications** : Alertes personnalisables + +## 📈 Monitoring et Maintenance + +### Logs +- **Développement** : Logs détaillés en console +- **Production** : Logs optimisés pour les performances +- **Erreurs** : Gestion gracieuse avec fallbacks + +### Performance +- **Cache intelligent** : Mise en cache des données météo +- **Lazy loading** : Chargement optimisé des composants +- **Optimisation bundle** : Build production optimisé + +### Sauvegardes +- **Automatiques** : Tous les jours à 2h du matin +- **Manuelles** : Via l'interface d'administration +- **Restauration** : Processus guidé de restauration + +## 🆘 Support et Dépannage + +### Problèmes Courants + +**L'API météo ne fonctionne pas** +- Vérifiez votre clé API Visual Crossing +- Testez la connexion dans les paramètres +- Vérifiez votre quota API + +**Données manquantes** +- Vérifiez la connexion à la base de données +- Consultez les logs serveur +- Vérifiez les permissions utilisateur + +**Interface lente** +- Videz le cache navigateur +- Vérifiez votre connexion internet +- Redémarrez l'application + +### Contact +Pour toute question technique ou fonctionnelle, consultez les logs de l'application ou contactez l'équipe de développement. + +## 📄 Licence + +Ce projet est développé pour un usage d'entreprise. Tous droits réservés. + +--- + +**Version actuelle** : 2.27.9 +**Dernière mise à jour** : Août 2025 +**Compatibilité** : Node.js 18+, PostgreSQL 13+, Navigateurs modernes \ No newline at end of file diff --git a/client/src/components/tasks/TaskForm.tsx b/client/src/components/tasks/TaskForm.tsx index 8cacd1d..e278adc 100644 --- a/client/src/components/tasks/TaskForm.tsx +++ b/client/src/components/tasks/TaskForm.tsx @@ -9,11 +9,16 @@ 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"; +import { cn } from "@/lib/utils"; type TaskWithRelations = Task & { assignedUser: { id: string; username: string; firstName?: string; lastName?: string; }; @@ -21,15 +26,14 @@ type TaskWithRelations = Task & { group: { id: number; name: string; color: string; }; }; -// Schéma simplifié sans magasin et sans date d'échéance -const taskFormSchema = insertTaskSchema.omit({ - id: true, - createdAt: true, - updatedAt: true, - completedAt: true, - createdBy: true, - groupId: true, - dueDate: true, // Suppression du champ échéance +// Schéma avec date d'échéance incluse +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"), + dueDate: z.date().optional(), }); type TaskFormData = z.infer; @@ -113,10 +117,10 @@ export default function TaskForm({ task, onClose }: TaskFormProps) { defaultValues: { title: task?.title || "", description: task?.description || "", - priority: task?.priority || "medium", - status: task?.status || "pending", + priority: (task?.priority as "low" | "medium" | "high") || "medium", + status: (task?.status as "pending" | "completed") || "pending", assignedTo: task?.assignedTo || "", - + dueDate: task?.dueDate ? new Date(task.dueDate) : undefined, }, }); @@ -190,6 +194,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) { priority: data.priority, status: data.status, assignedTo: data.assignedTo, + dueDate: data.dueDate?.toISOString(), }; updateMutation.mutate(submitData); } else { @@ -217,7 +222,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) { return (
- + {/* Affichage du magasin auto-sélectionné */} {localSelectedStoreId && (
@@ -236,7 +241,7 @@ export default function TaskForm({ task, onClose }: TaskFormProps) {
{selectedGroup.name} @@ -369,7 +374,49 @@ export default function TaskForm({ task, onClose }: TaskFormProps) { )} /> - + {/* Date d'échéance */} + ( + + Date d'échéance (optionnel) + + + + + + + + + date < new Date(new Date().setHours(0, 0, 0, 0)) + } + initialFocus + locale={fr} + /> + + + + + )} + /> {/* Boutons d'action */}
diff --git a/client/src/pages/Tasks.tsx b/client/src/pages/Tasks.tsx index 0717097..d55737b 100644 --- a/client/src/pages/Tasks.tsx +++ b/client/src/pages/Tasks.tsx @@ -10,6 +10,7 @@ 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, @@ -19,9 +20,13 @@ import { AlertTriangle, CheckCircle, Edit, - Trash2 + Trash2, + Calendar, + CalendarX, + CalendarClock, + Kanban } from "lucide-react"; -import { format } from "date-fns"; +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"; @@ -42,6 +47,8 @@ export default function Tasks() { 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); @@ -56,7 +63,7 @@ export default function Tasks() { queryFn: () => { const params = new URLSearchParams(); if (selectedStoreId) { - params.append('storeId', selectedStoreId); + params.append('storeId', selectedStoreId.toString()); } return fetch(`/api/tasks?${params.toString()}`, { credentials: 'include' @@ -157,26 +164,67 @@ export default function Tasks() { } }; - // Filtrer 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; - } + // 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 statut + if (statusFilter !== "all" && task.status !== statusFilter) { + return false; + } - // Filtre par priorité - if (priorityFilter !== "all" && task.priority !== priorityFilter) { - return false; - } + // Filtre par priorité + if (priorityFilter !== "all" && task.priority !== priorityFilter) { + return false; + } - return true; - }); + // 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 { @@ -218,6 +266,48 @@ export default function Tasks() { } }; + const getDueDateStatus = (dueDate: 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 canCreateTasks = user?.role === 'admin' || user?.role === 'manager' || user?.role === 'directeur'; const canEditTasks = user?.role === 'admin' || user?.role === 'manager' || user?.role === 'directeur'; @@ -243,24 +333,40 @@ export default function Tasks() { {totalItems} tâche{totalItems !== 1 ? 's' : ''} trouvée{totalItems !== 1 ? 's' : ''}

- {canCreateTasks && ( - - - - - - - Créer une nouvelle tâche - - setShowCreateModal(false)} - /> - - - )} +
+ {/* Sélecteur de vue */} + + + + + Liste + + + + Kanban + + + + + {canCreateTasks && ( + + + + + + + Créer une nouvelle tâche + + setShowCreateModal(false)} + /> + + + )} +
@@ -318,14 +424,36 @@ export default function Tasks() { + + {/* Filtre par échéance */} +
+ + +
{/* Zone principale avec les tâches */}
- {/* Liste des tâches */} -
+ {/* Contenu selon la vue sélectionnée */} + + + {/* Liste des tâches */} +
{totalItems === 0 ? (
@@ -372,7 +500,7 @@ export default function Tasks() {

)} -
+
Assigné à: {task.assignedTo} @@ -380,6 +508,33 @@ export default function Tasks() { Créée: {format(new Date(task.createdAt), 'dd/MM/yyyy HH:mm', { locale: fr })}
+ + {/* Affichage de l'é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; + })()}
@@ -492,19 +647,163 @@ export default function Tasks() {
)} - {/* Pagination */} - {totalItems > 0 && ( - - )} -
+ {/* 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 && ( + <> + + + + )} +
+
+
+ ); + })} +
+
+
+
+
+
diff --git a/replit.md b/replit.md index c7583fe..746a952 100644 --- a/replit.md +++ b/replit.md @@ -34,6 +34,7 @@ The platform is built with a clear separation of concerns, utilizing a modern fu - **Reconciliation:** Functionality for reconciling Bills of Lading (BL) and invoices with immediate UI updates. - **Invoice Verification Cache:** Robust caching system for invoice verification results with automatic expiration and conflict resolution. Enhanced with unique constraint handling and graceful error management for production stability (August 2025). Fixed production SSL certificate errors and fetch timeout loops with enhanced error handling, network timeout protection, and error result caching to prevent infinite retry loops (August 2025). - **UI Scrollbar Optimization:** Eliminated redundant vertical scrollbar on the right side of the screen while preserving necessary internal page scrolling. Implemented global CSS rules with `overflow: hidden` on html/body/root and Layout height management to prevent vertical overflow issues in production (August 2025). +- **Task Management:** Système complet de gestion des tâches avec fonctionnalités temporelles avancées - tri intelligent (pending > priorité > date), dates d'échéance avec sélecteur calendrier, indicateurs visuels pour tâches en retard/urgentes, filtres par échéance (aujourd'hui, cette semaine, en retard, sans échéance), vue Kanban et liste, badges colorés pour statut temporel. Interface optimisée pour suivi prioritaire des tâches non validées (Août 2025). - **User and Permission Management:** Granular access controls ensuring that users (e.g., employees) have appropriate permissions for tasks like creating customer orders. - **Reporting:** Statistical reporting, including monthly summaries of deliveries based on `deliveredDate`.