feat: add mobile responsive support (768px breakpoint)

- Add CSS media queries for all sections: sidebar overlay, navbar,
  calendar, kanban, dashboard, list view, drawer, modal, reports,
  team tasks, members
- Add hamburger menu button (hidden on desktop, visible on mobile)
- Add sidebar slide-in overlay with backdrop for mobile
- Auto-close sidebar on navigation for mobile UX
- Login card, drawer, and modal go full-width on mobile
- Dashboard stats grid collapses to 2-column on mobile
- Report charts stack to single column on mobile
This commit is contained in:
tusuii
2026-02-15 13:10:39 +05:30
parent e46d8773ee
commit 5d8af1f173
4 changed files with 2291 additions and 273 deletions

View File

@@ -34,6 +34,7 @@ export default function App() {
const [calView, setCalView] = useState('month'); const [calView, setCalView] = useState('month');
const [filterUser, setFilterUser] = useState<string | null>(null); const [filterUser, setFilterUser] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [sidebarOpen, setSidebarOpen] = useState(false);
const [quickAddDay, setQuickAddDay] = useState<{ date: string; rect: { top: number; left: number } } | null>(null); const [quickAddDay, setQuickAddDay] = useState<{ date: string; rect: { top: number; left: number } } | null>(null);
if (!currentUser) return <LoginPage onLogin={u => { setCurrentUser(u); setActivePage('calendar'); setActiveView('calendar'); }} />; if (!currentUser) return <LoginPage onLogin={u => { setCurrentUser(u); setActivePage('calendar'); setActiveView('calendar'); }} />;
@@ -41,6 +42,7 @@ export default function App() {
const handleNavigate = (page: string) => { const handleNavigate = (page: string) => {
setActivePage(page); setActivePage(page);
if (VIEW_PAGES.includes(page)) setActiveView(page); if (VIEW_PAGES.includes(page)) setActiveView(page);
setSidebarOpen(false);
}; };
const handleViewChange = (view: string) => { const handleViewChange = (view: string) => {
@@ -88,10 +90,12 @@ export default function App() {
return ( return (
<div className="app-shell"> <div className="app-shell">
<TopNavbar title={pageTitle} filterUser={filterUser} onFilterChange={setFilterUser} <TopNavbar title={pageTitle} filterUser={filterUser} onFilterChange={setFilterUser}
searchQuery={searchQuery} onSearch={setSearchQuery} onNewTask={handleNewTask} /> searchQuery={searchQuery} onSearch={setSearchQuery} onNewTask={handleNewTask}
onOpenSidebar={() => setSidebarOpen(true)} />
<div className="app-body"> <div className="app-body">
<Sidebar currentUser={currentUser} activePage={activePage} onNavigate={handleNavigate} <Sidebar currentUser={currentUser} activePage={activePage} onNavigate={handleNavigate}
onSignOut={() => { setCurrentUser(null); setActivePage('calendar'); setActiveView('calendar'); }} /> onSignOut={() => { setCurrentUser(null); setActivePage('calendar'); setActiveView('calendar'); setSidebarOpen(false); }}
isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
<div className="main-content"> <div className="main-content">
{displayPage === 'calendar' && ( {displayPage === 'calendar' && (
<CalendarView tasks={tasks} currentUser={currentUser} calMonth={calMonth} calView={calView} <CalendarView tasks={tasks} currentUser={currentUser} calMonth={calMonth} calView={calView}

View File

@@ -8,11 +8,13 @@ interface TopNavbarProps {
searchQuery: string; searchQuery: string;
onSearch: (q: string) => void; onSearch: (q: string) => void;
onNewTask: () => void; onNewTask: () => void;
onOpenSidebar: () => void;
} }
export function TopNavbar({ title, filterUser, onFilterChange, searchQuery, onSearch, onNewTask }: TopNavbarProps) { export function TopNavbar({ title, filterUser, onFilterChange, searchQuery, onSearch, onNewTask, onOpenSidebar }: TopNavbarProps) {
return ( return (
<div className="top-navbar"> <div className="top-navbar">
<button className="hamburger-btn" onClick={onOpenSidebar}></button>
<span className="navbar-title">{title}</span> <span className="navbar-title">{title}</span>
<div className="navbar-search"> <div className="navbar-search">
<span className="navbar-search-icon">🔍</span> <span className="navbar-search-icon">🔍</span>

View File

@@ -17,36 +17,42 @@ interface SidebarProps {
activePage: string; activePage: string;
onNavigate: (page: string) => void; onNavigate: (page: string) => void;
onSignOut: () => void; onSignOut: () => void;
isOpen: boolean;
onClose: () => void;
} }
export function Sidebar({ currentUser, activePage, onNavigate, onSignOut }: SidebarProps) { export function Sidebar({ currentUser, activePage, onNavigate, onSignOut, isOpen, onClose }: SidebarProps) {
const filteredNav = NAV_ITEMS.filter(n => n.roles.includes(currentUser.role)); const filteredNav = NAV_ITEMS.filter(n => n.roles.includes(currentUser.role));
return ( return (
<div className="sidebar"> <>
<div className="sidebar-logo"> {isOpen && <div className="sidebar-backdrop visible" onClick={onClose} />}
<div className="sidebar-logo-icon"></div> <div className={`sidebar ${isOpen ? 'sidebar-open' : ''}`}>
<span className="sidebar-logo-text">Scrum-manager</span> <div className="sidebar-logo">
</div> <div className="sidebar-logo-icon"></div>
<div className="sidebar-divider" /> <span className="sidebar-logo-text">Scrum-manager</span>
<div className="sidebar-section-label">Navigate</div> </div>
<nav className="sidebar-nav"> <div className="sidebar-divider" />
{filteredNav.map(n => ( <div className="sidebar-section-label">Navigate</div>
<div key={n.id} className={`sidebar-item ${activePage === n.id ? 'active' : ''}`} onClick={() => onNavigate(n.id)}> <nav className="sidebar-nav">
<span className="sidebar-item-icon">{n.icon}</span> {filteredNav.map(n => (
{n.label} <div key={n.id} className={`sidebar-item ${activePage === n.id ? 'active' : ''}`} onClick={() => onNavigate(n.id)}>
<span className="sidebar-item-icon">{n.icon}</span>
{n.label}
</div>
))}
</nav>
<div className="sidebar-profile">
<Avatar userId={currentUser.id} size={36} />
<div className="sidebar-profile-info">
<div className="sidebar-profile-name">{currentUser.name}</div>
<RoleBadge role={currentUser.role} />
</div> </div>
))} </div>
</nav> <div style={{ padding: '0 16px 12px' }}>
<div className="sidebar-profile"> <button className="sidebar-signout" onClick={onSignOut}>Sign Out</button>
<Avatar userId={currentUser.id} size={36} />
<div className="sidebar-profile-info">
<div className="sidebar-profile-name">{currentUser.name}</div>
<RoleBadge role={currentUser.role} />
</div> </div>
</div> </div>
<div style={{ padding: '0 16px 12px' }}> </>
<button className="sidebar-signout" onClick={onSignOut}>Sign Out</button>
</div>
</div>
); );
} }

File diff suppressed because it is too large Load Diff