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:
@@ -1,13 +1,7 @@
|
||||
<template>
|
||||
<div class="space-detail">
|
||||
<!-- Breadcrumbs -->
|
||||
<nav class="breadcrumbs">
|
||||
<router-link to="/">Home</router-link>
|
||||
<span class="separator">/</span>
|
||||
<router-link to="/spaces">Spaces</router-link>
|
||||
<span class="separator">/</span>
|
||||
<span class="current">{{ space?.name || 'Loading...' }}</span>
|
||||
</nav>
|
||||
<Breadcrumb :items="breadcrumbItems" />
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="loading">
|
||||
@@ -41,14 +35,25 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary btn-reserve"
|
||||
:disabled="!space.is_active"
|
||||
@click="handleReserve"
|
||||
>
|
||||
<Plus :size="18" />
|
||||
{{ showBookingForm ? 'Cancel Reservation' : 'Reserve Space' }}
|
||||
</button>
|
||||
<div class="header-actions">
|
||||
<button
|
||||
class="btn btn-primary btn-reserve"
|
||||
:disabled="!space.is_active"
|
||||
@click="handleReserve"
|
||||
>
|
||||
<Plus :size="18" />
|
||||
{{ showBookingForm ? 'Cancel' : 'Reserve Space' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="isAdmin"
|
||||
class="btn btn-secondary btn-reserve"
|
||||
:disabled="!space.is_active"
|
||||
@click="showAdminBookingForm = true"
|
||||
>
|
||||
<UserPlus :size="18" />
|
||||
Book for User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
@@ -76,24 +81,48 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Booking Modal -->
|
||||
<div v-if="showAdminBookingForm && space" class="modal" @click.self="showAdminBookingForm = false">
|
||||
<div class="modal-content">
|
||||
<h3>Admin: Book for User</h3>
|
||||
<AdminBookingForm
|
||||
:space-id="space.id"
|
||||
@submit="handleAdminBookingSubmit"
|
||||
@cancel="showAdminBookingForm = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { spacesApi, handleApiError } from '@/services/api'
|
||||
import Breadcrumb from '@/components/Breadcrumb.vue'
|
||||
import SpaceCalendar from '@/components/SpaceCalendar.vue'
|
||||
import BookingForm from '@/components/BookingForm.vue'
|
||||
import { Users, Plus } from 'lucide-vue-next'
|
||||
import AdminBookingForm from '@/components/AdminBookingForm.vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { Users, Plus, UserPlus } from 'lucide-vue-next'
|
||||
import type { Space } from '@/types'
|
||||
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const isAdmin = computed(() => authStore.user?.role === 'admin')
|
||||
|
||||
const breadcrumbItems = computed(() => [
|
||||
{ label: 'Dashboard', to: '/dashboard' },
|
||||
{ label: 'Spaces', to: '/spaces' },
|
||||
{ label: space.value?.name || 'Loading...' }
|
||||
])
|
||||
|
||||
const space = ref<Space | null>(null)
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const showBookingForm = ref(false)
|
||||
const showAdminBookingForm = ref(false)
|
||||
const calendarRef = ref<InstanceType<typeof SpaceCalendar> | null>(null)
|
||||
|
||||
// Format space type for display
|
||||
@@ -150,42 +179,18 @@ const handleBookingSubmit = () => {
|
||||
calendarRef.value?.refresh()
|
||||
}
|
||||
|
||||
// Handle admin booking form submit
|
||||
const handleAdminBookingSubmit = () => {
|
||||
showAdminBookingForm.value = false
|
||||
calendarRef.value?.refresh()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSpace()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Breadcrumbs */
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 24px;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.breadcrumbs a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.breadcrumbs a:hover {
|
||||
color: var(--color-accent-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.breadcrumbs .separator {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.breadcrumbs .current {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.loading {
|
||||
display: flex;
|
||||
@@ -335,8 +340,14 @@ onMounted(() => {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-reserve {
|
||||
min-width: 180px;
|
||||
min-width: 160px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -413,31 +424,6 @@ onMounted(() => {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
:deep(.fc .fc-toolbar) {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
|
||||
:deep(.fc .fc-toolbar-chunk) {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:deep(.fc .fc-toolbar-title) {
|
||||
font-size: 1.2em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.fc .fc-button) {
|
||||
padding: 6px 10px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
:deep(.fc .fc-col-header-cell) {
|
||||
font-size: 0.75em;
|
||||
padding: 4px 2px;
|
||||
}
|
||||
/* Calendar mobile styles handled by SpaceCalendar component */
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user