fix(dashboard): sidebar theming, calendar reactivity, and booking filters
Fix multiple dashboard and UI issues: Frontend fixes: - Fix sidebar remaining dark on light theme (add proper light/dark CSS variables) - Fix DashboardCalendar blank/not showing events (use watch + calendar API instead of computed options) - Fix upcoming bookings to include active and recent past (last 7 days) bookings - Improve sidebar collapsed state UX (stack footer buttons vertically, full width) Details: - theme.css: Add light sidebar colors (white bg) for :root, keep dark colors for [data-theme="dark"] - DashboardCalendar: Add watch on events, use calendarRef to update events via removeAllEvents/addEventSource - Dashboard: Change upcoming filter from "startDate >= now" to "endDate >= 7 days ago" - AppSidebar: Stack footer-actions vertically when collapsed for better visibility Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,10 +35,10 @@
|
|||||||
/* Sidebar */
|
/* Sidebar */
|
||||||
--sidebar-width: 260px;
|
--sidebar-width: 260px;
|
||||||
--sidebar-collapsed-width: 68px;
|
--sidebar-collapsed-width: 68px;
|
||||||
--sidebar-bg: #1a1a2e;
|
--sidebar-bg: #ffffff;
|
||||||
--sidebar-text: #a1a1b5;
|
--sidebar-text: #6b7280;
|
||||||
--sidebar-text-active: #ffffff;
|
--sidebar-text-active: #1a1a2e;
|
||||||
--sidebar-hover-bg: rgba(255, 255, 255, 0.08);
|
--sidebar-hover-bg: #f3f4f6;
|
||||||
|
|
||||||
/* Spacing */
|
/* Spacing */
|
||||||
--radius-sm: 6px;
|
--radius-sm: 6px;
|
||||||
@@ -66,4 +66,10 @@
|
|||||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.3);
|
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.4);
|
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
/* Sidebar - Dark theme */
|
||||||
|
--sidebar-bg: #1a1a2e;
|
||||||
|
--sidebar-text: #a1a1b5;
|
||||||
|
--sidebar-text-active: #ffffff;
|
||||||
|
--sidebar-hover-bg: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,16 @@ const handleLogout = () => {
|
|||||||
width: var(--sidebar-collapsed-width);
|
width: var(--sidebar-collapsed-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .footer-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.collapsed .footer-btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
<div class="dashboard-calendar">
|
<div class="dashboard-calendar">
|
||||||
<div v-if="error" class="error">{{ error }}</div>
|
<div v-if="error" class="error">{{ error }}</div>
|
||||||
<div v-if="loading" class="loading">Loading calendar...</div>
|
<div v-if="loading" class="loading">Loading calendar...</div>
|
||||||
<FullCalendar v-show="!loading" :options="calendarOptions" />
|
<FullCalendar ref="calendarRef" v-show="!loading" :options="calendarOptions" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch, nextTick } from 'vue'
|
||||||
import FullCalendar from '@fullcalendar/vue3'
|
import FullCalendar from '@fullcalendar/vue3'
|
||||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||||
import timeGridPlugin from '@fullcalendar/timegrid'
|
import timeGridPlugin from '@fullcalendar/timegrid'
|
||||||
import interactionPlugin from '@fullcalendar/interaction'
|
import interactionPlugin from '@fullcalendar/interaction'
|
||||||
import type { CalendarOptions, EventInput, DatesSetArg } from '@fullcalendar/core'
|
import type { CalendarOptions, EventInput, DatesSetArg, CalendarApi } from '@fullcalendar/core'
|
||||||
import { bookingsApi, handleApiError } from '@/services/api'
|
import { bookingsApi, handleApiError } from '@/services/api'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import type { Booking } from '@/types'
|
import type { Booking } from '@/types'
|
||||||
@@ -23,6 +23,7 @@ const bookings = ref<Booking[]>([])
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const initialLoad = ref(true)
|
const initialLoad = ref(true)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
|
const calendarRef = ref<InstanceType<typeof FullCalendar> | null>(null)
|
||||||
|
|
||||||
const STATUS_COLORS: Record<string, string> = {
|
const STATUS_COLORS: Record<string, string> = {
|
||||||
pending: '#FFA500',
|
pending: '#FFA500',
|
||||||
@@ -46,6 +47,17 @@ const events = computed<EventInput[]>(() => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Watch events and update FullCalendar
|
||||||
|
watch(events, (newEvents) => {
|
||||||
|
nextTick(() => {
|
||||||
|
const calendarApi = calendarRef.value?.getApi()
|
||||||
|
if (calendarApi) {
|
||||||
|
calendarApi.removeAllEvents()
|
||||||
|
calendarApi.addEventSource(newEvents)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
let currentStart: Date | null = null
|
let currentStart: Date | null = null
|
||||||
let currentEnd: Date | null = null
|
let currentEnd: Date | null = null
|
||||||
|
|
||||||
@@ -72,7 +84,7 @@ const handleDatesSet = (arg: DatesSetArg) => {
|
|||||||
loadBookings(arg.start, arg.end)
|
loadBookings(arg.start, arg.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarOptions = computed<CalendarOptions>(() => ({
|
const calendarOptions: CalendarOptions = {
|
||||||
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
|
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
|
||||||
initialView: 'dayGridMonth',
|
initialView: 'dayGridMonth',
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
@@ -82,7 +94,7 @@ const calendarOptions = computed<CalendarOptions>(() => ({
|
|||||||
},
|
},
|
||||||
timeZone: userTimezone.value,
|
timeZone: userTimezone.value,
|
||||||
firstDay: 1,
|
firstDay: 1,
|
||||||
events: events.value,
|
events: [],
|
||||||
datesSet: handleDatesSet,
|
datesSet: handleDatesSet,
|
||||||
editable: false,
|
editable: false,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
@@ -98,7 +110,7 @@ const calendarOptions = computed<CalendarOptions>(() => ({
|
|||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
hour12: false
|
hour12: false
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
if (currentStart && currentEnd) {
|
if (currentStart && currentEnd) {
|
||||||
|
|||||||
@@ -181,13 +181,18 @@ const adminStats = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get upcoming bookings (approved or pending, sorted by start time)
|
// Get upcoming bookings (active, future, and recent past - last 7 days)
|
||||||
const upcomingBookings = computed(() => {
|
const upcomingBookings = computed(() => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||||
|
|
||||||
return myBookings.value
|
return myBookings.value
|
||||||
.filter((b) => {
|
.filter((b) => {
|
||||||
const startDate = new Date(ensureUTC(b.start_datetime))
|
if (b.status !== 'approved' && b.status !== 'pending') return false
|
||||||
return startDate >= now && (b.status === 'approved' || b.status === 'pending')
|
|
||||||
|
const endDate = new Date(ensureUTC(b.end_datetime))
|
||||||
|
// Include if not ended yet OR ended within last 7 days
|
||||||
|
return endDate >= sevenDaysAgo
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return new Date(ensureUTC(a.start_datetime)).getTime() - new Date(ensureUTC(b.start_datetime)).getTime()
|
return new Date(ensureUTC(a.start_datetime)).getTime() - new Date(ensureUTC(b.start_datetime)).getTime()
|
||||||
|
|||||||
Reference in New Issue
Block a user