mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
Adapt layout and sidebar for responsive mobile experience
Introduce a `useResponsive` hook, implement mobile-first sidebar behavior with a toggleable menu, and adjust layout for various screen sizes, along with minor type and deposit value handling in `CustomerOrderForm`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: cc8ba926-557c-4bf1-a2ea-ceabbcf38824 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/cc8ba926-557c-4bf1-a2ea-ceabbcf38824/f2TM8xQ
This commit is contained in:
1 parent
479577c4ff
commit
f1d6f27e3d
5 files changed
+241
-73
No files matched your search
@@ -99,7 +99,7 @@ export function CustomerOrderForm({
|
||||
quantity: order?.quantity || 1,
|
||||
supplierId: order?.supplierId || undefined,
|
||||
status: "En attente de Commande", // Statut fixe
|
||||
deposit: order?.deposit || 0,
|
||||
deposit: Number(order?.deposit) || 0,
|
||||
isPromotionalPrice: order?.isPromotionalPrice || false,
|
||||
customerNotified: order?.customerNotified || false,
|
||||
groupId: getDefaultGroupId(), // ✅ PRIORITÉ CORRECTE APPLIQUÉE
|
||||
@@ -125,7 +125,7 @@ export function CustomerOrderForm({
|
||||
selectedStoreId,
|
||||
userHasGroups: !!user?.userGroups,
|
||||
userGroupsLength: user?.userGroups?.length,
|
||||
userGroups: user?.userGroups?.map(ug => ({
|
||||
userGroups: user?.userGroups?.map((ug: any) => ({
|
||||
groupId: ug.groupId || ug.group?.id,
|
||||
groupName: ug.group?.name,
|
||||
fullGroupData: ug
|
||||
@@ -156,7 +156,7 @@ export function CustomerOrderForm({
|
||||
contactNumber: data.contactNumber || '',
|
||||
productName: data.productName || '',
|
||||
quantity: data.quantity,
|
||||
groupId: typeof groupId === 'number' ? groupId : parseInt(groupId.toString()),
|
||||
groupId: typeof groupId === 'number' ? groupId : parseInt(groupId?.toString() || '1'),
|
||||
isPickup: false,
|
||||
notes: data.notes,
|
||||
deposit: data.deposit || 0,
|
||||
@@ -173,7 +173,7 @@ export function CustomerOrderForm({
|
||||
// Get available groups for the user
|
||||
const availableGroups = user?.role === 'admin'
|
||||
? groups
|
||||
: user?.userGroups?.map(ug => ug.group) || [];
|
||||
: user?.userGroups?.map((ug: any) => ug.group) || [];
|
||||
|
||||
// Auto-select group for users respecting admin store selection
|
||||
const currentGroupId = form.getValues('groupId');
|
||||
|
||||
@@ -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, X } from "lucide-react";
|
||||
import { useAuthUnified } from "@/hooks/useAuthUnified";
|
||||
import { useResponsive } from "@/hooks/useResponsive";
|
||||
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, isTablet, isDesktop } = useResponsive();
|
||||
|
||||
const [selectedStoreId, setSelectedStoreId] = useState<number | null>(() => {
|
||||
// Restaurer le selectedStoreId depuis localStorage si disponible
|
||||
const saved = localStorage.getItem('selectedStoreId');
|
||||
@@ -43,9 +48,12 @@ export default function Layout({ children }: LayoutProps) {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(() => {
|
||||
// Restaurer l'état de la sidebar depuis localStorage
|
||||
const saved = localStorage.getItem('sidebarCollapsed');
|
||||
return saved ? JSON.parse(saved) : false;
|
||||
// Sur mobile, toujours collapsé par défaut
|
||||
return saved ? JSON.parse(saved) : isMobile;
|
||||
});
|
||||
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState<boolean>(false);
|
||||
|
||||
const { data: stores = [] } = useQuery<Group[]>({
|
||||
queryKey: ['/api/groups'],
|
||||
enabled: !!user,
|
||||
@@ -56,20 +64,56 @@ export default function Layout({ children }: LayoutProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={{ selectedStoreId, setSelectedStoreId, stores, sidebarCollapsed, setSidebarCollapsed }}>
|
||||
<div className="flex h-screen overflow-hidden bg-gray-50">
|
||||
<Sidebar />
|
||||
<main className="flex-1 flex flex-col overflow-hidden">
|
||||
<StoreContext.Provider value={{
|
||||
selectedStoreId,
|
||||
setSelectedStoreId,
|
||||
stores,
|
||||
sidebarCollapsed,
|
||||
setSidebarCollapsed,
|
||||
mobileMenuOpen,
|
||||
setMobileMenuOpen
|
||||
}}>
|
||||
<div className="flex h-screen overflow-hidden bg-gray-50 dark:bg-gray-900">
|
||||
{/* Mobile Sidebar Overlay */}
|
||||
{isMobile && mobileMenuOpen && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 z-40"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
<div className="fixed inset-y-0 left-0 z-50 w-64 bg-white dark:bg-gray-800">
|
||||
<Sidebar />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Desktop Sidebar */}
|
||||
{!isMobile && <Sidebar />}
|
||||
|
||||
<main className={`flex-1 flex flex-col overflow-hidden ${
|
||||
isMobile ? 'ml-0' : sidebarCollapsed ? 'ml-16' : 'ml-64'
|
||||
}`}>
|
||||
{/* 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 className="h-16 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between px-4 sm:px-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<h1 className="text-lg font-medium text-gray-800">LogiFlow</h1>
|
||||
{/* Mobile menu button */}
|
||||
{isMobile && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
{mobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
||||
</Button>
|
||||
)}
|
||||
<h1 className="text-lg font-medium text-gray-800 dark:text-gray-200">LogiFlow</h1>
|
||||
</div>
|
||||
|
||||
{/* Store selector for admin - moved to top right */}
|
||||
{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 dark:text-gray-400" />
|
||||
<Select
|
||||
value={selectedStoreId?.toString() || "all"}
|
||||
onValueChange={(value) => {
|
||||
@@ -123,10 +167,21 @@ export default function Layout({ children }: LayoutProps) {
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="flex-1 overflow-auto bg-gray-50 p-6">
|
||||
{children}
|
||||
{/* Main content area */}
|
||||
<div className="flex-1 overflow-auto bg-gray-50 dark:bg-gray-900 p-4 sm:p-6">
|
||||
<div className="container-responsive">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Mobile menu close overlay */}
|
||||
{isMobile && mobileMenuOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 z-30"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</StoreContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { useAuthSimple } from "@/hooks/useAuthSimple";
|
||||
import { useResponsive } from "@/hooks/useResponsive";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useStore } from "./Layout";
|
||||
import {
|
||||
@@ -25,7 +26,8 @@ import {
|
||||
export default function Sidebar() {
|
||||
const { user, isLoading, error } = useAuthSimple();
|
||||
const [location] = useLocation();
|
||||
const { sidebarCollapsed, setSidebarCollapsed } = useStore();
|
||||
const { isMobile, isTablet, isDesktop } = useResponsive();
|
||||
const { sidebarCollapsed, setSidebarCollapsed, mobileMenuOpen, setMobileMenuOpen } = useStore();
|
||||
|
||||
// Debug logging pour production
|
||||
console.log('Sidebar - User:', user);
|
||||
@@ -48,9 +50,20 @@ export default function Sidebar() {
|
||||
};
|
||||
|
||||
const toggleSidebar = () => {
|
||||
const newCollapsed = !sidebarCollapsed;
|
||||
setSidebarCollapsed(newCollapsed);
|
||||
localStorage.setItem('sidebarCollapsed', JSON.stringify(newCollapsed));
|
||||
if (isMobile) {
|
||||
setMobileMenuOpen(!mobileMenuOpen);
|
||||
} else {
|
||||
const newCollapsed = !sidebarCollapsed;
|
||||
setSidebarCollapsed(newCollapsed);
|
||||
localStorage.setItem('sidebarCollapsed', JSON.stringify(newCollapsed));
|
||||
}
|
||||
};
|
||||
|
||||
// Close mobile menu when clicking on a link
|
||||
const handleLinkClick = () => {
|
||||
if (isMobile && mobileMenuOpen) {
|
||||
setMobileMenuOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getInitials = (firstName?: string, lastName?: string) => {
|
||||
@@ -168,10 +181,13 @@ export default function Sidebar() {
|
||||
|
||||
|
||||
|
||||
// Sur mobile, toujours afficher en mode collapsed quand fermé
|
||||
const isCollapsed = isMobile ? false : sidebarCollapsed;
|
||||
|
||||
// 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={`${isCollapsed ? 'w-16' : 'w-64'} bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col shadow-lg transition-all duration-300 ease-in-out`}>
|
||||
<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">
|
||||
@@ -207,7 +223,7 @@ 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={`${isCollapsed ? 'w-16' : 'w-64'} bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col shadow-lg transition-all duration-300 ease-in-out`}>
|
||||
<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">
|
||||
@@ -243,32 +259,34 @@ export default function Sidebar() {
|
||||
}
|
||||
|
||||
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={`${isCollapsed ? 'w-16' : 'w-64'} bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col shadow-lg transition-all duration-300 ease-in-out`}>
|
||||
{/* 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="h-16 flex items-center justify-between border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 px-3">
|
||||
{!isCollapsed && (
|
||||
<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>
|
||||
<Store className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
<span className="text-lg font-semibold text-gray-900 dark:text-gray-100">LogiFlow</span>
|
||||
</div>
|
||||
)}
|
||||
{sidebarCollapsed && (
|
||||
{isCollapsed && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<Store className="h-6 w-6 text-blue-600" />
|
||||
<Store className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
</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>
|
||||
{!isMobile && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8 p-0 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
@@ -290,16 +308,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 ${isCollapsed ? 'px-3 py-3 justify-center' : 'px-3 py-2'} text-sm font-medium transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 ${
|
||||
active
|
||||
? 'bg-gray-100 text-gray-900 border-r-2 border-gray-700'
|
||||
: 'text-gray-700'
|
||||
? 'bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100 border-r-2 border-blue-600 dark:border-blue-400'
|
||||
: 'text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
title={sidebarCollapsed ? item.label : undefined}
|
||||
onClick={() => console.log(`Navigating to: ${item.path}`)}
|
||||
title={isCollapsed ? item.label : undefined}
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${sidebarCollapsed ? '' : 'mr-3'}`} />
|
||||
{!sidebarCollapsed && item.label}
|
||||
<Icon className={`h-4 w-4 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && item.label}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
@@ -309,14 +327,14 @@ export default function Sidebar() {
|
||||
{/* Management Section */}
|
||||
{managementItems.some(item => hasPermission(item.roles)) && (
|
||||
<>
|
||||
{!sidebarCollapsed && (
|
||||
{!isCollapsed && (
|
||||
<div className="mt-6 mb-2">
|
||||
<h3 className="px-3 text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<h3 className="px-3 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Gestion
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
{sidebarCollapsed && <div className="mt-4 mb-2 border-t border-gray-200"></div>}
|
||||
{isCollapsed && <div className="mt-4 mb-2 border-t border-gray-200 dark:border-gray-700"></div>}
|
||||
<div className="space-y-1">
|
||||
{managementItems.map((item) => {
|
||||
if (!hasPermission(item.roles)) return null;
|
||||
@@ -327,16 +345,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 ${isCollapsed ? 'px-3 py-3 justify-center' : 'px-3 py-2'} text-sm font-medium transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 ${
|
||||
active
|
||||
? 'bg-gray-100 text-gray-900 border-r-2 border-gray-700'
|
||||
: 'text-gray-700'
|
||||
? 'bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100 border-r-2 border-blue-600 dark:border-blue-400'
|
||||
: 'text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
title={sidebarCollapsed ? item.label : undefined}
|
||||
onClick={() => console.log(`Navigating to management: ${item.path}`)}
|
||||
title={isCollapsed ? item.label : undefined}
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${sidebarCollapsed ? '' : 'mr-3'}`} />
|
||||
{!sidebarCollapsed && item.label}
|
||||
<Icon className={`h-4 w-4 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && item.label}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
@@ -348,10 +366,10 @@ export default function Sidebar() {
|
||||
|
||||
{/* Administration Section */}
|
||||
{adminItems.some(item => hasPermission(item.roles)) && (
|
||||
<div className="border-t border-gray-200 py-4 px-3">
|
||||
{!sidebarCollapsed && (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 py-4 px-3">
|
||||
{!isCollapsed && (
|
||||
<div className="mb-2">
|
||||
<h3 className="px-3 text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<h3 className="px-3 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Administration
|
||||
</h3>
|
||||
</div>
|
||||
@@ -366,16 +384,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 ${isCollapsed ? 'px-3 py-3 justify-center' : 'px-3 py-2'} text-sm font-medium transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 ${
|
||||
active
|
||||
? 'bg-gray-100 text-gray-900 border-r-2 border-gray-700'
|
||||
: 'text-gray-700'
|
||||
? 'bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100 border-r-2 border-blue-600 dark:border-blue-400'
|
||||
: 'text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
title={sidebarCollapsed ? item.label : undefined}
|
||||
onClick={() => console.log(`Navigating to admin: ${item.path}`)}
|
||||
title={isCollapsed ? item.label : undefined}
|
||||
onClick={handleLinkClick}
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${sidebarCollapsed ? '' : 'mr-3'}`} />
|
||||
{!sidebarCollapsed && item.label}
|
||||
<Icon className={`h-4 w-4 ${isCollapsed ? '' : 'mr-3'}`} />
|
||||
{!isCollapsed && item.label}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
@@ -385,20 +403,20 @@ export default function Sidebar() {
|
||||
)}
|
||||
|
||||
{/* User Profile & Logout */}
|
||||
<div className="border-t border-gray-200 p-4">
|
||||
{!sidebarCollapsed ? (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 p-4">
|
||||
{!isCollapsed ? (
|
||||
<>
|
||||
<div className="flex items-center space-x-3 mb-3">
|
||||
<div className="h-8 w-8 bg-gray-100 flex items-center justify-center">
|
||||
<span className="text-xs font-medium text-gray-700">
|
||||
<div className="h-8 w-8 bg-gray-100 dark:bg-gray-700 flex items-center justify-center rounded-full">
|
||||
<span className="text-xs font-medium text-gray-700 dark:text-gray-300">
|
||||
{getInitials(user?.firstName, user?.lastName)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-gray-900 truncate">
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">
|
||||
{user?.firstName || user?.lastName ? `${user?.firstName || ''} ${user?.lastName || ''}`.trim() : user?.username}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 truncate">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">
|
||||
{user?.role === 'admin' ? 'Administrateur' :
|
||||
user?.role === 'manager' ? 'Manager' :
|
||||
user?.role === 'directeur' ? 'Directeur' : 'Employé'}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface ResponsiveState {
|
||||
isMobile: boolean;
|
||||
isTablet: boolean;
|
||||
isDesktop: boolean;
|
||||
screenWidth: number;
|
||||
}
|
||||
|
||||
const BREAKPOINTS = {
|
||||
mobile: 768,
|
||||
tablet: 1024,
|
||||
} as const;
|
||||
|
||||
export function useResponsive(): ResponsiveState {
|
||||
const [state, setState] = useState<ResponsiveState>(() => {
|
||||
if (typeof window === 'undefined') {
|
||||
return {
|
||||
isMobile: false,
|
||||
isTablet: false,
|
||||
isDesktop: true,
|
||||
screenWidth: 1920,
|
||||
};
|
||||
}
|
||||
|
||||
const width = window.innerWidth;
|
||||
return {
|
||||
isMobile: width < BREAKPOINTS.mobile,
|
||||
isTablet: width >= BREAKPOINTS.mobile && width < BREAKPOINTS.tablet,
|
||||
isDesktop: width >= BREAKPOINTS.tablet,
|
||||
screenWidth: width,
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const width = window.innerWidth;
|
||||
setState({
|
||||
isMobile: width < BREAKPOINTS.mobile,
|
||||
isTablet: width >= BREAKPOINTS.mobile && width < BREAKPOINTS.tablet,
|
||||
isDesktop: width >= BREAKPOINTS.tablet,
|
||||
screenWidth: width,
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// Hook spécialisé pour la navigation mobile
|
||||
export function useIsMobileNavigation() {
|
||||
const { isMobile } = useResponsive();
|
||||
return isMobile;
|
||||
}
|
||||
@@ -125,6 +125,44 @@
|
||||
.hover\:bg-accent:hover {
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Responsive utilities */
|
||||
.container-responsive {
|
||||
@apply w-full mx-auto px-4 sm:px-6 lg:px-8;
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.grid-responsive {
|
||||
@apply grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4;
|
||||
}
|
||||
|
||||
.card-responsive {
|
||||
@apply bg-card border border-border rounded-lg shadow-sm hover:shadow-md transition-shadow p-4 sm:p-6;
|
||||
}
|
||||
|
||||
.text-responsive-sm {
|
||||
@apply text-sm sm:text-base;
|
||||
}
|
||||
|
||||
.text-responsive-lg {
|
||||
@apply text-lg sm:text-xl lg:text-2xl;
|
||||
}
|
||||
|
||||
.sidebar-width {
|
||||
@apply w-16 lg:w-64;
|
||||
}
|
||||
|
||||
.sidebar-collapsed {
|
||||
@apply w-16;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@apply flex-1 flex flex-col overflow-hidden ml-16 lg:ml-64;
|
||||
}
|
||||
|
||||
.main-content-expanded {
|
||||
@apply ml-16;
|
||||
}
|
||||
}
|
||||
|
||||
/* Supprimer le contour orange de la date du jour dans le calendrier */
|
||||
|
||||
Reference in new issue
Block a user