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

@@ -3,57 +3,59 @@
<div class="page-header">
<h2>Admin Dashboard - Space Management</h2>
<button class="btn btn-primary" @click="openCreateModal">
<Plus :size="16" />
Create New Space
</button>
</div>
<!-- Spaces List -->
<div class="card">
<h3>All Spaces</h3>
<CollapsibleSection title="All Spaces" :icon="Building2">
<div v-if="loadingSpaces" class="loading">Loading spaces...</div>
<div v-else-if="spaces.length === 0" class="empty">
No spaces created yet. Create one above!
</div>
<table v-else class="spaces-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Capacity</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="space in spaces" :key="space.id">
<td>{{ space.name }}</td>
<td>{{ space.type === 'sala' ? 'Sala' : 'Birou' }}</td>
<td>{{ space.capacity }}</td>
<td>
<span :class="['badge', space.is_active ? 'badge-active' : 'badge-inactive']">
{{ space.is_active ? 'Active' : 'Inactive' }}
</span>
</td>
<td class="actions">
<button
class="btn btn-sm btn-secondary"
@click="startEdit(space)"
:disabled="loading"
>
Edit
</button>
<button
:class="['btn', 'btn-sm', space.is_active ? 'btn-warning' : 'btn-success']"
@click="toggleStatus(space)"
:disabled="loading"
>
{{ space.is_active ? 'Deactivate' : 'Activate' }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div v-else class="table-responsive">
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Capacity</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="space in spaces" :key="space.id">
<td>{{ space.name }}</td>
<td>{{ space.type === 'sala' ? 'Sala' : 'Birou' }}</td>
<td>{{ space.capacity }}</td>
<td>
<span :class="['badge', space.is_active ? 'badge-active' : 'badge-inactive']">
{{ space.is_active ? 'Active' : 'Inactive' }}
</span>
</td>
<td class="actions">
<button
class="btn btn-sm btn-secondary"
@click="startEdit(space)"
:disabled="loading"
>
Edit
</button>
<button
:class="['btn', 'btn-sm', space.is_active ? 'btn-warning' : 'btn-success']"
@click="toggleStatus(space)"
:disabled="loading"
>
{{ space.is_active ? 'Deactivate' : 'Activate' }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</CollapsibleSection>
<!-- Create/Edit Space Modal -->
<div v-if="showModal" class="modal" @click.self="closeModal">
@@ -179,6 +181,8 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { spacesApi, handleApiError } from '@/services/api'
import CollapsibleSection from '@/components/CollapsibleSection.vue'
import { Building2, Plus } from 'lucide-vue-next'
import type { Space } from '@/types'
const spaces = ref<Space[]>([])
@@ -305,17 +309,18 @@ onMounted(() => {
</script>
<style scoped>
.admin {
max-width: 1200px;
margin: 0 auto;
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.card {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.page-header h2 {
margin: 0;
color: var(--color-text-primary);
}
.space-form {
@@ -328,20 +333,20 @@ onMounted(() => {
margin-top: 16px;
margin-bottom: 8px;
padding-top: 16px;
border-top: 1px solid #e5e7eb;
border-top: 1px solid var(--color-border);
}
.form-section-header h4 {
margin: 0 0 4px 0;
font-size: 16px;
font-weight: 600;
color: #1f2937;
color: var(--color-text-primary);
}
.help-text {
margin: 0;
font-size: 13px;
color: #6b7280;
color: var(--color-text-secondary);
}
.form-row {
@@ -364,7 +369,7 @@ onMounted(() => {
.form-group label {
font-weight: 500;
color: #374151;
color: var(--color-text-primary);
font-size: 14px;
}
@@ -372,17 +377,19 @@ onMounted(() => {
.form-group select,
.form-group textarea {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 4px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
font-size: 14px;
background: var(--color-surface);
color: var(--color-text-primary);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
border-color: var(--color-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 15%, transparent);
}
.form-actions {
@@ -392,13 +399,16 @@ onMounted(() => {
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 20px;
border: none;
border-radius: 4px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
transition: all var(--transition-fast);
}
.btn:disabled {
@@ -407,39 +417,39 @@ onMounted(() => {
}
.btn-primary {
background: #3b82f6;
background: var(--color-accent);
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #2563eb;
background: var(--color-accent-hover);
}
.btn-secondary {
background: #6b7280;
color: white;
background: var(--color-bg-tertiary);
color: var(--color-text-primary);
}
.btn-secondary:hover:not(:disabled) {
background: #4b5563;
background: var(--color-border);
}
.btn-success {
background: #10b981;
background: var(--color-success);
color: white;
}
.btn-success:hover:not(:disabled) {
background: #059669;
background: color-mix(in srgb, var(--color-success) 85%, black);
}
.btn-warning {
background: #f59e0b;
background: var(--color-warning);
color: white;
}
.btn-warning:hover:not(:disabled) {
background: #d97706;
background: color-mix(in srgb, var(--color-warning) 85%, black);
}
.btn-sm {
@@ -449,53 +459,58 @@ onMounted(() => {
.error {
padding: 12px;
background: #fee2e2;
color: #991b1b;
border-radius: 4px;
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
color: var(--color-danger);
border-radius: var(--radius-sm);
margin-top: 12px;
}
.success {
padding: 12px;
background: #d1fae5;
color: #065f46;
border-radius: 4px;
background: color-mix(in srgb, var(--color-success) 10%, transparent);
color: var(--color-success);
border-radius: var(--radius-sm);
margin-top: 12px;
}
.loading {
text-align: center;
color: #6b7280;
color: var(--color-text-secondary);
padding: 24px;
}
.empty {
text-align: center;
color: #9ca3af;
color: var(--color-text-muted);
padding: 24px;
}
.spaces-table {
.table-responsive {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.spaces-table th {
.data-table th {
text-align: left;
padding: 12px;
background: #f9fafb;
background: var(--color-bg-secondary);
font-weight: 600;
color: #374151;
border-bottom: 2px solid #e5e7eb;
color: var(--color-text-primary);
border-bottom: 2px solid var(--color-border);
}
.spaces-table td {
.data-table td {
padding: 12px;
border-bottom: 1px solid #e5e7eb;
border-bottom: 1px solid var(--color-border);
color: var(--color-text-primary);
}
.spaces-table tr:hover {
background: #f9fafb;
.data-table tr:hover {
background: var(--color-surface-hover);
}
.badge {
@@ -507,13 +522,13 @@ onMounted(() => {
}
.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);
}
.actions {
@@ -521,19 +536,6 @@ onMounted(() => {
gap: 8px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.page-header h2 {
margin: 0;
}
.modal {
position: fixed;
top: 0;
@@ -548,18 +550,30 @@ 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 {
margin-top: 0;
margin-bottom: 20px;
color: var(--color-text-primary);
}
@media (max-width: 768px) {
.page-header {
flex-direction: column;
align-items: stretch;
}
.actions {
flex-direction: column;
}
}
</style>