feat: complete UI/UX overhaul - dashboard unification, calendar UX, mobile optimization

- Dashboard redesign as command center with filters, quick actions, inline approve/reject
- Reusable components: BookingRow, BookingFilters, ActionMenu, BookingPreviewModal, BookingEditModal
- Calendar: drag & drop reschedule, eventClick preview modal, grid/list toggle
- Mobile: segmented control bookings/calendar toggle, compact pills, responsive layout
- Collapsible filters with active count badge
- Smart menu positioning with Teleport
- Calendar/list bidirectional data sync
- Navigation: unified History page, removed AdminPending
- Google Calendar OAuth integration
- Dark mode contrast improvements, breadcrumb navigation
- useLocalStorage composable for state persistence

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-12 15:34:47 +00:00
parent a4d3f862d2
commit d245c72757
36 changed files with 5275 additions and 1569 deletions

View File

@@ -1,13 +1,13 @@
<template>
<aside class="sidebar" :class="{ collapsed, 'mobile-open': mobileOpen }">
<div class="sidebar-header">
<div class="sidebar-header" @click="handleHeaderClick" :title="collapsed ? 'Expand sidebar' : 'Collapse sidebar'">
<LayoutDashboard :size="24" class="sidebar-logo-icon" />
<span v-show="!collapsed" class="sidebar-title">Space Booking</span>
<span v-show="showLabels" class="sidebar-title">Space Booking</span>
</div>
<nav class="sidebar-nav">
<div class="nav-section">
<span v-show="!collapsed" class="nav-section-label">Main</span>
<span v-show="showLabels" class="nav-section-label">Main</span>
<router-link
v-for="item in mainNav"
:key="item.to"
@@ -17,12 +17,12 @@
@click="closeMobile"
>
<component :is="item.icon" :size="20" class="nav-icon" />
<span v-show="!collapsed" class="nav-label">{{ item.label }}</span>
<span v-show="showLabels" class="nav-label">{{ item.label }}</span>
</router-link>
</div>
<div v-if="authStore.isAdmin" class="nav-section">
<span v-show="!collapsed" class="nav-section-label">Admin</span>
<span v-show="showLabels" class="nav-section-label">Admin</span>
<router-link
v-for="item in adminNav"
:key="item.to"
@@ -32,13 +32,13 @@
@click="closeMobile"
>
<component :is="item.icon" :size="20" class="nav-icon" />
<span v-show="!collapsed" class="nav-label">{{ item.label }}</span>
<span v-show="showLabels" class="nav-label">{{ item.label }}</span>
</router-link>
</div>
</nav>
<div class="sidebar-footer">
<div v-show="!collapsed" class="user-info">
<div v-show="showLabels" class="user-info">
<div class="user-avatar">
{{ authStore.user?.email?.charAt(0).toUpperCase() }}
</div>
@@ -79,7 +79,6 @@ import {
User,
Settings2,
Users,
ClipboardCheck,
Sliders,
BarChart3,
ScrollText,
@@ -96,6 +95,9 @@ const router = useRouter()
const { collapsed, mobileOpen, toggle, closeMobile } = useSidebar()
const { theme, resolvedTheme, toggleTheme } = useTheme()
// On mobile, always show labels when sidebar is open (even if collapsed on desktop)
const showLabels = computed(() => !collapsed.value || mobileOpen.value)
const themeTitle = computed(() => {
if (theme.value === 'light') return 'Switch to dark mode'
if (theme.value === 'dark') return 'Switch to auto mode'
@@ -105,14 +107,13 @@ const themeTitle = computed(() => {
const mainNav = [
{ to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
{ to: '/spaces', icon: Building2, label: 'Spaces' },
{ to: '/my-bookings', icon: CalendarDays, label: 'My Bookings' },
{ to: '/history', icon: CalendarDays, label: 'History' },
{ to: '/profile', icon: User, label: 'Profile' },
]
const adminNav = [
{ to: '/admin', icon: Settings2, label: 'Spaces Admin' },
{ to: '/users', icon: Users, label: 'Users' },
{ to: '/admin/pending', icon: ClipboardCheck, label: 'Pending' },
{ to: '/admin/settings', icon: Sliders, label: 'Settings' },
{ to: '/admin/reports', icon: BarChart3, label: 'Reports' },
{ to: '/admin/audit-log', icon: ScrollText, label: 'Audit Log' },
@@ -123,6 +124,13 @@ const isActive = (path: string) => {
return route.path.startsWith(path)
}
const handleHeaderClick = () => {
// Only toggle on desktop (≥768px)
if (window.innerWidth >= 768) {
toggle()
}
}
const handleLogout = () => {
authStore.logout()
router.push('/login')
@@ -166,6 +174,12 @@ const handleLogout = () => {
padding: 1.25rem 1.25rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
min-height: 60px;
cursor: pointer;
transition: background var(--transition-fast);
}
.sidebar-header:hover {
background: var(--sidebar-hover-bg);
}
.sidebar-logo-icon {
@@ -323,6 +337,14 @@ const handleLogout = () => {
width: var(--sidebar-width);
}
.sidebar-header {
cursor: default;
}
.sidebar-header:hover {
background: transparent;
}
.desktop-only {
display: none;
}