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:
@@ -3,9 +3,7 @@
|
||||
<h2>Global Booking Settings</h2>
|
||||
|
||||
<!-- Settings Form -->
|
||||
<div class="card">
|
||||
<h3>Booking Rules Configuration</h3>
|
||||
|
||||
<CollapsibleSection title="Booking Rules Configuration" :icon="Sliders">
|
||||
<div v-if="loadingSettings" class="loading">Loading settings...</div>
|
||||
|
||||
<form v-else @submit.prevent="handleSubmit" class="settings-form">
|
||||
@@ -108,25 +106,26 @@
|
||||
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
<div v-if="success" class="success">{{ success }}</div>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<!-- Info Card -->
|
||||
<div class="card info-card">
|
||||
<h4>About These Settings</h4>
|
||||
<ul>
|
||||
<CollapsibleSection title="About These Settings" :icon="Info">
|
||||
<ul class="info-list">
|
||||
<li><strong>Duration:</strong> Controls minimum and maximum booking length</li>
|
||||
<li><strong>Working Hours:</strong> Bookings outside these hours will be rejected</li>
|
||||
<li><strong>Max Bookings:</strong> Limits how many bookings a user can make per day</li>
|
||||
<li><strong>Cancel Policy:</strong> Users cannot cancel bookings too close to start time</li>
|
||||
</ul>
|
||||
<p class="note">These rules apply to all new booking requests.</p>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { settingsApi, handleApiError } from '@/services/api'
|
||||
import CollapsibleSection from '@/components/CollapsibleSection.vue'
|
||||
import { Sliders, Info } from 'lucide-vue-next'
|
||||
import type { Settings } from '@/types'
|
||||
|
||||
const loadingSettings = ref(true)
|
||||
@@ -163,7 +162,6 @@ const loadSettings = async () => {
|
||||
}
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
// Validate all fields are positive
|
||||
if (
|
||||
formData.value.min_duration_minutes <= 0 ||
|
||||
formData.value.max_duration_minutes <= 0 ||
|
||||
@@ -176,19 +174,16 @@ const validateForm = (): boolean => {
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate min < max duration
|
||||
if (formData.value.min_duration_minutes >= formData.value.max_duration_minutes) {
|
||||
error.value = 'Minimum duration must be less than maximum duration'
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate working hours start < end
|
||||
if (formData.value.working_hours_start >= formData.value.working_hours_end) {
|
||||
error.value = 'Working hours start must be less than working hours end'
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate working hours are within 0-23 for start and 1-24 for end
|
||||
if (formData.value.working_hours_start < 0 || formData.value.working_hours_start > 23) {
|
||||
error.value = 'Working hours start must be between 0 and 23'
|
||||
return false
|
||||
@@ -206,7 +201,6 @@ const handleSubmit = async () => {
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
|
||||
// Client-side validation
|
||||
if (!validateForm()) {
|
||||
return
|
||||
}
|
||||
@@ -239,21 +233,7 @@ onMounted(() => {
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h3, h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #444;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
@@ -276,18 +256,27 @@ h3, h4 {
|
||||
|
||||
.form-group label {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 1rem;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 15%, transparent);
|
||||
}
|
||||
|
||||
.form-group small {
|
||||
color: #666;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
@@ -300,19 +289,19 @@ h3, h4 {
|
||||
.btn {
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #4caf50;
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: #45a049;
|
||||
background: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
@@ -323,46 +312,48 @@ h3, h4 {
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 0.75rem;
|
||||
background-color: #fee;
|
||||
border: 1px solid #fcc;
|
||||
border-radius: 4px;
|
||||
color: #c33;
|
||||
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-danger);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.success {
|
||||
padding: 0.75rem;
|
||||
background-color: #efe;
|
||||
border: 1px solid #cfc;
|
||||
border-radius: 4px;
|
||||
color: #3c3;
|
||||
background: color-mix(in srgb, var(--color-success) 10%, transparent);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-success);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.info-card ul {
|
||||
.info-list {
|
||||
margin: 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.info-card li {
|
||||
.info-list li {
|
||||
margin-bottom: 0.5rem;
|
||||
color: #555;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.info-list strong {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.note {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.collapsible-section + .collapsible-section {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
Reference in New Issue
Block a user