feat: complete UI redesign with dark mode, sidebar navigation, and modern design system

Implemented comprehensive UI overhaul with three-layer architecture:

Layer 1 - Theme System:
- CSS variables for light/dark themes (theme.css)
- Theme composable with light/dark/auto mode (useTheme.ts)
- Sidebar state management composable (useSidebar.ts)
- Refactored main.css to use CSS variables throughout

Layer 2 - Core Components:
- AppSidebar with collapsible navigation (desktop) and overlay (mobile)
- CollapsibleSection reusable component for expandable cards
- Restructured App.vue with new sidebar layout
- Integrated Lucide icons library (lucide-vue-next)

Layer 3 - Views & Components:
- Updated all 14 views with CSS variables and responsive design
- Replaced inline SVG with Lucide icon components
- Added collapsible sections to Dashboard, Admin pages, UserProfile
- Updated 3 shared components (BookingForm, SpaceCalendar, AttachmentsList)

Features:
- Dark/light/auto theme with persistent preference
- Collapsible sidebar (icons-only on desktop, overlay on mobile)
- Consistent color palette using CSS variables
- Full responsive design across all pages
- Modern minimalist aesthetic with Indigo accent color

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-11 21:27:05 +00:00
parent 9c2846cf00
commit 0bf3e6a7e2
28 changed files with 1960 additions and 1641 deletions

View File

@@ -33,14 +33,7 @@
<div class="space-meta">
<span class="badge badge-type">{{ formatType(space.type) }}</span>
<span class="badge badge-capacity">
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
<Users :size="16" />
Capacity: {{ space.capacity }}
</span>
<span :class="['badge', space.is_active ? 'badge-active' : 'badge-inactive']">
@@ -53,14 +46,7 @@
:disabled="!space.is_active"
@click="handleReserve"
>
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
<Plus :size="18" />
{{ showBookingForm ? 'Cancel Reservation' : 'Reserve Space' }}
</button>
</div>
@@ -99,6 +85,7 @@ import { useRoute } from 'vue-router'
import { spacesApi, handleApiError } from '@/services/api'
import SpaceCalendar from '@/components/SpaceCalendar.vue'
import BookingForm from '@/components/BookingForm.vue'
import { Users, Plus } from 'lucide-vue-next'
import type { Space } from '@/types'
const route = useRoute()
@@ -132,7 +119,6 @@ const loadSpace = async () => {
}
// Fetch all spaces and filter by ID
// (Could be optimized with a dedicated GET /api/spaces/:id endpoint in the future)
const spaces = await spacesApi.list()
const foundSpace = spaces.find((s) => s.id === spaceId)
@@ -170,11 +156,6 @@ onMounted(() => {
</script>
<style scoped>
.space-detail {
max-width: 1200px;
margin: 0 auto;
}
/* Breadcrumbs */
.breadcrumbs {
display: flex;
@@ -182,26 +163,26 @@ onMounted(() => {
gap: 8px;
margin-bottom: 24px;
font-size: 14px;
color: #6b7280;
color: var(--color-text-secondary);
}
.breadcrumbs a {
color: #3b82f6;
color: var(--color-accent);
text-decoration: none;
transition: color 0.2s;
transition: color var(--transition-fast);
}
.breadcrumbs a:hover {
color: #2563eb;
color: var(--color-accent-hover);
text-decoration: underline;
}
.breadcrumbs .separator {
color: #9ca3af;
color: var(--color-text-muted);
}
.breadcrumbs .current {
color: #374151;
color: var(--color-text-primary);
font-weight: 500;
}
@@ -212,14 +193,14 @@ onMounted(() => {
align-items: center;
justify-content: center;
padding: 80px 20px;
color: #6b7280;
color: var(--color-text-secondary);
}
.spinner {
width: 48px;
height: 48px;
border: 4px solid #e5e7eb;
border-top-color: #3b82f6;
border: 4px solid var(--color-border);
border-top-color: var(--color-accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 16px;
@@ -239,21 +220,22 @@ onMounted(() => {
}
.error-card {
background: white;
border-radius: 8px;
background: var(--color-surface);
border-radius: var(--radius-md);
padding: 40px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-border);
text-align: center;
max-width: 500px;
}
.error-card h3 {
color: #991b1b;
color: var(--color-danger);
margin-bottom: 12px;
}
.error-card p {
color: #6b7280;
color: var(--color-text-secondary);
margin-bottom: 24px;
}
@@ -281,7 +263,7 @@ onMounted(() => {
.header-info h1 {
font-size: 32px;
font-weight: 700;
color: #111827;
color: var(--color-text-primary);
margin-bottom: 16px;
}
@@ -303,42 +285,37 @@ onMounted(() => {
}
.badge-type {
background: #dbeafe;
color: #1e40af;
background: var(--color-accent-light);
color: var(--color-accent);
}
.badge-capacity {
background: #f3f4f6;
color: #374151;
background: var(--color-bg-tertiary);
color: var(--color-text-primary);
}
.badge-active {
background: #d1fae5;
color: #065f46;
background: color-mix(in srgb, var(--color-success) 15%, transparent);
color: var(--color-success);
}
.badge-inactive {
background: #fee2e2;
color: #991b1b;
background: color-mix(in srgb, var(--color-danger) 15%, transparent);
color: var(--color-danger);
}
.icon {
width: 18px;
height: 18px;
}
/* Reserve Button */
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border: none;
border-radius: 8px;
border-radius: var(--radius-md);
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
transition: all var(--transition-fast);
white-space: nowrap;
}
@@ -348,14 +325,14 @@ onMounted(() => {
}
.btn-primary {
background: #3b82f6;
background: var(--color-accent);
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #2563eb;
background: var(--color-accent-hover);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
box-shadow: var(--shadow-md);
}
.btn-reserve {
@@ -365,33 +342,29 @@ onMounted(() => {
/* Cards */
.card {
background: white;
border-radius: 8px;
background: var(--color-surface);
border-radius: var(--radius-md);
padding: 24px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-border);
}
.card h3 {
font-size: 20px;
font-weight: 600;
color: #111827;
color: var(--color-text-primary);
margin-bottom: 12px;
}
/* Description Card */
.description-card p {
color: #4b5563;
color: var(--color-text-secondary);
line-height: 1.6;
}
/* Booking Card */
.booking-card h3 {
margin-bottom: 16px;
}
/* Calendar Card */
.calendar-subtitle {
color: #6b7280;
color: var(--color-text-secondary);
font-size: 14px;
margin-bottom: 20px;
}
@@ -411,14 +384,14 @@ onMounted(() => {
}
.modal-content {
background: white;
border-radius: 8px;
background: var(--color-surface);
border-radius: var(--radius-md);
padding: 24px;
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
box-shadow: var(--shadow-lg);
}
.modal-content h3 {