298 lines
9.5 KiB
TypeScript
298 lines
9.5 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import { get, post, put, del } from '../utils/request'
|
|
import type {
|
|
CardType,
|
|
CreateCardTypeDto,
|
|
UpdateCardTypeDto,
|
|
StudioConfig,
|
|
UpdateStudioConfigDto,
|
|
OrderWithDetails,
|
|
TimeSlot,
|
|
CreateManualSlotDto,
|
|
PaginatedData,
|
|
ScheduleSlotPreview,
|
|
PublishDaySlotsDto,
|
|
FlashSaleAdminItem,
|
|
CreateFlashSaleDto,
|
|
UpdateFlashSaleDto,
|
|
CreateStudioUploadCredentialDto,
|
|
StudioUploadCredential,
|
|
} 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 interface MemberSummary {
|
|
userId: string
|
|
openid: string
|
|
nickname: string
|
|
phone: string | null
|
|
avatarUrl: string | null
|
|
totalBookings: number
|
|
completedBookings: number
|
|
cancelledBookings: number
|
|
}
|
|
|
|
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 updateUserMembership(
|
|
userId: string,
|
|
dto: {
|
|
cardTypeId: string
|
|
remainingTimes?: number | null
|
|
startDate: string
|
|
expireDate: string
|
|
},
|
|
): Promise<any> {
|
|
return put<any>(`/admin/members/${userId}/membership`, dto)
|
|
}
|
|
|
|
async function deleteUserMembership(userId: string): Promise<void> {
|
|
return del<void>(`/admin/members/${userId}/membership`)
|
|
}
|
|
|
|
// ── 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 get<ScheduleSlotPreview[]>('/admin/schedule/preview', { date })
|
|
schedulePreview.value = data
|
|
return data
|
|
} finally {
|
|
scheduleLoading.value = false
|
|
}
|
|
}
|
|
|
|
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}`)
|
|
}
|
|
|
|
return {
|
|
// State
|
|
cardTypes,
|
|
studioConfig,
|
|
schedulePreview,
|
|
scheduleLoading,
|
|
// Card types
|
|
fetchCardTypes,
|
|
createCardType,
|
|
updateCardType,
|
|
deleteCardType,
|
|
// Studio
|
|
fetchStudioConfig,
|
|
saveStudioConfig,
|
|
createStudioUploadCredential,
|
|
// Orders
|
|
fetchAdminOrders,
|
|
// Bookings
|
|
fetchAdminBookings,
|
|
// Members
|
|
fetchMembers,
|
|
getUserMembership,
|
|
updateUserMembership,
|
|
deleteUserMembership,
|
|
// Time slots
|
|
fetchSlotsByDate,
|
|
createManualSlot,
|
|
closeSlot,
|
|
generateSlots,
|
|
// Schedule
|
|
fetchSchedulePreview,
|
|
publishDaySlots,
|
|
// Stats
|
|
fetchDashboardStats,
|
|
// Flash sales
|
|
fetchFlashSales,
|
|
createFlashSale,
|
|
updateFlashSale,
|
|
deleteFlashSale,
|
|
}
|
|
})
|