feat: 新的预约列表样式
This commit is contained in:
@@ -40,8 +40,11 @@ export class TimeSlotController {
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getSlotById(@Param('id') id: string) {
|
||||
return this.timeSlotService.getSlotById(id)
|
||||
getSlotById(
|
||||
@Param('id') id: string,
|
||||
@CurrentUser('sub') userId: string,
|
||||
) {
|
||||
return this.timeSlotService.getSlotById(id, userId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>()
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user