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:
21
frontend/src/composables/useLocalStorage.ts
Normal file
21
frontend/src/composables/useLocalStorage.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ref, watch, type Ref } from 'vue'
|
||||
|
||||
export function useLocalStorage<T>(key: string, defaultValue: T): Ref<T> {
|
||||
let initial = defaultValue
|
||||
try {
|
||||
const stored = localStorage.getItem(key)
|
||||
if (stored !== null) {
|
||||
initial = JSON.parse(stored)
|
||||
}
|
||||
} catch {
|
||||
// Invalid JSON in storage, use default
|
||||
}
|
||||
|
||||
const value = ref<T>(initial) as Ref<T>
|
||||
|
||||
watch(value, (newVal) => {
|
||||
localStorage.setItem(key, JSON.stringify(newVal))
|
||||
}, { deep: true })
|
||||
|
||||
return value
|
||||
}
|
||||
28
frontend/src/composables/useMediaQuery.ts
Normal file
28
frontend/src/composables/useMediaQuery.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export function useMediaQuery(query: string) {
|
||||
const matches = ref(false)
|
||||
let mediaQuery: MediaQueryList | null = null
|
||||
|
||||
const update = () => {
|
||||
if (mediaQuery) {
|
||||
matches.value = mediaQuery.matches
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
mediaQuery = window.matchMedia(query)
|
||||
matches.value = mediaQuery.matches
|
||||
mediaQuery.addEventListener('change', update)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
mediaQuery?.removeEventListener('change', update)
|
||||
})
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
export function useIsMobile() {
|
||||
return useMediaQuery('(max-width: 768px)')
|
||||
}
|
||||
Reference in New Issue
Block a user