Files
mp-pilates/packages/app/src/stores/admin.ts

344 lines
11 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref } from 'vue'
import { get, post, put, del } from '../utils/request'
import type {
TeachingAnalytics,
CardType,
CreateCardTypeDto,
UpdateCardTypeDto,
StudioConfig,
UpdateStudioConfigDto,
OrderWithDetails,
TimeSlot,
CreateManualSlotDto,
PaginatedData,
ScheduleSlotPreview,
PublishDaySlotsDto,
FlashSaleAdminItem,
CreateFlashSaleDto,
UpdateFlashSaleDto,
CreateStudioUploadCredentialDto,
StudioUploadCredential,
AdminMemberSummary,
AdminMemberDetail,
LessonSupplementRecord,
CreateLessonSupplementDto,
UpdateAdminMemberProfileDto,
AdminArrangeBookingDto,
MembershipWithCardType,
BookingWithDetails,
} from '@mp-pilates/shared'
interface LegacyPaginatedData<T> {
readonly data: readonly T[]
readonly total: number
readonly page: number
readonly limit: number
}
function normalizePaginatedData<T>(
result: PaginatedData<T> | LegacyPaginatedData<T>,
): PaginatedData<T> {
if ('items' in result) {
return {
items: [...result.items],
total: result.total,
page: result.page,
limit: result.limit,
}
}
return {
items: [...result.data],
total: result.total,
page: result.page,
limit: result.limit,
}
}
export interface AdminStats {
todayBookings: number
totalOrders: number
totalBookings: number
}
export type MemberSummary = AdminMemberSummary
export interface UserMembership {
userId: string
membership: {
id: string
cardTypeId: string
remainingTimes: number | null
startDate: string
expireDate: string
status: string
cardType: {
id: string
name: string
type: string
totalTimes: number | null
durationDays: number
}
} | null
}
export const useAdminStore = defineStore('admin', () => {
// ── Card types ───────────────────────────────────────────────────
const cardTypes = ref<CardType[]>([])
async function fetchCardTypes(): Promise<CardType[]> {
const data = await get<CardType[]>('/admin/card-types')
cardTypes.value = [...data].sort((a, b) => a.sortOrder - b.sortOrder)
return cardTypes.value
}
async function createCardType(dto: CreateCardTypeDto): Promise<CardType> {
const data = await post<CardType>('/admin/card-types', dto as unknown as Record<string, unknown>)
await fetchCardTypes()
return data
}
async function updateCardType(id: string, dto: UpdateCardTypeDto): Promise<CardType> {
const data = await put<CardType>(`/admin/card-types/${id}`, dto as unknown as Record<string, unknown>)
await fetchCardTypes()
return data
}
async function deleteCardType(id: string): Promise<{ deleted: boolean; deactivated: boolean }> {
const result = await del<{ deleted: boolean; deactivated: boolean }>(`/admin/card-types/${id}`)
await fetchCardTypes()
return result
}
// ── Studio config ────────────────────────────────────────────────
const studioConfig = ref<StudioConfig | null>(null)
async function fetchStudioConfig(): Promise<StudioConfig> {
const data = await get<StudioConfig>('/studio/info')
studioConfig.value = data
return data
}
async function saveStudioConfig(dto: UpdateStudioConfigDto): Promise<StudioConfig> {
const data = await put<StudioConfig>('/admin/studio/info', dto as unknown as Record<string, unknown>)
studioConfig.value = data
return data
}
async function createStudioUploadCredential(
dto: CreateStudioUploadCredentialDto,
): Promise<StudioUploadCredential> {
return post<StudioUploadCredential>(
'/admin/studio/upload-credentials',
dto as unknown as Record<string, unknown>,
)
}
// ── Orders ───────────────────────────────────────────────────────
async function fetchAdminOrders(params: {
page?: number
limit?: number
status?: string
}): Promise<PaginatedData<OrderWithDetails>> {
const result = await get<PaginatedData<OrderWithDetails> | LegacyPaginatedData<OrderWithDetails>>(
'/admin/orders',
params,
)
return normalizePaginatedData(result)
}
// ── Bookings ─────────────────────────────────────────────────────
async function fetchAdminBookings(params?: {
page?: number
limit?: number
userId?: string
}): Promise<PaginatedData<any>> {
return get<PaginatedData<any>>('/admin/bookings', params)
}
// ── Members ──────────────────────────────────────────────────────
async function fetchMembers(params?: {
page?: number
limit?: number
search?: string
cardType?: string
}): Promise<PaginatedData<MemberSummary>> {
const cleanParams: Record<string, unknown> = {}
if (params?.page != null) cleanParams.page = params.page
if (params?.limit != null) cleanParams.limit = params.limit
if (params?.search) cleanParams.search = params.search
if (params?.cardType) cleanParams.cardType = params.cardType
return get<PaginatedData<MemberSummary>>('/admin/members', cleanParams)
}
async function getUserMembership(userId: string): Promise<UserMembership> {
return get<UserMembership>(`/admin/members/${userId}/membership`)
}
async function fetchLessonSupplements(userId: string): Promise<LessonSupplementRecord[]> {
return get<LessonSupplementRecord[]>(`/admin/members/${userId}/lesson-supplements`)
}
async function createLessonSupplement(userId: string, dto: CreateLessonSupplementDto): Promise<LessonSupplementRecord> {
return post<LessonSupplementRecord>(`/admin/members/${userId}/lesson-supplements`, { ...dto })
}
async function revokeLessonSupplement(userId: string, id: string): Promise<void> {
await post(`/admin/members/${userId}/lesson-supplements/${id}/revoke`)
}
async function fetchMemberDetail(userId: string): Promise<AdminMemberDetail> {
return get<AdminMemberDetail>(`/admin/members/${userId}`)
}
async function updateMemberProfile(
userId: string,
dto: UpdateAdminMemberProfileDto,
): Promise<AdminMemberDetail> {
return put<AdminMemberDetail>(`/admin/members/${userId}`, dto as unknown as Record<string, unknown>)
}
async function arrangeMemberBooking(dto: AdminArrangeBookingDto): Promise<BookingWithDetails> {
return post<BookingWithDetails>('/admin/bookings', dto as unknown as Record<string, unknown>)
}
async function updateUserMembership(
userId: string,
dto: {
membershipId?: string
cardTypeId: string
remainingTimes?: number | null
startDate: string
expireDate: string
},
): Promise<MembershipWithCardType> {
return put<MembershipWithCardType>(`/admin/members/${userId}/membership`, dto)
}
async function deleteUserMembership(userId: string, membershipId: string): Promise<void> {
return del<void>(
`/admin/members/${userId}/membership?membershipId=${encodeURIComponent(membershipId)}`,
)
}
// ── Time slots ───────────────────────────────────────────────────
async function fetchSlotsByDate(date: string): Promise<TimeSlot[]> {
return get<TimeSlot[]>('/admin/time-slots', { date })
}
async function createManualSlot(dto: CreateManualSlotDto): Promise<TimeSlot> {
return post<TimeSlot>('/admin/time-slot/manual', dto as unknown as Record<string, unknown>)
}
async function closeSlot(id: string): Promise<TimeSlot> {
return put<TimeSlot>(`/admin/time-slot/${id}/close`, {})
}
async function generateSlots(startDate: string, endDate: string): Promise<{ count: number }> {
return post<{ count: number }>('/admin/generate-slots', { startDate, endDate })
}
// ── Schedule management ─────────────────────────────────────────
const schedulePreview = ref<ScheduleSlotPreview[]>([])
const scheduleLoading = ref(false)
async function fetchSchedulePreview(date: string): Promise<ScheduleSlotPreview[]> {
scheduleLoading.value = true
try {
const data = await previewScheduleByDate(date)
schedulePreview.value = data
return data
} finally {
scheduleLoading.value = false
}
}
async function previewScheduleByDate(date: string): Promise<ScheduleSlotPreview[]> {
return get<ScheduleSlotPreview[]>('/admin/schedule/preview', { date })
}
async function publishDaySlots(dto: PublishDaySlotsDto): Promise<void> {
await post('/admin/schedule/publish', dto as unknown as Record<string, unknown>)
await fetchSchedulePreview(dto.date)
}
// ── Dashboard stats ──────────────────────────────────────────────
async function fetchDashboardStats(): Promise<AdminStats> {
return get<AdminStats>('/admin/stats')
}
// ── Flash sales ─────────────────────────────────────────────────
async function fetchFlashSales(params?: {
page?: number
limit?: number
}): Promise<PaginatedData<FlashSaleAdminItem>> {
return get<PaginatedData<FlashSaleAdminItem>>('/admin/flash-sales', params as Record<string, unknown>)
}
async function createFlashSale(dto: CreateFlashSaleDto): Promise<FlashSaleAdminItem> {
return post<FlashSaleAdminItem>('/admin/flash-sales', dto as unknown as Record<string, unknown>)
}
async function updateFlashSale(id: string, dto: UpdateFlashSaleDto): Promise<FlashSaleAdminItem> {
return put<FlashSaleAdminItem>(`/admin/flash-sales/${id}`, dto as unknown as Record<string, unknown>)
}
async function deleteFlashSale(id: string): Promise<{ deleted: boolean }> {
return del<{ deleted: boolean }>(`/admin/flash-sales/${id}`)
}
async function fetchTeachingAnalytics(month: string): Promise<TeachingAnalytics> {
return get<TeachingAnalytics>('/admin/teaching-analytics', { month })
}
return {
fetchTeachingAnalytics,
// State
cardTypes,
studioConfig,
schedulePreview,
scheduleLoading,
// Card types
fetchCardTypes,
createCardType,
updateCardType,
deleteCardType,
// Studio
fetchStudioConfig,
saveStudioConfig,
createStudioUploadCredential,
// Orders
fetchAdminOrders,
// Bookings
fetchAdminBookings,
// Members
fetchMembers,
fetchMemberDetail,
fetchLessonSupplements,
createLessonSupplement,
revokeLessonSupplement,
updateMemberProfile,
arrangeMemberBooking,
getUserMembership,
updateUserMembership,
deleteUserMembership,
// Time slots
fetchSlotsByDate,
createManualSlot,
closeSlot,
generateSlots,
// Schedule
fetchSchedulePreview,
previewScheduleByDate,
publishDaySlots,
// Stats
fetchDashboardStats,
// Flash sales
fetchFlashSales,
createFlashSale,
updateFlashSale,
deleteFlashSale,
}
})