Make app navigation mobile usable
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 47s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 47s
This commit is contained in:
@@ -94,12 +94,12 @@ const DateRangePicker: React.FC<DateRangePickerProps> = ({ dateRange, onChange,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:flex-wrap sm:items-center sm:gap-3">
|
||||
{/* Date Presets Dropdown */}
|
||||
<div className="relative">
|
||||
<div className="relative w-full sm:w-auto">
|
||||
<button
|
||||
onClick={() => setIsPresetOpen(!isPresetOpen)}
|
||||
className="flex items-center gap-2 bg-dark-card border border-dark-border px-4 py-2.5 rounded-xl shadow-sm hover:border-brand-primary transition-colors text-sm font-medium text-dark-text cursor-pointer"
|
||||
className="flex min-h-11 w-full items-center gap-2 rounded-xl border border-dark-border bg-dark-card px-4 py-2.5 text-sm font-medium text-dark-text shadow-sm transition-colors hover:border-brand-primary cursor-pointer sm:w-auto"
|
||||
>
|
||||
<Calendar size={16} className="text-dark-muted shrink-0" />
|
||||
<span>{formatDisplayDate(dateRange.start)} - {formatDisplayDate(dateRange.end)}</span>
|
||||
@@ -109,7 +109,7 @@ const DateRangePicker: React.FC<DateRangePickerProps> = ({ dateRange, onChange,
|
||||
{isPresetOpen && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-10" onClick={() => setIsPresetOpen(false)}></div>
|
||||
<div className="absolute right-0 mt-2 w-56 bg-dark-card border border-dark-border rounded-xl shadow-xl z-20 py-2 max-h-[32rem] overflow-y-auto">
|
||||
<div className="fixed inset-x-3 top-20 z-20 mt-0 max-h-[calc(100dvh-6rem)] overflow-y-auto rounded-xl border border-dark-border bg-dark-card py-2 shadow-xl sm:absolute sm:right-0 sm:top-auto sm:inset-x-auto sm:mt-2 sm:max-h-[32rem] sm:w-56">
|
||||
<div className="px-4 pb-3 pt-1 border-b border-dark-border mb-2 flex flex-col gap-2">
|
||||
<span className="text-xs font-bold text-dark-muted uppercase tracking-widest">Período Customizado</span>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
@@ -177,7 +177,7 @@ const DateRangePicker: React.FC<DateRangePickerProps> = ({ dateRange, onChange,
|
||||
|
||||
{/* Auto Refresh Dropdown */}
|
||||
{setRefreshInterval && onManualRefresh && (
|
||||
<div className="flex items-center bg-dark-card border border-dark-border rounded-xl shadow-sm overflow-hidden hover:border-brand-primary transition-colors">
|
||||
<div className="flex min-h-11 w-full items-center overflow-hidden rounded-xl border border-dark-border bg-dark-card shadow-sm transition-colors hover:border-brand-primary sm:w-auto">
|
||||
<button
|
||||
onClick={onManualRefresh}
|
||||
className="flex items-center gap-1.5 px-4 py-2.5 text-sm font-medium text-dark-text hover:text-brand-primary hover:bg-dark-input transition-colors border-r border-dark-border cursor-pointer"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Outlet, Link, useLocation } from 'react-router-dom';
|
||||
import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, LogOut, Megaphone, Grid3X3, Shield, Moon, Sun, Tags, Boxes, ClipboardList } from 'lucide-react';
|
||||
import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, LogOut, Megaphone, Grid3X3, Shield, Moon, Sun, Tags, Boxes, ClipboardList, Menu, X } from 'lucide-react';
|
||||
import type { DateRange, OrderData } from '../types';
|
||||
import { isSuperAdmin, logout } from '../dataService';
|
||||
import { rangeForLastDays } from '../dateRanges';
|
||||
@@ -13,6 +13,7 @@ const Layout = () => {
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => {
|
||||
return localStorage.getItem('graph_sidebar_collapsed') === 'true';
|
||||
});
|
||||
const [isMobileNavOpen, setIsMobileNavOpen] = useState(false);
|
||||
const [themeMode, setThemeMode] = useState<ThemeMode>(() => {
|
||||
return localStorage.getItem('nexstar_theme') === 'offwhite' ? 'offwhite' : 'dark';
|
||||
});
|
||||
@@ -58,6 +59,7 @@ const Layout = () => {
|
||||
const toggleTheme = () => {
|
||||
setThemeMode(current => current === 'dark' ? 'offwhite' : 'dark');
|
||||
};
|
||||
const showSidebarLabels = !isSidebarCollapsed || isMobileNavOpen;
|
||||
|
||||
const adminNavigation = isSuperAdmin()
|
||||
? [{ name: 'Usuários', href: '/admin/users', icon: Shield }]
|
||||
@@ -90,25 +92,36 @@ const Layout = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="app-shell flex h-screen text-dark-text overflow-hidden">
|
||||
<div className="app-shell flex h-dvh min-w-0 text-dark-text overflow-hidden">
|
||||
{isMobileNavOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="fixed inset-0 z-40 bg-black/55 md:hidden"
|
||||
onClick={() => setIsMobileNavOpen(false)}
|
||||
aria-label="Fechar menu"
|
||||
/>
|
||||
)}
|
||||
{/* Sidebar */}
|
||||
<aside className={`bg-dark-sidebar border-r border-dark-border flex flex-col transition-all duration-300 ${isSidebarCollapsed ? 'w-20' : 'w-64'}`}>
|
||||
<div className={`h-20 px-6 border-b border-dark-border flex items-center ${isSidebarCollapsed ? 'justify-center' : 'justify-between'}`}>
|
||||
<aside className={`fixed inset-y-0 left-0 z-50 flex w-72 flex-col border-r border-dark-border bg-dark-sidebar transition-transform duration-300 md:relative md:z-auto md:translate-x-0 md:transition-[width] ${isSidebarCollapsed ? 'md:w-20' : 'md:w-64'} ${isMobileNavOpen ? 'translate-x-0' : '-translate-x-full'}`}>
|
||||
<div className={`flex h-16 items-center border-b border-dark-border px-5 md:h-20 md:px-6 ${isSidebarCollapsed ? 'md:justify-center' : 'justify-between'}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="rounded-lg border border-brand-primary/25 bg-brand-primary/15 p-1.5 text-brand-primary shadow-sm shadow-brand-primary/10">
|
||||
<BarChart3 className="w-6 h-6" />
|
||||
</div>
|
||||
{!isSidebarCollapsed && <span className="text-xl font-bold text-dark-text">Nexstar</span>}
|
||||
{showSidebarLabels && <span className="text-xl font-bold text-dark-text">Nexstar</span>}
|
||||
</div>
|
||||
<button onClick={() => setIsMobileNavOpen(false)} className="text-dark-muted transition-colors hover:text-dark-text md:hidden" aria-label="Fechar menu">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
{!isSidebarCollapsed && (
|
||||
<button onClick={toggleSidebar} className="text-dark-muted hover:text-dark-text transition-colors cursor-pointer">
|
||||
<button onClick={toggleSidebar} className="hidden cursor-pointer text-dark-muted transition-colors hover:text-dark-text md:block">
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isSidebarCollapsed && (
|
||||
<div className="p-4 border-b border-dark-border flex justify-center">
|
||||
<div className="hidden border-b border-dark-border p-4 md:flex md:justify-center">
|
||||
<button onClick={toggleSidebar} className="text-dark-muted hover:text-dark-text transition-colors cursor-pointer">
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
@@ -120,10 +133,10 @@ const Layout = () => {
|
||||
<div
|
||||
key={section.label}
|
||||
className={`space-y-1.5 ${
|
||||
isSidebarCollapsed ? 'border-t border-dark-border pt-5 first:border-t-0 first:pt-0' : ''
|
||||
isSidebarCollapsed ? 'md:border-t md:border-dark-border md:pt-5 md:first:border-t-0 md:first:pt-0' : ''
|
||||
}`}
|
||||
>
|
||||
{!isSidebarCollapsed && (
|
||||
{showSidebarLabels && (
|
||||
<div className="px-3 pb-1 text-[11px] font-bold uppercase tracking-widest text-dark-muted">
|
||||
{section.label}
|
||||
</div>
|
||||
@@ -134,8 +147,9 @@ const Layout = () => {
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
onClick={() => setIsMobileNavOpen(false)}
|
||||
className={`group flex min-h-11 items-center rounded-lg px-3 py-2.5 text-sm transition-all ${
|
||||
isSidebarCollapsed ? 'justify-center' : 'gap-3'
|
||||
isSidebarCollapsed ? 'gap-3 md:justify-center' : 'gap-3'
|
||||
} ${
|
||||
isActive
|
||||
? 'bg-brand-primary/12 text-brand-primary font-semibold shadow-sm shadow-brand-primary/10 ring-1 ring-brand-primary/15'
|
||||
@@ -144,7 +158,7 @@ const Layout = () => {
|
||||
title={isSidebarCollapsed ? item.name : undefined}
|
||||
>
|
||||
<item.icon className="h-4.5 w-4.5 shrink-0" />
|
||||
{!isSidebarCollapsed && <span className="truncate font-medium">{item.name}</span>}
|
||||
{showSidebarLabels && <span className="truncate font-medium">{item.name}</span>}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@@ -155,23 +169,33 @@ const Layout = () => {
|
||||
<div className="p-4 border-t border-dark-border">
|
||||
<button
|
||||
onClick={logout}
|
||||
className={`w-full flex items-center text-red-500 hover:bg-red-500/10 px-4 py-3 rounded-xl transition-all cursor-pointer ${isSidebarCollapsed ? 'justify-center' : 'space-x-3'}`}
|
||||
className={`flex w-full items-center rounded-xl px-4 py-3 text-red-500 transition-all hover:bg-red-500/10 cursor-pointer ${isSidebarCollapsed ? 'gap-3 md:justify-center' : 'gap-3'}`}
|
||||
>
|
||||
<LogOut className="w-5 h-5 shrink-0" />
|
||||
{!isSidebarCollapsed && <span className="font-medium">Sair</span>}
|
||||
{showSidebarLabels && <span className="font-medium">Sair</span>}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex-1 flex flex-col h-screen overflow-hidden">
|
||||
<main className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
{/* Header */}
|
||||
<header className="h-20 bg-dark-header border-b border-dark-border flex items-center justify-between px-8 shrink-0">
|
||||
<h2 className="text-xl font-bold text-dark-text">Painel de Análise</h2>
|
||||
<header className="flex h-16 shrink-0 items-center justify-between border-b border-dark-border bg-dark-header px-4 md:h-20 md:px-8">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsMobileNavOpen(true)}
|
||||
className="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border border-dark-border bg-dark-input text-dark-text md:hidden"
|
||||
aria-label="Abrir menu"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</button>
|
||||
<h2 className="truncate text-lg font-bold text-dark-text md:text-xl">Painel de Análise</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleTheme}
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-xl border border-transparent bg-transparent text-dark-text transition-colors hover:border-dark-border hover:bg-dark-input hover:text-brand-primary cursor-pointer"
|
||||
className="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border border-transparent bg-transparent text-dark-text transition-colors hover:border-dark-border hover:bg-dark-input hover:text-brand-primary cursor-pointer"
|
||||
title={themeMode === 'dark' ? 'Usar modo off-white' : 'Usar modo escuro'}
|
||||
aria-label={themeMode === 'dark' ? 'Usar modo off-white' : 'Usar modo escuro'}
|
||||
>
|
||||
@@ -180,7 +204,7 @@ const Layout = () => {
|
||||
</header>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="app-content flex-1 overflow-y-auto p-8 relative">
|
||||
<div className="app-content relative flex-1 overflow-y-auto px-4 py-5 pb-24 sm:px-5 md:p-8">
|
||||
<Outlet context={{ dateRange, setDateRange, ordersData: emptyOrdersData, isDataLoading: false, refreshInterval, setRefreshInterval, themeMode }} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -149,6 +149,36 @@
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0) 16rem),
|
||||
transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-width: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
scroll-padding-bottom: 5rem;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.app-content .overflow-x-auto {
|
||||
overscroll-behavior-x: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-dark-border) transparent;
|
||||
}
|
||||
|
||||
.app-content .overflow-x-auto::-webkit-scrollbar {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.app-content input,
|
||||
.app-content select,
|
||||
.app-content textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
||||
Reference in New Issue
Block a user