feat(dashboard): redesign with active bookings, calendar, and compact stats

Major Dashboard improvements focusing on active reservations and calendar view:

Frontend changes:
- Add ActiveBookings component showing in-progress bookings with progress bars
- Add DashboardCalendar component with read-only calendar view of all user bookings
- Refactor Dashboard layout: active bookings → stats grid → calendar → activity
- Remove redundant Quick Actions and Available Spaces sections
- Make Quick Stats compact (36px icons, 20px font) and clickable (router-link)
- Add datetime utility functions (isBookingActive, getBookingProgress, formatRemainingTime)
- Fix MyBookings to read status query parameter from URL
- Auto-refresh active bookings every 60s with proper cleanup

Backend changes:
- Add GET /api/bookings/my/calendar endpoint with date range filtering
- Fix Google Calendar sync in reschedule_booking and admin_update_booking
- Add Google OAuth environment variables to .env.example

Design:
- Dark mode compatible with CSS variables throughout
- Mobile responsive (768px breakpoint, 2-column stats grid)
- CollapsibleSection pattern for all dashboard sections
- Progress bars with accent colors for active bookings

Performance:
- Optimized API calls (calendar uses date range filtering)
- Remove duplicate calendar data loading on mount
- Computed property caching for stats and filtered bookings
- Memory leak prevention (setInterval cleanup on unmount)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-12 10:21:32 +00:00
parent 72f46b1062
commit 28685d8254
8 changed files with 560 additions and 251 deletions

View File

@@ -131,3 +131,39 @@ export const isoToLocalDateTime = (isoDateTime: string, timezone: string = 'UTC'
// Format as YYYY-MM-DDTHH:mm for datetime-local input
return `${year}-${month}-${day}T${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`
}
/**
* Check if a booking is currently active (in progress).
*/
export const isBookingActive = (startDatetime: string, endDatetime: string): boolean => {
const now = new Date()
const start = new Date(ensureUTC(startDatetime))
const end = new Date(ensureUTC(endDatetime))
return start <= now && end >= now
}
/**
* Calculate progress percentage for an active booking.
*/
export const getBookingProgress = (startDatetime: string, endDatetime: string): number => {
const now = new Date()
const start = new Date(ensureUTC(startDatetime))
const end = new Date(ensureUTC(endDatetime))
const total = end.getTime() - start.getTime()
const elapsed = now.getTime() - start.getTime()
return Math.min(100, Math.max(0, (elapsed / total) * 100))
}
/**
* Format remaining time for an active booking.
*/
export const formatRemainingTime = (endDatetime: string): string => {
const now = new Date()
const end = new Date(ensureUTC(endDatetime))
const remaining = end.getTime() - now.getTime()
if (remaining <= 0) return 'Ended'
const hours = Math.floor(remaining / 3600000)
const minutes = Math.floor((remaining % 3600000) / 60000)
if (hours > 0) return `${hours}h ${minutes}m remaining`
return `${minutes}m remaining`
}