import { defineStore } from 'pinia' import { ref } from 'vue' import type { TimeSlotWithBookingStatus, BookingWithDetails, BookingWithUser, BookingStatusHistory, CreateBookingDto, TeachingScheduleSlot, } from '@mp-pilates/shared' import { get, post, put } from '../utils/request' import { useBodyPortraitStore } from './body-portrait' /** Server paginated responses use `data` field, not `items` from the shared type */ interface ServerPaginatedResult { readonly data: readonly T[] readonly total: number readonly page: number readonly limit: number } export const useBookingStore = defineStore('booking', () => { const slots = ref([]) const myBookings = ref([]) const upcomingBookings = ref([]) const teachingSchedule = ref([]) const loadingSlots = ref(false) const loadingBookings = ref(false) const loadingTeachingSchedule = ref(false) async function fetchSlots(date: string) { loadingSlots.value = true try { slots.value = await get('/time-slot/available', { date }) } catch (err) { console.error('Fetch slots failed:', err) slots.value = [] } finally { loadingSlots.value = false } } async function createBooking(dto: CreateBookingDto) { const portraitStore = useBodyPortraitStore() const originAssessmentId = dto.originAssessmentId || (portraitStore.claimed ? portraitStore.assessmentId : undefined) const result = await post('/booking', { ...dto, ...(originAssessmentId ? { originAssessmentId } : {}), } as unknown as Record) return result } /** * Replace a booking in `myBookings` by id. Preserves immutability: always * returns a new array reference so Vue's computed/watchers pick up the change. * If the booking isn't in the list (e.g. paginated out), leaves state untouched. */ function replaceBooking(updated: BookingWithDetails) { const idx = myBookings.value.findIndex((b) => b.id === updated.id) if (idx === -1) return const next = myBookings.value.slice() next[idx] = updated myBookings.value = next } async function cancelBooking(bookingId: string) { const result = await put(`/booking/${bookingId}/cancel`) replaceBooking(result) return result } async function fetchMyBookings(status?: string, opts: { silent?: boolean } = {}) { if (!opts.silent) loadingBookings.value = true try { const params: Record = status ? { status } : {} const paginated = await get>('/booking/my', params) myBookings.value = Array.isArray(paginated.data) ? paginated.data : [] } catch (err) { console.error('Fetch bookings failed:', err) myBookings.value = [] } finally { if (!opts.silent) loadingBookings.value = false } } async function fetchUpcomingBookings() { try { const result = await get('/booking/my/upcoming') upcomingBookings.value = Array.isArray(result) ? result : [] } catch (err) { console.error('Fetch upcoming bookings failed:', err) upcomingBookings.value = [] } } async function fetchTeachingSchedule(date: string) { loadingTeachingSchedule.value = true try { const result = await get('/admin/teaching-schedule', { date }) teachingSchedule.value = Array.isArray(result) ? result : [] return teachingSchedule.value } catch (err) { console.error('Fetch teaching schedule failed:', err) teachingSchedule.value = [] throw err } finally { loadingTeachingSchedule.value = false } } // ─── Admin methods ────────────────────────────────────────────────────── async function fetchAllAdminBookings( page = 1, limit = 20, status?: string, ): Promise> { const params: Record = { page, limit } if (status) params.status = status const paginated = await get>('/admin/bookings', params) return paginated } async function confirmBooking(bookingId: string, remark?: string) { const result = await put(`/booking/${bookingId}/confirm`, { remark, }) replaceBooking(result) return result } async function completeBooking(bookingId: string, remark?: string) { const result = await put(`/booking/${bookingId}/complete`, { remark, }) replaceBooking(result) return result } async function markNoShow(bookingId: string, remark?: string) { const result = await put(`/booking/${bookingId}/noshow`, { remark, }) replaceBooking(result) return result } async function fetchBookingHistory(bookingId: string): Promise { const result = await get(`/booking/${bookingId}/history`) return result } async function fetchBookingById(bookingId: string) { const result = await get(`/booking/${bookingId}`) return result } async function fetchSlotById(slotId: string) { const result = await get(`/time-slot/${slotId}`) return result } return { slots, myBookings, upcomingBookings, teachingSchedule, loadingSlots, loadingBookings, loadingTeachingSchedule, fetchSlots, createBooking, cancelBooking, fetchMyBookings, fetchUpcomingBookings, fetchTeachingSchedule, fetchAllAdminBookings, confirmBooking, completeBooking, markNoShow, fetchBookingHistory, fetchSlotById, fetchBookingById, replaceBooking, } })