feat: 新的预约列表样式

This commit is contained in:
richarjiang
2026-04-06 21:22:18 +08:00
parent 168968f073
commit f94b48203f
11 changed files with 599 additions and 342 deletions

View File

@@ -10,23 +10,58 @@ import type { PublishDaySlotsDto } from './dto/publish-day-slots.dto'
export class TimeSlotService {
constructor(private readonly prisma: PrismaService) {}
private toDateOfDay(date: Date): Date {
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0, 0))
}
private toEndOfDay(date: Date): Date {
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 23, 59, 59, 999))
}
private mapToWithBookingStatus(
slot: {
id: string
date: Date
startTime: string
endTime: string
capacity: number
bookedCount: number
status: string
source: string
templateId: string | null
createdAt: Date
updatedAt: Date
},
myBooking: { id: string } | null,
): TimeSlotWithBookingStatus {
return {
id: slot.id,
date: slot.date.toISOString().split('T')[0],
startTime: slot.startTime,
endTime: slot.endTime,
capacity: slot.capacity,
bookedCount: slot.bookedCount,
status: slot.status as TimeSlotStatus,
source: slot.source as TimeSlotSource,
templateId: slot.templateId,
createdAt: slot.createdAt.toISOString(),
updatedAt: slot.updatedAt.toISOString(),
isBookedByMe: myBooking !== null,
myBookingId: myBooking?.id ?? null,
}
}
async getAvailableSlots(
date: string,
userId?: string,
): Promise<TimeSlotWithBookingStatus[]> {
const parsedDate = new Date(date)
// Build start/end of day boundaries for the query
const startOfDay = new Date(parsedDate)
startOfDay.setUTCHours(0, 0, 0, 0)
const endOfDay = new Date(parsedDate)
endOfDay.setUTCHours(23, 59, 59, 999)
const slots = await this.prisma.timeSlot.findMany({
where: {
date: {
gte: startOfDay,
lte: endOfDay,
gte: this.toDateOfDay(parsedDate),
lte: this.toEndOfDay(parsedDate),
},
status: { not: TimeSlotStatus.CLOSED },
},
@@ -50,44 +85,44 @@ export class TimeSlotService {
? slot.bookings[0]
: null
return {
id: slot.id,
date: slot.date.toISOString().split('T')[0],
startTime: slot.startTime,
endTime: slot.endTime,
capacity: slot.capacity,
bookedCount: slot.bookedCount,
status: slot.status as TimeSlotStatus,
source: slot.source as TimeSlotSource,
templateId: slot.templateId,
createdAt: slot.createdAt.toISOString(),
updatedAt: slot.updatedAt.toISOString(),
isBookedByMe: myBooking !== null,
myBookingId: myBooking?.id ?? null,
} satisfies TimeSlotWithBookingStatus
return this.mapToWithBookingStatus(slot, myBooking)
})
}
async getSlotById(id: string) {
async getSlotById(id: string, userId?: string): Promise<TimeSlotWithBookingStatus> {
const slot = await this.prisma.timeSlot.findUnique({
where: { id },
include: { bookings: true },
include: {
bookings: userId
? {
where: {
userId,
status: BookingStatus.CONFIRMED,
},
select: { id: true },
}
: false,
},
})
if (!slot) {
throw new NotFoundException(`TimeSlot ${id} not found`)
}
return slot
const myBooking =
userId && Array.isArray(slot.bookings) && slot.bookings.length > 0
? slot.bookings[0]
: null
return this.mapToWithBookingStatus(slot, myBooking)
}
async createManualSlot(dto: CreateManualSlotDto) {
const date = new Date(dto.date)
date.setUTCHours(0, 0, 0, 0)
const parsedDate = new Date(dto.date + 'T00:00:00Z')
return this.prisma.timeSlot.create({
data: {
date,
date: parsedDate,
startTime: dto.startTime,
endTime: dto.endTime,
capacity: dto.capacity ?? DEFAULT_SLOT_CAPACITY,
@@ -156,15 +191,11 @@ export class TimeSlotService {
*/
async getSchedulePreview(date: string): Promise<ScheduleSlotPreview[]> {
const parsedDate = new Date(date)
const startOfDay = new Date(parsedDate)
startOfDay.setUTCHours(0, 0, 0, 0)
const endOfDay = new Date(parsedDate)
endOfDay.setUTCHours(23, 59, 59, 999)
// 1. Check for existing TimeSlot records (all statuses)
const existingSlots = await this.prisma.timeSlot.findMany({
where: {
date: { gte: startOfDay, lte: endOfDay },
date: { gte: this.toDateOfDay(parsedDate), lte: this.toEndOfDay(parsedDate) },
},
orderBy: { startTime: 'asc' },
})
@@ -212,17 +243,12 @@ export class TimeSlotService {
* - Existing DB slots not referenced → delete (or CLOSE if they have bookings)
*/
async publishDaySlots(dto: PublishDaySlotsDto) {
const parsedDate = new Date(dto.date)
parsedDate.setUTCHours(0, 0, 0, 0)
const startOfDay = new Date(parsedDate)
const endOfDay = new Date(parsedDate)
endOfDay.setUTCHours(23, 59, 59, 999)
const parsedDate = new Date(dto.date + 'T00:00:00Z')
return this.prisma.$transaction(async (tx) => {
// 1. Get existing slots for this date
const existing = await tx.timeSlot.findMany({
where: { date: { gte: startOfDay, lte: endOfDay } },
where: { date: { gte: this.toDateOfDay(parsedDate), lte: this.toEndOfDay(parsedDate) } },
})
const existingMap = new Map(existing.map((s) => [s.id, s]))
const keptIds = new Set<string>()