mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Adapt the application interface for mobile and tablet viewing
Enhance responsiveness of the application's layout and various pages (Dashboard, Deliveries, Orders) to better support mobile and tablet screen sizes by implementing conditional rendering for navigation, adjusting padding, and optimizing element display. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 76582ba3-44cb-4730-92fa-66f28725967b Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/76582ba3-44cb-4730-92fa-66f28725967b/GDasGiN
This commit is contained in:
1 parent
a7936fab8d
commit
597d3564ee
5 files changed
+244
-134
No files matched your search
@@ -2,8 +2,9 @@ import { ReactNode, useState, createContext, useContext } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LogOut, Store } from "lucide-react";
|
||||
import { LogOut, Store, Menu } from "lucide-react";
|
||||
import { useAuthUnified } from "@/hooks/useAuthUnified";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import Sidebar from "./Sidebar";
|
||||
import type { Group } from "@shared/schema";
|
||||
|
||||
@@ -13,6 +14,8 @@ interface StoreContextType {
|
||||
stores: Group[];
|
||||
sidebarCollapsed: boolean;
|
||||
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||
mobileMenuOpen: boolean;
|
||||
setMobileMenuOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const StoreContext = createContext<StoreContextType | undefined>(undefined);
|
||||
@@ -32,6 +35,8 @@ interface LayoutProps {
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const { user } = useAuthUnified();
|
||||
const queryClient = useQueryClient();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [selectedStoreId, setSelectedStoreId] = useState<number | null>(() => {
|
||||
// Restaurer le selectedStoreId depuis localStorage si disponible
|
||||
const saved = localStorage.getItem('selectedStoreId');
|
||||
@@ -41,11 +46,13 @@ export default function Layout({ children }: LayoutProps) {
|
||||
});
|
||||
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(() => {
|
||||
// Restaurer l'état de la sidebar depuis localStorage
|
||||
// Restaurer l'état de la sidebar depuis localStorage, mais forcer collapsed sur mobile
|
||||
const saved = localStorage.getItem('sidebarCollapsed');
|
||||
return saved ? JSON.parse(saved) : false;
|
||||
return isMobile ? true : (saved ? JSON.parse(saved) : false);
|
||||
});
|
||||
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
const { data: stores = [] } = useQuery<Group[]>({
|
||||
queryKey: ['/api/groups'],
|
||||
enabled: !!user,
|
||||
@@ -56,20 +63,40 @@ export default function Layout({ children }: LayoutProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={{ selectedStoreId, setSelectedStoreId, stores, sidebarCollapsed, setSidebarCollapsed }}>
|
||||
<StoreContext.Provider value={{ selectedStoreId, setSelectedStoreId, stores, sidebarCollapsed, setSidebarCollapsed, mobileMenuOpen, setMobileMenuOpen }}>
|
||||
<div className="flex h-screen overflow-hidden bg-gray-50">
|
||||
{/* Mobile overlay */}
|
||||
{isMobile && mobileMenuOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 z-40"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Sidebar />
|
||||
|
||||
<main className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Header with store selector for admin */}
|
||||
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6">
|
||||
{/* Header with mobile menu and store selector */}
|
||||
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 sm:px-6">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Mobile menu button */}
|
||||
{isMobile && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</Button>
|
||||
)}
|
||||
<h1 className="text-lg font-medium text-gray-800">LogiFlow</h1>
|
||||
</div>
|
||||
|
||||
{/* Store selector for admin - moved to top right */}
|
||||
{/* Store selector for admin - responsive */}
|
||||
{user?.role === 'admin' && stores.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Store className="h-4 w-4 text-gray-500" />
|
||||
<Store className="h-4 w-4 text-gray-500 hidden sm:block" />
|
||||
<Select
|
||||
value={selectedStoreId?.toString() || "all"}
|
||||
onValueChange={(value) => {
|
||||
@@ -96,8 +123,8 @@ export default function Layout({ children }: LayoutProps) {
|
||||
setSelectedStoreId(newStoreId);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-64 border border-gray-300">
|
||||
<SelectValue placeholder="Sélectionner un magasin" />
|
||||
<SelectTrigger className="w-32 sm:w-64 border border-gray-300">
|
||||
<SelectValue placeholder="Magasin" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Link, useLocation } from "wouter";
|
||||
import { useAuthSimple } from "@/hooks/useAuthSimple";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useStore } from "./Layout";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import {
|
||||
Store,
|
||||
Calendar,
|
||||
@@ -19,13 +20,15 @@ import {
|
||||
Clock,
|
||||
ListTodo,
|
||||
ChevronLeft,
|
||||
ChevronRight
|
||||
ChevronRight,
|
||||
X
|
||||
} from "lucide-react";
|
||||
|
||||
export default function Sidebar() {
|
||||
const { user, isLoading, error } = useAuthSimple();
|
||||
const [location] = useLocation();
|
||||
const { sidebarCollapsed, setSidebarCollapsed } = useStore();
|
||||
const { sidebarCollapsed, setSidebarCollapsed, mobileMenuOpen, setMobileMenuOpen } = useStore();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// Debug logging pour production
|
||||
console.log('Sidebar - User:', user);
|
||||
@@ -168,34 +171,61 @@ export default function Sidebar() {
|
||||
|
||||
|
||||
|
||||
// Classes responsives pour la sidebar
|
||||
const sidebarClasses = isMobile
|
||||
? `fixed inset-y-0 left-0 z-50 w-64 bg-white border-r border-gray-200 shadow-lg transform transition-transform duration-300 ease-in-out ${
|
||||
mobileMenuOpen ? 'translate-x-0' : '-translate-x-full'
|
||||
}`
|
||||
: `${sidebarCollapsed ? 'w-16' : 'w-64'} bg-white border-r border-gray-200 flex flex-col shadow-lg transition-all duration-300 ease-in-out`;
|
||||
|
||||
// Si l'utilisateur n'est pas encore chargé, afficher un état de chargement
|
||||
if (isLoading) {
|
||||
return (
|
||||
<aside className={`${sidebarCollapsed ? 'w-16' : 'w-64'} bg-white border-r border-gray-200 flex flex-col shadow-lg transition-all duration-300 ease-in-out`}>
|
||||
<aside className={sidebarClasses}>
|
||||
<div className="h-16 flex items-center justify-between border-b border-gray-200 bg-white px-3">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
{/* Mobile close button */}
|
||||
{isMobile ? (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
</div>
|
||||
)}
|
||||
{sidebarCollapsed && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{sidebarCollapsed && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
@@ -207,31 +237,51 @@ export default function Sidebar() {
|
||||
// Si l'utilisateur n'est pas authentifié, afficher seulement le logo
|
||||
if (!user) {
|
||||
return (
|
||||
<aside className={`${sidebarCollapsed ? 'w-16' : 'w-64'} bg-white border-r border-gray-200 flex flex-col shadow-lg transition-all duration-300 ease-in-out`}>
|
||||
<aside className={sidebarClasses}>
|
||||
<div className="h-16 flex items-center justify-between border-b border-gray-200 bg-white px-3">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
{/* Mobile close button */}
|
||||
{isMobile ? (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
</div>
|
||||
)}
|
||||
{sidebarCollapsed && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{sidebarCollapsed && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center text-gray-500">
|
||||
@@ -242,33 +292,59 @@ export default function Sidebar() {
|
||||
);
|
||||
}
|
||||
|
||||
const handleMenuClick = () => {
|
||||
if (isMobile) {
|
||||
setMobileMenuOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className={`${sidebarCollapsed ? 'w-16' : 'w-64'} bg-white border-r border-gray-200 flex flex-col shadow-lg transition-all duration-300 ease-in-out`}>
|
||||
<aside className={sidebarClasses}>
|
||||
{/* Logo et bouton de collapse */}
|
||||
<div className="h-16 flex items-center justify-between border-b border-gray-200 bg-white px-3">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
{/* Mobile close button */}
|
||||
{isMobile ? (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!sidebarCollapsed && (
|
||||
<div className="flex items-center space-x-3">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<span className="text-lg font-semibold text-gray-900">LogiFlow</span>
|
||||
</div>
|
||||
)}
|
||||
{sidebarCollapsed && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{sidebarCollapsed && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100"
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
@@ -290,16 +366,16 @@ export default function Sidebar() {
|
||||
return (
|
||||
<Link key={item.path} href={item.path}>
|
||||
<div
|
||||
className={`flex items-center ${sidebarCollapsed ? 'px-3 py-3 justify-center' : 'px-3 py-2'} text-sm font-medium transition-colors hover:bg-gray-100 ${
|
||||
className={`flex items-center ${sidebarCollapsed && !isMobile ? 'px-3 py-3 justify-center' : 'px-3 py-2'} text-sm font-medium transition-colors hover:bg-gray-100 ${
|
||||
active
|
||||
? 'bg-gray-100 text-gray-900 border-r-2 border-gray-700'
|
||||
: 'text-gray-700'
|
||||
}`}
|
||||
title={sidebarCollapsed ? item.label : undefined}
|
||||
onClick={() => console.log(`Navigating to: ${item.path}`)}
|
||||
title={sidebarCollapsed && !isMobile ? item.label : undefined}
|
||||
onClick={handleMenuClick}
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${sidebarCollapsed ? '' : 'mr-3'}`} />
|
||||
{!sidebarCollapsed && item.label}
|
||||
<Icon className={`h-4 w-4 ${sidebarCollapsed && !isMobile ? '' : 'mr-3'}`} />
|
||||
{(!sidebarCollapsed || isMobile) && item.label}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -137,6 +137,7 @@ export default function Dashboard() {
|
||||
const averageDeliveryTime = Math.round(stats?.averageDeliveryTime || 0);
|
||||
const deliveredThisMonth = Array.isArray(allDeliveries) ? allDeliveries.filter((delivery: any) => {
|
||||
const deliveryDate = safeDate(delivery.deliveredDate || delivery.createdAt);
|
||||
if (!deliveryDate) return false;
|
||||
const now = new Date();
|
||||
return deliveryDate.getMonth() === now.getMonth() &&
|
||||
deliveryDate.getFullYear() === now.getFullYear() &&
|
||||
@@ -147,6 +148,7 @@ export default function Dashboard() {
|
||||
const totalPalettes = Array.isArray(allDeliveries) ? allDeliveries.reduce((total: number, delivery: any) => {
|
||||
if (delivery.status === 'delivered' && delivery.unit === 'palettes') {
|
||||
const deliveryDate = safeDate(delivery.deliveredDate || delivery.createdAt);
|
||||
if (!deliveryDate) return total;
|
||||
const now = new Date();
|
||||
if (deliveryDate.getMonth() === now.getMonth() && deliveryDate.getFullYear() === now.getFullYear()) {
|
||||
return total + (delivery.quantity || 0);
|
||||
@@ -192,10 +194,10 @@ export default function Dashboard() {
|
||||
upcomingPublicities: Array.isArray(upcomingPublicities) ? upcomingPublicities.length : 'NOT_ARRAY',
|
||||
stats: stats,
|
||||
samples: {
|
||||
order: allOrders[0],
|
||||
delivery: allDeliveries[0],
|
||||
customerOrder: customerOrders[0],
|
||||
publicity: upcomingPublicities[0]
|
||||
order: Array.isArray(allOrders) && allOrders.length > 0 ? allOrders[0] : null,
|
||||
delivery: Array.isArray(allDeliveries) && allDeliveries.length > 0 ? allDeliveries[0] : null,
|
||||
customerOrder: Array.isArray(customerOrders) && customerOrders.length > 0 ? customerOrders[0] : null,
|
||||
publicity: Array.isArray(upcomingPublicities) && upcomingPublicities.length > 0 ? upcomingPublicities[0] : null
|
||||
}
|
||||
});
|
||||
|
||||
@@ -218,13 +220,18 @@ export default function Dashboard() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 space-y-6 p-6">
|
||||
<div className="flex-1 space-y-4 sm:space-y-6 p-4 sm:p-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-gray-900">Tableau de Bord</h2>
|
||||
<p className="text-gray-600 mt-1">Vue d'ensemble des performances et statistiques</p>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold text-gray-900">Tableau de Bord</h2>
|
||||
<p className="text-gray-600 mt-1 text-sm sm:text-base">Vue d'ensemble des performances et statistiques</p>
|
||||
</div>
|
||||
{selectedStoreId && user?.role === 'admin' && (
|
||||
<Badge variant="outline" className="text-blue-700 border-blue-300 w-fit">
|
||||
Magasin sélectionné
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Alerts */}
|
||||
@@ -258,58 +265,58 @@ export default function Dashboard() {
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6">
|
||||
<Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<CardContent className="p-6">
|
||||
<CardContent className="p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">Livraisons ce mois</p>
|
||||
<p className="text-3xl font-bold text-gray-900 mt-2">{deliveredThisMonth}</p>
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Livraisons ce mois</p>
|
||||
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1 sm:mt-2">{deliveredThisMonth}</p>
|
||||
</div>
|
||||
<div className="h-12 w-12 bg-green-100 flex items-center justify-center">
|
||||
<Truck className="h-6 w-6 text-green-600" />
|
||||
<div className="h-10 w-10 sm:h-12 sm:w-12 bg-green-100 flex items-center justify-center rounded-lg">
|
||||
<Truck className="h-5 w-5 sm:h-6 sm:w-6 text-green-600" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<CardContent className="p-6">
|
||||
<CardContent className="p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">Commandes en attente</p>
|
||||
<p className="text-3xl font-bold text-gray-900 mt-2">{pendingOrdersCount}</p>
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Commandes en attente</p>
|
||||
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1 sm:mt-2">{pendingOrdersCount}</p>
|
||||
</div>
|
||||
<div className="h-12 w-12 bg-orange-100 flex items-center justify-center">
|
||||
<Clock className="h-6 w-6 text-orange-600" />
|
||||
<div className="h-10 w-10 sm:h-12 sm:w-12 bg-orange-100 flex items-center justify-center rounded-lg">
|
||||
<Clock className="h-5 w-5 sm:h-6 sm:w-6 text-orange-600" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<CardContent className="p-6">
|
||||
<CardContent className="p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">Délai moyen (jours)</p>
|
||||
<p className="text-3xl font-bold text-gray-900 mt-2">{averageDeliveryTime}</p>
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Délai moyen (jours)</p>
|
||||
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1 sm:mt-2">{averageDeliveryTime}</p>
|
||||
</div>
|
||||
<div className="h-12 w-12 bg-blue-100 flex items-center justify-center">
|
||||
<BarChart3 className="h-6 w-6 text-blue-600" />
|
||||
<div className="h-10 w-10 sm:h-12 sm:w-12 bg-blue-100 flex items-center justify-center rounded-lg">
|
||||
<BarChart3 className="h-5 w-5 sm:h-6 sm:w-6 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<CardContent className="p-6">
|
||||
<CardContent className="p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">Total palettes</p>
|
||||
<p className="text-3xl font-bold text-gray-900 mt-2">{totalPalettes}</p>
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Total palettes</p>
|
||||
<p className="text-2xl sm:text-3xl font-bold text-gray-900 mt-1 sm:mt-2">{totalPalettes}</p>
|
||||
</div>
|
||||
<div className="h-12 w-12 bg-purple-100 flex items-center justify-center">
|
||||
<Package className="h-6 w-6 text-purple-600" />
|
||||
<div className="h-10 w-10 sm:h-12 sm:w-12 bg-purple-100 flex items-center justify-center rounded-lg">
|
||||
<Package className="h-5 w-5 sm:h-6 sm:w-6 text-purple-600" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -317,7 +324,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
|
||||
{/* Main Content Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
||||
{/* Dernières Commandes */}
|
||||
<Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<CardHeader className="pb-4 border-b border-gray-100">
|
||||
@@ -403,7 +410,7 @@ export default function Dashboard() {
|
||||
.slice(0, 3)
|
||||
.map((publicity: any) => {
|
||||
const participatingStores = publicity.participations || [];
|
||||
const isCurrentStoreParticipating = selectedStoreId && participatingStores.some((p: any) => p.groupId === parseInt(selectedStoreId));
|
||||
const isCurrentStoreParticipating = selectedStoreId && participatingStores.some((p: any) => p.groupId === parseInt(selectedStoreId.toString()));
|
||||
|
||||
return (
|
||||
<div key={publicity.id} className="p-4 bg-gray-50 hover:bg-gray-100 transition-colors border-l-3 border-purple-500 space-y-2">
|
||||
@@ -436,7 +443,7 @@ export default function Dashboard() {
|
||||
<Badge
|
||||
key={participation.groupId}
|
||||
className={`text-xs ${
|
||||
selectedStoreId && participation.groupId === parseInt(selectedStoreId)
|
||||
selectedStoreId && participation.groupId === parseInt(selectedStoreId.toString())
|
||||
? 'bg-green-100 text-green-800 border border-green-300'
|
||||
: 'bg-gray-100 text-gray-700'
|
||||
}`}
|
||||
@@ -461,7 +468,7 @@ export default function Dashboard() {
|
||||
</div>
|
||||
|
||||
{/* Section Rapprochement BL */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
|
||||
{/* Statistiques des commandes clients */}
|
||||
<Card className="bg-white border border-gray-200 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<CardHeader className="pb-4 border-b border-gray-100">
|
||||
|
||||
@@ -42,8 +42,8 @@ export default function Deliveries() {
|
||||
// Redirection pour les employés
|
||||
if (user?.role === 'employee') {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="bg-orange-50 border-l-4 border-orange-400 p-4">
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="bg-orange-50 border-l-4 border-orange-400 p-4 rounded-r-lg">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<Truck className="h-5 w-5 text-orange-400" />
|
||||
|
||||
+16
-16
@@ -40,8 +40,8 @@ export default function Orders() {
|
||||
// Redirection pour les employés
|
||||
if (user?.role === 'employee') {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="bg-orange-50 border-l-4 border-orange-400 p-4">
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="bg-orange-50 border-l-4 border-orange-400 p-4 rounded-r-lg">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<Package className="h-5 w-5 text-orange-400" />
|
||||
@@ -226,23 +226,23 @@ export default function Orders() {
|
||||
const canDelete = permissions.canDelete('orders');
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="p-4 sm:p-6 space-y-4 sm:space-y-6">
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-gray-200 p-6 shadow-sm -m-6 mb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="bg-white border-b border-gray-200 p-4 sm:p-6 shadow-sm -m-4 sm:-m-6 mb-4 sm:mb-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-gray-900 flex items-center">
|
||||
<Package className="w-6 h-6 mr-3 text-blue-600" />
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-gray-900 flex items-center">
|
||||
<Package className="w-5 h-5 sm:w-6 sm:h-6 mr-2 sm:mr-3 text-blue-600" />
|
||||
Gestion des Commandes
|
||||
</h2>
|
||||
<p className="text-gray-600 mt-1">
|
||||
<p className="text-gray-600 mt-1 text-sm sm:text-base">
|
||||
{filteredOrders.length} commande{filteredOrders.length !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
{canCreate && (
|
||||
<Button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white shadow-md"
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white shadow-md w-full sm:w-auto"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Nouvelle Commande
|
||||
@@ -253,21 +253,21 @@ export default function Orders() {
|
||||
|
||||
{/* Filters */}
|
||||
<div className="bg-gray-50 border border-gray-200 p-4 rounded-lg">
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<div className="flex-1 min-w-64">
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
<Input
|
||||
placeholder="Rechercher par fournisseur, groupe ou commentaire..."
|
||||
placeholder="Rechercher par fournisseur, groupe..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10 border border-gray-300 shadow-sm"
|
||||
className="pl-10 border border-gray-300 shadow-sm w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-48 border border-gray-300 shadow-sm">
|
||||
<SelectTrigger className="w-full sm:w-48 border border-gray-300 shadow-sm">
|
||||
<Filter className="w-4 h-4 mr-2" />
|
||||
<SelectValue placeholder="Filtrer par statut" />
|
||||
</SelectTrigger>
|
||||
@@ -307,10 +307,10 @@ export default function Orders() {
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="bg-white border border-gray-200 shadow-lg overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<table className="w-full min-w-[700px]">
|
||||
<thead className="bg-gray-50 border-b border-gray-200">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
|
||||
Reference in new issue
Block a user