Added optimizations for mobile
This commit is contained in:
@@ -33,6 +33,7 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showNotifications, setShowNotifications] = useState(false);
|
||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
||||
// Récupérer les conversations pour compter les messages non lus
|
||||
const { data: conversations } = useSWR<Array<{ unreadCount: number }>>(
|
||||
@@ -190,10 +191,20 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-cwhite flex">
|
||||
{/* Mobile Sidebar Overlay */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<aside className="w-64 bg-white flex flex-col h-screen sticky top-0 border-r border-gray-200">
|
||||
<aside className={`fixed lg:static inset-y-0 left-0 z-50 w-64 bg-white flex flex-col h-screen border-r border-gray-200 transform transition-transform duration-300 ease-in-out ${
|
||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
|
||||
}`}>
|
||||
{/* Logo Section */}
|
||||
<div className="py-5 m-auto">
|
||||
<div className="py-5 px-4 flex items-center justify-between lg:justify-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<Image
|
||||
src="/logo.svg"
|
||||
@@ -202,9 +213,19 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
height={32}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-semibold text-gray-900">Association MAD</span>
|
||||
<span className="font-semibold text-gray-900 hidden sm:inline">Association MAD</span>
|
||||
<span className="font-semibold text-gray-900 sm:hidden">MAD</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Close button mobile */}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="lg:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
@@ -228,6 +249,7 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
<li key={item.href}>
|
||||
<Link
|
||||
href={item.href}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors relative ${
|
||||
isActive
|
||||
? 'bg-lblue text-white'
|
||||
@@ -261,22 +283,32 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Top Navbar */}
|
||||
<header className="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<header className="bg-white border-b border-gray-200 px-4 sm:px-6 py-3 sm:py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold text-gray-900"></h1>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Hamburger menu button */}
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="lg:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<h1 className="text-xl font-semibold text-gray-900 hidden lg:block"></h1>
|
||||
<div className="flex items-center gap-1.5 sm:gap-3">
|
||||
{/* Notification Button */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowNotifications(!showNotifications)}
|
||||
className="relative w-10 h-10 rounded-lg bg-gray-50 hover:bg-gray-100 border border-gray-200 flex items-center justify-center transition-all duration-200 hover:shadow-sm group"
|
||||
className="relative w-8 h-8 sm:w-10 sm:h-10 rounded-lg bg-gray-50 hover:bg-gray-100 border border-gray-200 flex items-center justify-center transition-all duration-200 hover:shadow-sm group"
|
||||
aria-label={`Notifications${unreadNotificationsCount > 0 ? ` (${unreadNotificationsCount} non lues)` : ''}`}
|
||||
>
|
||||
<svg className="w-5 h-5 text-gray-600 group-hover:text-gray-900 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-4 h-4 sm:w-5 sm:h-5 text-gray-600 group-hover:text-gray-900 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
{/* Badge de notification */}
|
||||
{unreadNotificationsCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 min-w-[18px] h-[18px] bg-red-500 text-white text-[10px] font-semibold rounded-full border-2 border-white flex items-center justify-center px-1">
|
||||
<span className="absolute -top-0.5 -right-0.5 min-w-[16px] h-4 sm:min-w-[18px] sm:h-[18px] bg-red-500 text-white text-[9px] sm:text-[10px] font-semibold rounded-full border-2 border-white flex items-center justify-center px-0.5 sm:px-1">
|
||||
{unreadNotificationsCount > 99 ? '99+' : unreadNotificationsCount}
|
||||
</span>
|
||||
)}
|
||||
@@ -284,9 +316,9 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
|
||||
{/* Dropdown Notifications */}
|
||||
{showNotifications && (
|
||||
<div className="absolute right-0 top-12 w-80 bg-white rounded-lg shadow-xl border border-gray-200 z-50 animate-slideUp">
|
||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-gray-900">Notifications</h3>
|
||||
<div className="fixed sm:absolute right-2 sm:right-0 top-14 sm:top-12 w-[calc(100vw-1rem)] sm:w-80 max-w-sm bg-white rounded-lg shadow-xl border border-gray-200 z-50 animate-slideUp max-h-[calc(100vh-4rem)] sm:max-h-96 flex flex-col">
|
||||
<div className="p-3 sm:p-4 border-b border-gray-200 flex items-center justify-between flex-shrink-0">
|
||||
<h3 className="text-xs sm:text-sm font-semibold text-gray-900">Notifications</h3>
|
||||
{unreadNotificationsCount > 0 && (
|
||||
<button
|
||||
onClick={async () => {
|
||||
@@ -303,15 +335,15 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
console.error('Erreur lors du marquage des notifications:', error);
|
||||
}
|
||||
}}
|
||||
className="text-xs text-lblue hover:text-dblue font-medium"
|
||||
className="text-[10px] sm:text-xs text-lblue hover:text-dblue font-medium"
|
||||
>
|
||||
Tout marquer comme lu
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
<div className="overflow-y-auto flex-1 min-h-0">
|
||||
{notifications.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-gray-500">
|
||||
<div className="p-4 text-center text-xs sm:text-sm text-gray-500">
|
||||
Aucune notification
|
||||
</div>
|
||||
) : (
|
||||
@@ -341,22 +373,22 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
setShowNotifications(false);
|
||||
}
|
||||
}}
|
||||
className={`w-full text-left p-4 hover:bg-gray-50 transition-colors ${
|
||||
className={`w-full text-left p-3 sm:p-4 hover:bg-gray-50 transition-colors active:bg-gray-100 ${
|
||||
!notification.read ? 'bg-blue-50/50' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`flex-shrink-0 w-2 h-2 rounded-full mt-2 ${
|
||||
<div className="flex items-start gap-2 sm:gap-3">
|
||||
<div className={`flex-shrink-0 w-1.5 h-1.5 sm:w-2 sm:h-2 rounded-full mt-1.5 sm:mt-2 ${
|
||||
!notification.read ? 'bg-lblue' : 'bg-transparent'
|
||||
}`}></div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<p className={`text-sm font-medium ${
|
||||
<div className="flex items-start sm:items-center justify-between gap-2 mb-1">
|
||||
<p className={`text-xs sm:text-sm font-medium flex-1 ${
|
||||
!notification.read ? 'text-gray-900' : 'text-gray-700'
|
||||
}`}>
|
||||
{notification.title}
|
||||
</p>
|
||||
<span className="text-xs text-gray-400 ml-2 flex-shrink-0">
|
||||
<span className="text-[10px] sm:text-xs text-gray-400 flex-shrink-0 whitespace-nowrap">
|
||||
{new Date(notification.createdAt).toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
@@ -365,7 +397,7 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 line-clamp-2">
|
||||
<p className="text-[11px] sm:text-xs text-gray-600 line-clamp-2 sm:line-clamp-3">
|
||||
{notification.message}
|
||||
</p>
|
||||
</div>
|
||||
@@ -383,31 +415,31 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowProfileMenu(!showProfileMenu)}
|
||||
className="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-50 border border-gray-200 transition-all duration-200 hover:shadow-sm group"
|
||||
className="flex items-center gap-2 sm:gap-3 px-2 sm:px-3 py-2 rounded-lg hover:bg-gray-50 border border-gray-200 transition-all duration-200 hover:shadow-sm group"
|
||||
>
|
||||
{userPhotoUrl ? (
|
||||
<img
|
||||
src={userPhotoUrl}
|
||||
alt={user.name || 'Utilisateur'}
|
||||
className="w-9 h-9 rounded-lg object-cover shadow-sm border border-gray-200"
|
||||
className="w-8 h-8 sm:w-9 sm:h-9 rounded-lg object-cover shadow-sm border border-gray-200"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-lblue to-dblue flex items-center justify-center shadow-sm">
|
||||
<span className="text-white text-sm font-semibold">{getUserInitials()}</span>
|
||||
<div className="w-8 h-8 sm:w-9 sm:h-9 rounded-lg bg-gradient-to-br from-lblue to-dblue flex items-center justify-center shadow-sm">
|
||||
<span className="text-white text-xs sm:text-sm font-semibold">{getUserInitials()}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="hidden md:block text-left">
|
||||
<div className="hidden lg:block text-left">
|
||||
<div className="text-sm font-medium text-gray-900">{user.name || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-500">{user.email}</div>
|
||||
</div>
|
||||
<svg className="w-4 h-4 text-gray-400 group-hover:text-gray-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="hidden sm:block w-4 h-4 text-gray-400 group-hover:text-gray-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Profile Menu */}
|
||||
{showProfileMenu && (
|
||||
<div className="absolute right-0 top-14 w-56 bg-white rounded-lg shadow-xl border border-gray-200 z-50 animate-slideUp">
|
||||
<div className="absolute right-0 top-14 w-56 sm:w-64 bg-white rounded-lg shadow-xl border border-gray-200 z-50 animate-slideUp">
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="text-sm font-medium text-gray-900">{user.name || 'Utilisateur'}</div>
|
||||
<div className="text-xs text-gray-500 mt-1">{user.email}</div>
|
||||
@@ -454,7 +486,7 @@ export default function DashboardLayout({ user, children }: DashboardLayoutProps
|
||||
{/* Overlay pour fermer les menus */}
|
||||
{(showNotifications || showProfileMenu) && (
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
className="fixed inset-0 z-40 lg:z-30"
|
||||
onClick={() => {
|
||||
setShowNotifications(false);
|
||||
setShowProfileMenu(false);
|
||||
|
||||
Reference in New Issue
Block a user