From 75be8e75b36d2d027be7cd09c914375cd5eaec9e Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Wed, 10 Dec 2025 15:41:47 +0100 Subject: [PATCH] feat: add mobile pages for customer orders, publicities, and calendar, and introduce a production router component. --- client/src/components/RouterProduction.tsx | 6 +- client/src/pages/mobile/CalendarPage.tsx | 39 +--- .../src/pages/mobile/CustomerOrdersPage.tsx | 203 ++++++++++++++++++ client/src/pages/mobile/PublicitiesPage.tsx | 172 +++++++++++++++ 4 files changed, 383 insertions(+), 37 deletions(-) create mode 100644 client/src/pages/mobile/CustomerOrdersPage.tsx create mode 100644 client/src/pages/mobile/PublicitiesPage.tsx diff --git a/client/src/components/RouterProduction.tsx b/client/src/components/RouterProduction.tsx index 225106b..7acb02c 100644 --- a/client/src/components/RouterProduction.tsx +++ b/client/src/components/RouterProduction.tsx @@ -36,6 +36,8 @@ import MobileOrdersPage from "@/pages/mobile/OrdersPage"; import MobileDeliveriesPage from "@/pages/mobile/DeliveriesPage"; import MobileCalendarPage from "@/pages/mobile/CalendarPage"; import MobileTasksPage from "@/pages/mobile/TasksPage"; +import MobilePublicitiesPage from "@/pages/mobile/PublicitiesPage"; +import MobileCustomerOrdersPage from "@/pages/mobile/CustomerOrdersPage"; function RouterProduction() { const { isAuthenticated, isLoading, user, environment, error } = useAuthUnified(); @@ -102,8 +104,8 @@ function RouterProduction() { - - + + diff --git a/client/src/pages/mobile/CalendarPage.tsx b/client/src/pages/mobile/CalendarPage.tsx index 797f66d..8af0fb9 100644 --- a/client/src/pages/mobile/CalendarPage.tsx +++ b/client/src/pages/mobile/CalendarPage.tsx @@ -48,11 +48,8 @@ export default function MobileCalendarPage() { const [currentMonth, setCurrentMonth] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date()); - // Debug log - doit apparaître dans la console - console.log('🗓️ MobileCalendarPage RENDER', { user: !!user, selectedStoreId, currentMonth: currentMonth.toISOString() }); - // Fetch orders for current month - const { data: orders = [], isLoading: loadingOrders } = useQuery({ + const { data: orders = [] } = useQuery({ queryKey: ["/api/orders", selectedStoreId, format(currentMonth, 'yyyy-MM')], queryFn: async () => { const start = format(startOfMonth(currentMonth), 'yyyy-MM-dd'); @@ -64,24 +61,20 @@ export default function MobileCalendarPage() { }); if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); - console.log('📅 Mobile Calendar fetching orders:', { start, end, selectedStoreId }); - const response = await fetch(`/api/orders?${params}`, { credentials: 'include' }); if (!response.ok) { - console.error('❌ Orders fetch failed:', response.status); return []; } const data = await response.json(); - console.log('📦 Orders received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', data); return Array.isArray(data) ? data : []; }, enabled: !!user, }); // Fetch deliveries for current month - const { data: deliveries = [], isLoading: loadingDeliveries } = useQuery({ + const { data: deliveries = [] } = useQuery({ queryKey: ["/api/deliveries", selectedStoreId, format(currentMonth, 'yyyy-MM')], queryFn: async () => { const start = format(startOfMonth(currentMonth), 'yyyy-MM-dd'); @@ -93,17 +86,13 @@ export default function MobileCalendarPage() { }); if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); - console.log('📅 Mobile Calendar fetching deliveries:', { start, end, selectedStoreId }); - const response = await fetch(`/api/deliveries?${params}`, { credentials: 'include' }); if (!response.ok) { - console.error('❌ Deliveries fetch failed:', response.status); return []; } const data = await response.json(); - console.log('🚚 Deliveries received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', data); return Array.isArray(data) ? data : []; }, enabled: !!user, @@ -279,8 +268,8 @@ export default function MobileCalendarPage() {
{event.type === 'order' ? @@ -312,26 +301,6 @@ export default function MobileCalendarPage() { )}
)} - - {/* DEBUG SECTION - VISIBLE ON MOBILE */} -
-

DEBUG INFO:

-

Current Month: {format(currentMonth, 'yyyy-MM')}

-

Store ID: {selectedStoreId ? selectedStoreId : 'None'}

-

Orders: {loadingOrders ? 'Loading...' : orders?.length || 0}

-

Deliveries: {loadingDeliveries ? 'Loading...' : deliveries?.length || 0}

-

Total Events: {events.length}

-

User: {user ? user.username : 'No user'}

- - {events.length > 0 && ( -
-

First Event Sample:

-

Title: {events[0].title}

-

Raw Date: {String(events[0].date)}

-

Parsed: {new Date(events[0].date).toString()}

-
- )} -
); diff --git a/client/src/pages/mobile/CustomerOrdersPage.tsx b/client/src/pages/mobile/CustomerOrdersPage.tsx new file mode 100644 index 0000000..d683ae0 --- /dev/null +++ b/client/src/pages/mobile/CustomerOrdersPage.tsx @@ -0,0 +1,203 @@ +/** + * MobileCustomerOrdersPage.tsx + * Version mobile de la page Commandes Clients + */ +import { useState } from "react"; +import { useQuery, useMutation, useQueryClient } 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 { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Package, + Search, + Phone, + User, + ClipboardList, + Plus, + MoreVertical +} from "lucide-react"; +import { format } from "date-fns"; +import { fr } from "date-fns/locale"; +import { apiRequest } from "@/lib/queryClient"; +import { useToast } from "@/hooks/use-toast"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export default function MobileCustomerOrdersPage() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + const [searchTerm, setSearchTerm] = useState(""); + const { toast } = useToast(); + const queryClient = useQueryClient(); + + const { data: orders = [], isLoading } = useQuery({ + queryKey: ["/api/customer-orders", selectedStoreId], + queryFn: async () => { + if (!selectedStoreId) return []; + const response = await fetch( + `/api/customer-orders?storeId=${selectedStoreId}`, + { credentials: 'include' } + ); + if (!response.ok) throw new Error("Failed to fetch customer orders"); + return response.json(); + }, + enabled: !!selectedStoreId && !!user, + }); + + // Mutation pour changer le statut + const statusMutation = useMutation({ + mutationFn: ({ id, status }: { id: number; status: string }) => + apiRequest(`/api/customer-orders/${id}`, 'PUT', { status }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/customer-orders"] }); + toast({ title: "Statut mis à jour" }); + }, + }); + + const filteredOrders = orders.filter((order: any) => + order.customerName.toLowerCase().includes(searchTerm.toLowerCase()) || + order.productDesignation.toLowerCase().includes(searchTerm.toLowerCase()) || + (order.productReference && order.productReference.toLowerCase().includes(searchTerm.toLowerCase())) + ); + + const getStatusVariant = (status: string) => { + switch (status) { + case "Disponible": return "success"; // vert + case "Commande en Cours": return "default"; // bleu + case "Retiré": return "secondary"; // gris + case "Annulé": return "destructive"; // rouge + default: return "outline"; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "Disponible": return "bg-green-100 text-green-800 border-green-200"; + case "Commande en Cours": return "bg-blue-100 text-blue-800 border-blue-200"; + case "Retiré": return "bg-gray-100 text-gray-800 border-gray-200"; + case "Annulé": return "bg-red-100 text-red-800 border-red-200"; + default: return "bg-yellow-100 text-yellow-800 border-yellow-200"; + } + }; + + return ( + +
+ {/* Header */} +
+ +

Commandes Clients

+
+ + {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="pl-9 h-10" + /> +
+ + {/* Orders List */} +
+ {isLoading ? ( +
+
+
+ ) : filteredOrders.length === 0 ? ( +
+ +

Aucune commande trouvée

+
+ ) : ( + filteredOrders.map((order: any) => ( + + +
+
+
+ + {order.status} + + + {format(new Date(order.createdAt), "dd/MM", { locale: fr })} + +
+

+ {order.customerName} +

+

+ {order.productDesignation} +

+
+ + + + + + + statusMutation.mutate({ id: order.id, status: 'Disponible' })}> + Marquer Disponible + + statusMutation.mutate({ id: order.id, status: 'Retiré' })}> + Marquer Retiré + + statusMutation.mutate({ id: order.id, status: 'Annulé' })}> + Annuler + + + +
+ +
+ {order.customerPhone && ( + + + Appeler + + )} +
+
+ Qty: {order.quantity || 1} +
+ {order.deposit && parseFloat(order.deposit) > 0 && ( +
+ Acompte: {parseFloat(order.deposit)}€ +
+ )} +
+
+
+
+ )) + )} +
+
+ + {/* FAB */} + + + ); +} diff --git a/client/src/pages/mobile/PublicitiesPage.tsx b/client/src/pages/mobile/PublicitiesPage.tsx new file mode 100644 index 0000000..b025d91 --- /dev/null +++ b/client/src/pages/mobile/PublicitiesPage.tsx @@ -0,0 +1,172 @@ +/** + * MobilePublicitiesPage.tsx + * Version mobile de la page Publicités + */ +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 { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Megaphone, + Search, + Calendar, + User, + Plus +} from "lucide-react"; +import { format, isWithinInterval, parseISO } from "date-fns"; +import { fr } from "date-fns/locale"; + +export default function MobilePublicitiesPage() { + const { user } = useAuthUnified(); + const { selectedStoreId } = useStore(); + const [selectedYear, setSelectedYear] = useState(new Date().getFullYear()); + const [searchTerm, setSearchTerm] = useState(""); + + // Générer les années pour le select (année courante +/- 2 ans) + const currentYear = new Date().getFullYear(); + const years = Array.from({ length: 5 }, (_, i) => currentYear - 2 + i); + + const { data: publicities = [], isLoading } = useQuery({ + queryKey: ["/api/publicities", selectedStoreId, selectedYear], + queryFn: async () => { + if (!selectedStoreId) return []; + const response = await fetch( + `/api/publicities?storeId=${selectedStoreId}&year=${selectedYear}`, + { credentials: 'include' } + ); + if (!response.ok) throw new Error("Failed to fetch publicities"); + return response.json(); + }, + enabled: !!selectedStoreId && !!user, + }); + + const filteredPublicities = publicities.filter((pub: any) => + pub.designation.toLowerCase().includes(searchTerm.toLowerCase()) || + pub.pubNumber.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const getStatusBadge = (startDate: string, endDate: string) => { + const start = parseISO(startDate); + const end = parseISO(endDate); + const now = new Date(); + + if (isWithinInterval(now, { start, end })) { + return En cours; + } else if (now > end) { + return Terminée; + } else { + return À venir; + } + }; + + return ( + +
+ {/* Header & Filters */} +
+
+ +

Publicités

+
+
+ +
+
+ + setSearchTerm(e.target.value)} + className="pl-9 h-10 w-full" + /> +
+ +
+ + {/* List */} +
+ {isLoading ? ( +
+
+
+ ) : filteredPublicities.length === 0 ? ( +
+ +

Aucune publicité trouvée pour {selectedYear}

+
+ ) : ( + filteredPublicities.map((pub: any) => ( + + +
+
+
+ + {pub.pubNumber} + + {getStatusBadge(pub.startDate, pub.endDate)} +
+

+ {pub.designation} +

+
+
+ +
+
+ + + Du {format(parseISO(pub.startDate), "dd/MM", { locale: fr })} au {format(parseISO(pub.endDate), "dd/MM/yyyy", { locale: fr })} + +
+
+ + Créé par {pub.creator?.username || pub.createdBy} +
+
+
+
+ )) + )} +
+
+ + {/* FAB Add Button */} + + + ); +}