Files
car_mms/app/components/layout/DashboardLayout.tsx
2026-03-08 14:27:16 +03:00

203 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ReactNode, useState, useEffect } from 'react';
import { Form } from '@remix-run/react';
import { Sidebar } from './Sidebar';
import { Container } from './Container';
import { Flex } from './Flex';
import { Text, Button } from '../ui';
import { designTokens } from '~/lib/design-tokens';
interface DashboardLayoutProps {
children: ReactNode;
user: {
id: number;
name: string;
authLevel: number;
};
title?: string; // Optional page title for mobile header
}
export function DashboardLayout({ children, user, title }: DashboardLayoutProps) {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [isClient, setIsClient] = useState(false);
// Set client-side flag
useEffect(() => {
setIsClient(true);
}, []);
// Handle responsive behavior
useEffect(() => {
if (!isClient) return;
const checkMobile = () => {
const mobile = window.innerWidth < 768; // md breakpoint
setIsMobile(mobile);
// Auto-collapse sidebar on mobile and tablet
if (mobile) {
setSidebarCollapsed(true);
}
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => window.removeEventListener('resize', checkMobile);
}, [isClient]);
// Load sidebar state from localStorage
useEffect(() => {
if (!isClient) return;
const savedState = localStorage.getItem('sidebarCollapsed');
if (savedState !== null && !isMobile) {
setSidebarCollapsed(JSON.parse(savedState));
}
}, [isMobile, isClient]);
// Save sidebar state to localStorage
const handleSidebarToggle = () => {
const newState = !sidebarCollapsed;
setSidebarCollapsed(newState);
if (!isMobile) {
localStorage.setItem('sidebarCollapsed', JSON.stringify(newState));
}
};
const handleMobileMenuClose = () => {
setMobileMenuOpen(false);
};
const getAuthLevelText = (authLevel: number) => {
switch (authLevel) {
case 1:
return "مدير عام";
case 2:
return "مدير";
case 3:
return "مستخدم";
default:
return "غير محدد";
}
};
return (
<div className="min-h-screen bg-gray-50 flex" dir="rtl">
{/* Sidebar */}
<Sidebar
isCollapsed={sidebarCollapsed}
onToggle={handleSidebarToggle}
isMobile={isMobile}
isOpen={mobileMenuOpen}
onClose={handleMobileMenuClose}
userAuthLevel={user.authLevel}
/>
{/* Main Content */}
<div
className="flex-1 min-h-screen transition-all duration-300 ease-out"
style={{
marginRight: isClient ? (isMobile ? '0' : (sidebarCollapsed ? '4rem' : '16rem')) : '16rem'
}}
>
{/* Header */}
<header
className="bg-white shadow-sm border-b border-gray-200 sticky top-0"
style={{ zIndex: designTokens.zIndex.header }}
>
<div className="max-w-full mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Mobile menu button and title - Always render, hide with CSS */}
<div className="flex items-center gap-3 md:gap-4">
<button
onClick={() => setMobileMenuOpen(true)}
className="md:hidden p-2 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors"
aria-label="فتح القائمة"
aria-expanded={mobileMenuOpen}
>
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
{/* Page title - show on mobile */}
<h1 className="text-base font-bold text-gray-900 md:hidden">
{title || 'لوحة التحكم'}
</h1>
</div>
{/* User info and actions */}
<div className="flex items-center gap-2 sm:gap-3 md:gap-4">
{/* Desktop: Full user info */}
<div className="hidden lg:block text-right">
<div className="text-sm font-medium text-gray-900">
مرحباً، {user.name}
</div>
<div className="text-xs text-gray-500">
{getAuthLevelText(user.authLevel)}
</div>
</div>
{/* Tablet: Name only */}
<div className="hidden md:block lg:hidden text-right">
<div className="text-sm font-medium text-gray-900">
{user.name}
</div>
</div>
{/* Mobile: Compact display */}
<div className="md:hidden text-right">
<div className="text-xs font-medium text-gray-900">
{user.name}
</div>
</div>
<Form action="/logout" method="post">
<button
type="submit"
className="inline-flex items-center px-2.5 py-1.5 sm:px-3 sm:py-2 border border-transparent text-xs sm:text-sm leading-4 font-medium rounded-lg text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-150"
aria-label="تسجيل الخروج"
>
<svg
className="h-4 w-4 sm:mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
<span className="hidden sm:inline">خروج</span>
</button>
</Form>
</div>
</div>
</div>
</header>
{/* Page Content */}
<main className="flex-1 p-4 sm:p-5 md:p-6 lg:p-8">
{children}
</main>
</div>
</div>
);
}