diff --git a/client/src/components/ResponsiveCalendar.tsx b/client/src/components/ResponsiveCalendar.tsx new file mode 100644 index 0000000..22a6758 --- /dev/null +++ b/client/src/components/ResponsiveCalendar.tsx @@ -0,0 +1,16 @@ +/** + * ResponsiveCalendar - Wrapper for automatic mobile/desktop detection + */ +import { useScreenSize } from "@/hooks/use-screen-size"; +import Calendar from "@/pages/Calendar"; +import MobileCalendarPage from "@/pages/mobile/CalendarPage"; + +export default function ResponsiveCalendar() { + const { isMobile } = useScreenSize(); + + if (isMobile) { + return ; + } + + return ; +} diff --git a/client/src/components/ResponsiveDashboard.tsx b/client/src/components/ResponsiveDashboard.tsx new file mode 100644 index 0000000..33bd5ba --- /dev/null +++ b/client/src/components/ResponsiveDashboard.tsx @@ -0,0 +1,16 @@ +/** + * ResponsiveDashboard - Wrapper for automatic mobile/desktop detection + */ +import { useScreenSize } from "@/hooks/use-screen-size"; +import Dashboard from "@/pages/Dashboard"; +import MobileDashboardPage from "@/pages/mobile/DashboardPage"; + +export default function ResponsiveDashboard() { + const { isMobile } = useScreenSize(); + + if (isMobile) { + return ; + } + + return ; +} diff --git a/client/src/components/ResponsiveDeliveries.tsx b/client/src/components/ResponsiveDeliveries.tsx new file mode 100644 index 0000000..f5f6001 --- /dev/null +++ b/client/src/components/ResponsiveDeliveries.tsx @@ -0,0 +1,16 @@ +/** + * ResponsiveDeliveries - Wrapper for automatic mobile/desktop detection + */ +import { useScreenSize } from "@/hooks/use-screen-size"; +import Deliveries from "@/pages/Deliveries"; +import MobileDeliveriesPage from "@/pages/mobile/DeliveriesPage"; + +export default function ResponsiveDeliveries() { + const { isMobile } = useScreenSize(); + + if (isMobile) { + return ; + } + + return ; +} diff --git a/client/src/components/ResponsiveOrders.tsx b/client/src/components/ResponsiveOrders.tsx new file mode 100644 index 0000000..a926e21 --- /dev/null +++ b/client/src/components/ResponsiveOrders.tsx @@ -0,0 +1,16 @@ +/** + * ResponsiveOrders - Wrapper for automatic mobile/desktop detection + */ +import { useScreenSize } from "@/hooks/use-screen-size"; +import Orders from "@/pages/Orders"; +import MobileOrdersPage from "@/pages/mobile/OrdersPage"; + +export default function ResponsiveOrders() { + const { isMobile } = useScreenSize(); + + if (isMobile) { + return ; + } + + return ; +} diff --git a/client/src/components/RouterProduction.tsx b/client/src/components/RouterProduction.tsx index 10b18a3..536f19b 100644 --- a/client/src/components/RouterProduction.tsx +++ b/client/src/components/RouterProduction.tsx @@ -21,6 +21,10 @@ import BackupManager from "@/pages/BackupManager"; import Utilities from "@/pages/Utilities"; import Tasks from "@/pages/Tasks"; import ResponsiveTasks from "@/components/ResponsiveTasks"; +import ResponsiveDashboard from "@/components/ResponsiveDashboard"; +import ResponsiveOrders from "@/components/ResponsiveOrders"; +import ResponsiveDeliveries from "@/components/ResponsiveDeliveries"; +import ResponsiveCalendar from "@/components/ResponsiveCalendar"; import SavTickets from "@/pages/SavTickets"; import Avoirs from "@/pages/Avoirs"; import WeatherSettings from "@/pages/WeatherSettings"; @@ -90,10 +94,10 @@ function RouterProduction() { return ( - - - - + + + + diff --git a/client/src/pages/mobile/CalendarPage.tsx b/client/src/pages/mobile/CalendarPage.tsx new file mode 100644 index 0000000..3c4c708 --- /dev/null +++ b/client/src/pages/mobile/CalendarPage.tsx @@ -0,0 +1,218 @@ +/** + * CalendarPage Mobile - Calendrier simplifié pour mobile + */ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { useStore } from "@/contexts/StoreContext"; +import MobileLayout from "./MobileLayout"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Calendar as CalendarIcon, + ChevronLeft, + ChevronRight, + Clock, + MapPin +} from "lucide-react"; +import { + format, + startOfMonth, + endOfMonth, + eachDayOfInterval, + isSameMonth, + isToday, + addMonths, + subMonths, + startOfWeek, + endOfWeek, + isSameDay +} from "date-fns"; +import { fr } from "date-fns/locale"; + +// Type pour les événements +type CalendarEvent = { + id: number; + title: string; + startDate: string; + endDate?: string; + description?: string; + location?: string; + type?: string; +}; + +export default function MobileCalendarPage() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + const [currentMonth, setCurrentMonth] = useState(new Date()); + const [selectedDate, setSelectedDate] = useState(new Date()); + + // Fetch calendar events + const { data: events = [], isLoading } = useQuery({ + queryKey: ["/api/calendar/events", selectedStoreId, format(currentMonth, 'yyyy-MM')], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); + params.append('month', format(currentMonth, 'yyyy-MM')); + + const response = await fetch(`/api/calendar/events?${params}`, { + credentials: 'include' + }); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data : []; + }, + enabled: !!user, + }); + + // Get days for the calendar grid + const monthStart = startOfMonth(currentMonth); + const monthEnd = endOfMonth(currentMonth); + const calendarStart = startOfWeek(monthStart, { locale: fr }); + const calendarEnd = endOfWeek(monthEnd, { locale: fr }); + const calendarDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd }); + + // Get events for selected date + const selectedDateEvents = selectedDate + ? events.filter((event: CalendarEvent) => + isSameDay(new Date(event.startDate), selectedDate) + ) + : []; + + // Check if a day has events + const hasEvents = (day: Date) => { + return events.some((event: CalendarEvent) => + isSameDay(new Date(event.startDate), day) + ); + }; + + const navigateMonth = (direction: 'prev' | 'next') => { + setCurrentMonth(direction === 'prev' + ? subMonths(currentMonth, 1) + : addMonths(currentMonth, 1) + ); + }; + + return ( + +
+ {/* Header */} +
+
+ +

Calendrier

+
+
+ + {/* Month navigation */} +
+ + +

+ {format(currentMonth, 'MMMM yyyy', { locale: fr })} +

+ + +
+ + {/* Calendar grid */} + + + {/* Weekday headers */} +
+ {['L', 'M', 'M', 'J', 'V', 'S', 'D'].map((day, i) => ( +
+ {day} +
+ ))} +
+ + {/* Days grid */} +
+ {calendarDays.map((day, i) => { + const isCurrentMonth = isSameMonth(day, currentMonth); + const isTodayDate = isToday(day); + const isSelected = selectedDate && isSameDay(day, selectedDate); + const dayHasEvents = hasEvents(day); + + return ( + + ); + })} +
+
+
+ + {/* Events for selected date */} + {selectedDate && ( +
+

+ {format(selectedDate, 'd MMMM', { locale: fr })} +

+ + {selectedDateEvents.length === 0 ? ( +

+ Aucun événement +

+ ) : ( + selectedDateEvents.map((event: CalendarEvent) => ( + + +

+ {event.title} +

+ {event.startDate && ( +

+ + {format(new Date(event.startDate), 'HH:mm')} +

+ )} + {event.location && ( +

+ + {event.location} +

+ )} +
+
+ )) + )} +
+ )} +
+
+ ); +} diff --git a/client/src/pages/mobile/DashboardPage.tsx b/client/src/pages/mobile/DashboardPage.tsx new file mode 100644 index 0000000..53c6ef2 --- /dev/null +++ b/client/src/pages/mobile/DashboardPage.tsx @@ -0,0 +1,234 @@ +/** + * DashboardPage Mobile - Tableau de bord simplifié pour mobile + */ +import { useQuery } from "@tanstack/react-query"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { useStore } from "@/contexts/StoreContext"; +import MobileLayout from "./MobileLayout"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Package, + Truck, + CheckSquare, + AlertTriangle, + TrendingUp, + Clock, + Calendar +} from "lucide-react"; +import { format } from "date-fns"; +import { fr } from "date-fns/locale"; + +// Stats card component +function StatCard({ + icon: Icon, + label, + value, + trend, + color +}: { + icon: React.ComponentType<{ className?: string }>; + label: string; + value: string | number; + trend?: string; + color: string; +}) { + return ( + + +
+
+ +
+ {trend && ( + + + {trend} + + )} +
+
+

{value}

+

{label}

+
+
+
+ ); +} + +export default function MobileDashboardPage() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + + // Fetch stats + const { data: stats, isLoading } = useQuery({ + queryKey: ["/api/stats/dashboard", selectedStoreId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); + + const response = await fetch(`/api/stats/dashboard?${params}`, { + credentials: 'include' + }); + if (!response.ok) return null; + return response.json(); + }, + enabled: !!user, + }); + + // Fetch pending tasks count + const { data: tasks = [] } = useQuery({ + queryKey: ["/api/tasks", selectedStoreId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); + + const response = await fetch(`/api/tasks?${params}`, { + credentials: 'include' + }); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data : []; + }, + enabled: !!user, + }); + + // Fetch today's orders + const { data: orders = [] } = useQuery({ + queryKey: ["/api/orders/today", selectedStoreId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); + params.append('date', format(new Date(), 'yyyy-MM-dd')); + + const response = await fetch(`/api/orders?${params}`, { + credentials: 'include' + }); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data : []; + }, + enabled: !!user, + }); + + // Fetch today's deliveries + const { data: deliveries = [] } = useQuery({ + queryKey: ["/api/deliveries/today", selectedStoreId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); + params.append('date', format(new Date(), 'yyyy-MM-dd')); + + const response = await fetch(`/api/deliveries?${params}`, { + credentials: 'include' + }); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data : []; + }, + enabled: !!user, + }); + + const pendingTasks = tasks.filter((t: any) => t.status !== 'completed').length; + const urgentTasks = tasks.filter((t: any) => t.priority === 'high' && t.status !== 'completed').length; + const todayDate = format(new Date(), "EEEE d MMMM", { locale: fr }); + + return ( + +
+ {/* Welcome */} +
+

Bonjour,

+

+ {user?.firstName || user?.username || 'Utilisateur'} +

+

{todayDate}

+
+ + {/* Stats Grid */} +
+ + + + +
+ + {/* Quick Actions */} +
+

Accès rapide

+
+ {[ + { icon: Package, label: "Commande", path: "/orders" }, + { icon: Truck, label: "Livraison", path: "/deliveries" }, + { icon: CheckSquare, label: "Tâche", path: "/tasks" }, + { icon: Calendar, label: "Agenda", path: "/calendar" }, + ].map((item) => { + const Icon = item.icon; + return ( + + + {item.label} + + ); + })} +
+
+ + {/* Recent Tasks */} + {pendingTasks > 0 && ( +
+

Tâches à faire

+
+ {tasks + .filter((t: any) => t.status !== 'completed') + .slice(0, 3) + .map((task: any) => ( +
+

+ {task.title} +

+ {task.dueDate && ( +

+ + {format(new Date(task.dueDate), 'dd/MM')} +

+ )} +
+ )) + } +
+
+ )} +
+
+ ); +} diff --git a/client/src/pages/mobile/DeliveriesPage.tsx b/client/src/pages/mobile/DeliveriesPage.tsx new file mode 100644 index 0000000..7939091 --- /dev/null +++ b/client/src/pages/mobile/DeliveriesPage.tsx @@ -0,0 +1,196 @@ +/** + * DeliveriesPage Mobile - Liste des livraisons simplifiée pour mobile + */ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { useStore } from "@/contexts/StoreContext"; +import MobileLayout from "./MobileLayout"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Truck, + Search, + Calendar, + Building, + ChevronRight, + Package, + CheckCircle, + Clock +} from "lucide-react"; +import { format, isToday, isTomorrow, isPast } from "date-fns"; +import { fr } from "date-fns/locale"; + +// Type pour les livraisons +type Delivery = { + id: number; + deliveryNumber?: string; + supplier?: { id: number; name: string; }; + expectedDate: string; + status: string; + order?: { id: number; orderNumber: string; }; + group?: { id: number; name: string; color?: string; }; +}; + +export default function MobileDeliveriesPage() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState<'all' | 'pending' | 'received'>('all'); + + // Fetch deliveries + const { data: deliveries = [], isLoading } = useQuery({ + queryKey: ["/api/deliveries", selectedStoreId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); + + const response = await fetch(`/api/deliveries?${params}`, { + credentials: 'include' + }); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data : []; + }, + enabled: !!user, + }); + + // Filter deliveries + const filteredDeliveries = deliveries.filter((delivery: Delivery) => { + // Status filter + if (statusFilter === 'pending' && delivery.status === 'received') return false; + if (statusFilter === 'received' && delivery.status !== 'received') return false; + + // Search filter + if (searchTerm) { + const search = searchTerm.toLowerCase(); + const matchNumber = delivery.deliveryNumber?.toLowerCase().includes(search); + const matchSupplier = delivery.supplier?.name?.toLowerCase().includes(search); + if (!matchNumber && !matchSupplier) return false; + } + + return true; + }); + + const getDateBadge = (dateStr: string) => { + const date = new Date(dateStr); + if (isToday(date)) { + return Aujourd'hui; + } + if (isTomorrow(date)) { + return Demain; + } + if (isPast(date)) { + return En retard; + } + return null; + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'received': + return ; + case 'pending': + return ; + default: + return ; + } + }; + + if (isLoading) { + return ( + +
+
+
+ + ); + } + + return ( + +
+ {/* Header */} +
+
+ +

Livraisons

+
+ {filteredDeliveries.length} +
+ + {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="pl-10 h-10" + /> +
+ + {/* Filter tabs */} +
+ {(['all', 'pending', 'received'] as const).map((status) => ( + + ))} +
+ + {/* Deliveries list */} +
+ {filteredDeliveries.length === 0 ? ( +
+ +

Aucune livraison trouvée

+
+ ) : ( + filteredDeliveries.map((delivery: Delivery) => ( + + +
+
+
+ {getStatusIcon(delivery.status)} + + #{delivery.deliveryNumber || delivery.id} + + {getDateBadge(delivery.expectedDate)} +
+ + {delivery.supplier && ( +

+ + {delivery.supplier.name} +

+ )} + +

+ + {format(new Date(delivery.expectedDate), 'dd/MM/yyyy', { locale: fr })} +

+
+ + +
+
+
+ )) + )} +
+
+
+ ); +} diff --git a/client/src/pages/mobile/MobileBottomNav.tsx b/client/src/pages/mobile/MobileBottomNav.tsx new file mode 100644 index 0000000..7ba2109 --- /dev/null +++ b/client/src/pages/mobile/MobileBottomNav.tsx @@ -0,0 +1,137 @@ +/** + * MobileBottomNav - Navigation bottom fixe pour l'application mobile + * 5 boutons: Accueil, Commandes, Livraisons, Tâches, Plus + */ +import { Link, useLocation } from "wouter"; +import { useState } from "react"; +import { + Home, + Package, + Truck, + CheckSquare, + MoreHorizontal, + Calendar, + Megaphone, + ShoppingCart, + Clock, + Wrench, + Receipt +} from "lucide-react"; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; + +interface NavItem { + path: string; + label: string; + icon: React.ComponentType<{ className?: string }>; +} + +const primaryItems: NavItem[] = [ + { path: "/dashboard", label: "Accueil", icon: Home }, + { path: "/orders", label: "Commandes", icon: Package }, + { path: "/deliveries", label: "Livraisons", icon: Truck }, + { path: "/tasks", label: "Tâches", icon: CheckSquare }, +]; + +const moreItems: NavItem[] = [ + { path: "/calendar", label: "Calendrier", icon: Calendar }, + { path: "/publicities", label: "Publicités", icon: Megaphone }, + { path: "/customer-orders", label: "Cmd Client", icon: ShoppingCart }, + { path: "/dlc", label: "DLC", icon: Clock }, + { path: "/sav", label: "SAV", icon: Wrench }, + { path: "/avoirs", label: "Avoirs", icon: Receipt }, +]; + +export default function MobileBottomNav() { + const [location] = useLocation(); + const { user } = useAuthUnified(); + const [moreOpen, setMoreOpen] = useState(false); + + const isActive = (path: string) => { + if (path === "/dashboard" && (location === "/" || location === "/dashboard")) return true; + return location.startsWith(path); + }; + + // Check if any "more" item is active + const isMoreActive = moreItems.some(item => isActive(item.path)); + + return ( + <> + {/* Navigation bottom fixe */} + + + {/* Sheet "Plus" */} + + + + Autres pages + + +
+ {moreItems.map((item) => { + const Icon = item.icon; + const active = isActive(item.path); + + return ( + setMoreOpen(false)} + > +
+ + + {item.label} + +
+ + ); + })} +
+
+
+ + ); +} diff --git a/client/src/pages/mobile/MobileLayout.tsx b/client/src/pages/mobile/MobileLayout.tsx new file mode 100644 index 0000000..2f5921f --- /dev/null +++ b/client/src/pages/mobile/MobileLayout.tsx @@ -0,0 +1,180 @@ +/** + * MobileLayout - Layout dédié pour l'application mobile + * Affiche le magasin sélectionné de manière visible et une navigation bottom + */ +import { ReactNode, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Menu, X, Store, ChevronDown, LogOut } from "lucide-react"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { useStore } from "@/contexts/StoreContext"; +import { Button } from "@/components/ui/button"; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import MobileBottomNav from "./MobileBottomNav"; +import type { Group } from "@shared/schema"; + +interface MobileLayoutProps { + children: ReactNode; + title?: string; + showBackButton?: boolean; + onBack?: () => void; +} + +export default function MobileLayout({ children, title }: MobileLayoutProps) { + const { user } = useAuthUnified(); + const { selectedStoreId, setSelectedStoreId, stores } = useStore(); + const [menuOpen, setMenuOpen] = useState(false); + + // Get selected store name + const selectedStore = stores?.find((s: Group) => s.id === selectedStoreId); + const storeName = selectedStore?.name || "Tous les magasins"; + + const handleLogout = async () => { + try { + await fetch('/api/logout', { method: 'POST', credentials: 'include' }); + } catch (error) { + console.error('Logout error:', error); + } finally { + window.location.href = "/auth"; + } + }; + + const handleStoreChange = (value: string) => { + const newStoreId = value === "all" ? null : parseInt(value); + if (newStoreId) { + localStorage.setItem('selectedStoreId', newStoreId.toString()); + } else { + localStorage.removeItem('selectedStoreId'); + } + setSelectedStoreId(newStoreId); + }; + + return ( +
+ {/* Header fixe */} +
+
+ {/* Menu hamburger */} + + + {/* Logo */} +
+ + LogiFlow +
+ + {/* Magasin sélectionné - bien visible */} +
setMenuOpen(true)} + > + + {storeName} + + +
+
+ + {/* Titre de page optionnel */} + {title && ( +
+

{title}

+
+ )} +
+ + {/* Contenu principal */} +
+ {children} +
+ + {/* Navigation bottom */} + + + {/* Menu latéral (Sheet) */} + + + + + + LogiFlow + + + +
+ {/* Utilisateur */} + {user && ( +
+

+ {user.firstName} {user.lastName} +

+

{user.role}

+
+ )} + + {/* Sélecteur de magasin */} +
+ + +
+ + {/* Déconnexion */} + +
+
+
+
+ ); +} diff --git a/client/src/pages/mobile/OrdersPage.tsx b/client/src/pages/mobile/OrdersPage.tsx new file mode 100644 index 0000000..8dd130d --- /dev/null +++ b/client/src/pages/mobile/OrdersPage.tsx @@ -0,0 +1,181 @@ +/** + * OrdersPage Mobile - Liste des commandes simplifiée pour mobile + */ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { useStore } from "@/contexts/StoreContext"; +import MobileLayout from "./MobileLayout"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Package, + Search, + Plus, + Calendar, + Building, + ChevronRight +} from "lucide-react"; +import { format } from "date-fns"; +import { fr } from "date-fns/locale"; + +// Type pour les commandes +type Order = { + id: number; + orderNumber?: string; + supplier?: { id: number; name: string; }; + orderDate: string; + status: string; + totalAmount?: number; + group?: { id: number; name: string; color?: string; }; +}; + +export default function MobileOrdersPage() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState<'all' | 'pending' | 'delivered'>('all'); + + // Fetch orders + const { data: orders = [], isLoading } = useQuery({ + queryKey: ["/api/orders", selectedStoreId], + queryFn: async () => { + const params = new URLSearchParams(); + if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); + + const response = await fetch(`/api/orders?${params}`, { + credentials: 'include' + }); + if (!response.ok) return []; + const data = await response.json(); + return Array.isArray(data) ? data : []; + }, + enabled: !!user, + }); + + // Filter orders + const filteredOrders = orders.filter((order: Order) => { + // Status filter + if (statusFilter === 'pending' && order.status === 'delivered') return false; + if (statusFilter === 'delivered' && order.status !== 'delivered') return false; + + // Search filter + if (searchTerm) { + const search = searchTerm.toLowerCase(); + const matchNumber = order.orderNumber?.toLowerCase().includes(search); + const matchSupplier = order.supplier?.name?.toLowerCase().includes(search); + if (!matchNumber && !matchSupplier) return false; + } + + return true; + }); + + const getStatusBadge = (status: string) => { + switch (status) { + case 'delivered': + return Livrée; + case 'pending': + return En attente; + case 'cancelled': + return Annulée; + default: + return {status}; + } + }; + + if (isLoading) { + return ( + +
+
+
+ + ); + } + + return ( + +
+ {/* Header */} +
+
+ +

Commandes

+
+ {filteredOrders.length} +
+ + {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="pl-10 h-10" + /> +
+ + {/* Filter tabs */} +
+ {(['all', 'pending', 'delivered'] as const).map((status) => ( + + ))} +
+ + {/* Orders list */} +
+ {filteredOrders.length === 0 ? ( +
+ +

Aucune commande trouvée

+
+ ) : ( + filteredOrders.map((order: Order) => ( + + +
+
+
+ + #{order.orderNumber || order.id} + + {getStatusBadge(order.status)} +
+ + {order.supplier && ( +

+ + {order.supplier.name} +

+ )} + +

+ + {format(new Date(order.orderDate), 'dd/MM/yyyy', { locale: fr })} +

+
+ + +
+
+
+ )) + )} +
+
+
+ ); +} diff --git a/client/src/pages/mobile/TasksPage.tsx b/client/src/pages/mobile/TasksPage.tsx index aedf25c..708e0c5 100644 --- a/client/src/pages/mobile/TasksPage.tsx +++ b/client/src/pages/mobile/TasksPage.tsx @@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; +import MobileLayout from "./MobileLayout"; import { ListTodo, Plus,