diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx index 06b4553..d8cd84c 100644 --- a/client/src/components/Layout.tsx +++ b/client/src/components/Layout.tsx @@ -12,6 +12,7 @@ import Sidebar from "./Sidebar"; import ErrorBoundary from "./ErrorBoundary"; import WeatherWidget from "./WeatherWidget"; import DateWidget from "./DateWidget"; +import { getNavItemForPath } from "@/lib/navigation"; import type { Group } from "@shared/schema"; @@ -63,6 +64,12 @@ export default function Layout({ children }: LayoutProps) { const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + // Titre de l'onglet du navigateur tiré du menu : « Livraisons — LogiFlow » + useEffect(() => { + const navItem = getNavItemForPath(location); + document.title = navItem ? `${navItem.label} — LogiFlow` : "LogiFlow"; + }, [location]); + const { data: stores = EMPTY_STORES } = useQuery({ queryKey: ['/api/groups'], enabled: !!user, diff --git a/client/src/components/PhoneBottomNav.tsx b/client/src/components/PhoneBottomNav.tsx deleted file mode 100644 index ccb4cd8..0000000 --- a/client/src/components/PhoneBottomNav.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { Home, Package, Truck, ClipboardList, MoreHorizontal } from "lucide-react" -import { Link, useLocation } from "wouter" -import { useState } from "react" -import { Button } from "@/components/ui/button" -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet" -import type { User } from "@shared/schema" - -interface NavItem { - path: string - label: string - icon: any - primary?: boolean -} - -interface MoreMenuItem { - path: string - label: string - icon: any - roles?: string[] -} - -const primaryNavItems: NavItem[] = [ - { path: "/", label: "Accueil", icon: Home, primary: true }, - { path: "/orders", label: "Commandes", icon: Package, primary: true }, - { path: "/deliveries", label: "Livraisons", icon: Truck, primary: true }, - { path: "/tasks", label: "Tâches", icon: ClipboardList, primary: true }, -] - -const moreMenuItems: MoreMenuItem[] = [ - { path: "/bl-reconciliation", label: "Rapprochement", icon: ClipboardList, roles: ["admin", "directeur", "manager"] }, - { path: "/publicities", label: "Publicités", icon: Package, roles: ["admin", "directeur", "manager"] }, - { path: "/customer-orders", label: "Commandes Clients", icon: Package, roles: ["admin", "directeur", "manager"] }, - { path: "/avoirs", label: "Avoirs", icon: Package, roles: ["admin", "directeur", "manager"] }, - { path: "/dlc", label: "DLC", icon: Package, roles: ["admin", "directeur", "manager"] }, - { path: "/sav", label: "SAV", icon: ClipboardList, roles: ["admin", "directeur", "manager"] }, - { path: "/calendar", label: "Calendrier", icon: ClipboardList }, -] - -interface PhoneBottomNavProps { - user: User - location: string -} - -export default function PhoneBottomNav({ user, location }: PhoneBottomNavProps) { - const [moreSheetOpen, setMoreSheetOpen] = useState(false) - - const isActive = (path: string) => { - if (path === "/") return location === "/" - return location.startsWith(path) - } - - const hasPermission = (roles?: string[]) => { - if (!roles || !user) return true - return roles.includes(user.role) - } - - const filteredMoreItems = moreMenuItems.filter(item => hasPermission(item.roles)) - - return ( -
-
- {/* Navigation principale */} - {primaryNavItems.map((item) => { - const Icon = item.icon - const active = isActive(item.path) - - return ( - -
- - - {item.label} - -
- - ) - })} - - {/* Menu "Plus" */} - - -
- - Plus -
-
- - - Navigation - - Accédez aux autres sections de l'application - - -
- {filteredMoreItems.map((item) => { - const Icon = item.icon - const active = isActive(item.path) - - return ( - setMoreSheetOpen(false)} - > -
- - - {item.label} - -
- - ) - })} -
-
-
-
-
- ) -} \ No newline at end of file diff --git a/client/src/components/RouterProduction.tsx b/client/src/components/RouterProduction.tsx index de57377..a80b05f 100644 --- a/client/src/components/RouterProduction.tsx +++ b/client/src/components/RouterProduction.tsx @@ -1,4 +1,4 @@ -import { lazy, useEffect } from "react"; +import { lazy, Suspense, useEffect, type ComponentType } from "react"; import { Switch, Route, Redirect, useLocation } from "wouter"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useScreenSize } from "@/hooks/use-screen-size"; @@ -10,6 +10,7 @@ import Layout from "@/components/Layout"; // Mobile : coque et page d'accueil chargées immédiatement import MobileApp from "@/pages/mobile/MobileApp"; import MobileDashboardPage from "@/pages/mobile/DashboardPage"; +import MobileLayout from "@/pages/mobile/MobileLayout"; // Les autres pages sont chargées à la demande (un fichier JS par page) : l'écran // de connexion et le tableau de bord n'attendent plus le code de toutes les pages @@ -44,6 +45,36 @@ const MobileDlcPage = lazy(() => import("@/pages/mobile/DlcPage")); const MobileSavPage = lazy(() => import("@/pages/mobile/SavPage")); const MobileAvoirsPage = lazy(() => import("@/pages/mobile/AvoirsPage")); +// Pages sans version mobile : affichées dans le cadre téléphone (en-tête, barre +// du bas et menu) pour ne jamais laisser une page sans navigation +function withMobileFrame(Page: ComponentType) { + return function MobileFramedPage() { + return ( + +
+ +
+
+ }> + + + +
+ ); + }; +} + +const MobileFramedContacts = withMobileFrame(Contacts); +const MobileFramedSuppliers = withMobileFrame(Suppliers); +const MobileFramedGroups = withMobileFrame(Groups); +const MobileFramedUsers = withMobileFrame(Users); +const MobileFramedBLReconciliation = withMobileFrame(BLReconciliation); +const MobileFramedUtilities = withMobileFrame(Utilities); +const MobileFramedAnalytics = withMobileFrame(Analytics); +const MobileFramedPaymentSchedule = withMobileFrame(PaymentSchedulePage); +const MobileFramedNotFound = withMobileFrame(NotFound); + // Helper component to handle redirection const RedirectToAuth = () => { const [, setLocation] = useLocation(); @@ -92,20 +123,20 @@ function RouterProduction() { - {/* Pages sans version mobile - utiliser version desktop pour l'instant */} - - - - - + {/* Pages sans version mobile : version PC dans le cadre téléphone */} + + + + + - + - - + + {/* Redirection /auth vers dashboard (navigation interne, sans rechargement) */} @@ -113,7 +144,7 @@ function RouterProduction() { - + ); diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 66f5e0e..f54c620 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -4,191 +4,88 @@ import { Button } from "@/components/ui/button"; import { useStore } from "@/contexts/StoreContext"; import { useIsMobile } from "@/hooks/use-mobile"; import { useScreenSize } from "@/hooks/use-screen-size"; -import { useState } from "react"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Label } from "@/components/ui/label"; -import { Input } from "@/components/ui/input"; -import { useToast } from "@/hooks/use-toast"; -import { apiRequest } from "@/lib/queryClient"; -import { - Store, - Calendar, - BarChart3, - Package, - Truck, - Building, - Users, - UserCog, - LogOut, - FileText, - Megaphone, - Database, - ShoppingCart, - Clock, - ListTodo, - ChevronLeft, - ChevronRight, - X, - Settings, - Wrench, - FileUp, - Send, - Receipt, - TrendingUp, - CreditCard, - BookUser, -} from "lucide-react"; +import { getNavSections, getRoleLabel, isNavItemActive, type NavItem } from "@/lib/navigation"; +import { Boxes, LogOut, PanelLeftClose, PanelLeftOpen, X } from "lucide-react"; + +// En-tête du menu : logo + bouton de repli (PC/tablette) ou de fermeture (mobile) +function SidebarHeader({ collapsed, isMobile, onToggle, onClose }: { + collapsed: boolean; + isMobile: boolean; + onToggle: () => void; + onClose: () => void; +}) { + const showLabel = !collapsed || isMobile; + + return ( +
+ {showLabel && ( + + + + LogiFlow + + )} + {isMobile ? ( + + ) : ( + + )} +
+ ); +} + +function SidebarLink({ item, active, compact, onNavigate }: { + item: NavItem; + active: boolean; + compact: boolean; + onNavigate: () => void; +}) { + const Icon = item.icon; + + return ( + + {active &&