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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user