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,5 +1,6 @@
|
||||
<template>
|
||||
<div class="user-profile">
|
||||
<Breadcrumb :items="breadcrumbItems" />
|
||||
<h2>User Profile</h2>
|
||||
|
||||
<!-- Profile Information Card -->
|
||||
@@ -74,9 +75,18 @@
|
||||
Your approved bookings will automatically sync to your Google Calendar.
|
||||
</p>
|
||||
|
||||
<button @click="disconnectGoogle" class="btn btn-danger" :disabled="disconnecting">
|
||||
{{ disconnecting ? 'Disconnecting...' : 'Disconnect Google Calendar' }}
|
||||
</button>
|
||||
<div class="button-group">
|
||||
<button @click="syncGoogle" class="btn btn-primary" :disabled="syncing">
|
||||
{{ syncing ? 'Syncing...' : 'Sync Now' }}
|
||||
</button>
|
||||
<button @click="disconnectGoogle" class="btn btn-danger" :disabled="disconnecting">
|
||||
{{ disconnecting ? 'Disconnecting...' : 'Disconnect' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="syncResult" class="sync-result">
|
||||
Synced {{ syncResult.synced }} bookings ({{ syncResult.created }} created, {{ syncResult.updated }} updated<span v-if="syncResult.failed">, {{ syncResult.failed }} failed</span>)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="google-disconnected">
|
||||
@@ -125,10 +135,16 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { usersApi, googleCalendarApi, handleApiError } from '@/services/api'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { formatDateTime as formatDateTimeUtil } from '@/utils/datetime'
|
||||
import Breadcrumb from '@/components/Breadcrumb.vue'
|
||||
import CollapsibleSection from '@/components/CollapsibleSection.vue'
|
||||
import { User as UserIcon, Globe, CalendarDays, CheckCircle, Info } from 'lucide-vue-next'
|
||||
import type { User } from '@/types'
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Dashboard', to: '/dashboard' },
|
||||
{ label: 'Profile' }
|
||||
]
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const userTimezone = computed(() => authStore.user?.timezone || 'UTC')
|
||||
|
||||
@@ -136,6 +152,8 @@ const user = ref<User | null>(null)
|
||||
const loadingGoogleStatus = ref(true)
|
||||
const connecting = ref(false)
|
||||
const disconnecting = ref(false)
|
||||
const syncing = ref(false)
|
||||
const syncResult = ref<{ synced: number; created: number; updated: number; failed: number } | null>(null)
|
||||
const error = ref('')
|
||||
const success = ref('')
|
||||
|
||||
@@ -296,6 +314,27 @@ const disconnectGoogle = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const syncGoogle = async () => {
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
syncResult.value = null
|
||||
syncing.value = true
|
||||
|
||||
try {
|
||||
const result = await googleCalendarApi.sync()
|
||||
syncResult.value = result
|
||||
success.value = 'Calendar synced successfully!'
|
||||
setTimeout(() => {
|
||||
success.value = ''
|
||||
syncResult.value = null
|
||||
}, 5000)
|
||||
} catch (err) {
|
||||
error.value = handleApiError(err)
|
||||
} finally {
|
||||
syncing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
return formatDateTimeUtil(dateString, userTimezone.value)
|
||||
}
|
||||
@@ -420,6 +459,20 @@ h2 {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sync-result {
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: color-mix(in srgb, var(--color-info) 10%, transparent);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-info);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 0.75rem;
|
||||
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
||||
|
||||
Reference in New Issue
Block a user