## Features ### Admin Schedule Page (`packages/app/src/pages/admin/schedule.vue`) - Interactive date-based slot editor for managing daily schedules - Real-time slot editing: start/end times, capacity adjustments - Slot deletion with conflict warnings when bookings exist - Add new slots with modal dialog - Live booking status display (booked count, people names) - Publish/Save changes with sync feedback - Revert unsaved changes with confirmation - Skeleton loading states and empty state handling - Responsive design with optimized mobile UX ### Backend Enhancements - **New DTO** (`PublishDaySlotsDto`): Structured slot publishing with validation - Date string validation - Slot array with existing slot IDs for updates - Time and capacity validation per slot - **Schedule Preview API** (`getSchedulePreview`): - Check for existing published slots - Fallback to active WeekTemplates for unpublished dates - Unified response format with isPublished flag - **Publish Slots API** (`publishDaySlots`): - Atomic transaction for consistency - Update existing slots with new times/capacity - Create new slots from template data - Delete unpublished slots or set to CLOSED if bookings exist - Prevent capacity reduction below existing bookings - Returns all published slots for feedback ### State Management - Enhanced admin store with schedule state - Support for pending/unsaved slot changes - Optimistic UI updates with server sync ### Documentation - Comprehensive scheduling system architecture docs - Quick reference for admin workflows - Flow diagrams and state transitions - Implementation guide for future maintenance ## Breaking Changes None ## Testing Recommendations - Create slots for future dates via schedule editor - Verify booking prevention for locked/full slots - Test capacity adjustments with existing bookings - Confirm template-based schedule generation - Verify transaction rollback on publish failures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Put,
|
|
Param,
|
|
Body,
|
|
Query,
|
|
UseGuards,
|
|
HttpCode,
|
|
HttpStatus,
|
|
} from '@nestjs/common'
|
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard'
|
|
import { RolesGuard } from '../auth/roles.guard'
|
|
import { Roles } from '../auth/roles.decorator'
|
|
import { CurrentUser } from '../common/decorators/current-user.decorator'
|
|
import { UserRole } from '@mp-pilates/shared'
|
|
import { TimeSlotService } from './time-slot.service'
|
|
import { SlotGeneratorService } from './slot-generator.service'
|
|
import { QuerySlotsDto } from './dto/query-slots.dto'
|
|
import { CreateManualSlotDto } from './dto/create-manual-slot.dto'
|
|
import { UpdateWeekTemplateDto } from './dto/week-template.dto'
|
|
import { PublishDaySlotsDto } from './dto/publish-day-slots.dto'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Member endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@UseGuards(JwtAuthGuard)
|
|
@Controller('time-slot')
|
|
export class TimeSlotController {
|
|
constructor(private readonly timeSlotService: TimeSlotService) {}
|
|
|
|
@Get('available')
|
|
getAvailableSlots(
|
|
@Query() query: QuerySlotsDto,
|
|
@CurrentUser('sub') userId: string,
|
|
) {
|
|
return this.timeSlotService.getAvailableSlots(query.date, userId)
|
|
}
|
|
|
|
@Get(':id')
|
|
getSlotById(@Param('id') id: string) {
|
|
return this.timeSlotService.getSlotById(id)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Admin endpoints
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
@Roles(UserRole.ADMIN)
|
|
@Controller('admin')
|
|
export class AdminTimeSlotController {
|
|
constructor(
|
|
private readonly timeSlotService: TimeSlotService,
|
|
private readonly slotGeneratorService: SlotGeneratorService,
|
|
) {}
|
|
|
|
// Week template management
|
|
|
|
@Get('week-template')
|
|
getWeekTemplates() {
|
|
return this.timeSlotService.getWeekTemplates()
|
|
}
|
|
|
|
@Put('week-template')
|
|
replaceWeekTemplates(@Body() dto: UpdateWeekTemplateDto) {
|
|
return this.timeSlotService.replaceWeekTemplates(dto.templates)
|
|
}
|
|
|
|
// Manual slot management
|
|
|
|
@Post('time-slot/manual')
|
|
createManualSlot(@Body() dto: CreateManualSlotDto) {
|
|
return this.timeSlotService.createManualSlot(dto)
|
|
}
|
|
|
|
@Put('time-slot/:id/close')
|
|
@HttpCode(HttpStatus.OK)
|
|
closeSlot(@Param('id') id: string) {
|
|
return this.timeSlotService.closeSlot(id)
|
|
}
|
|
|
|
// Slot generation trigger
|
|
|
|
@Post('generate-slots')
|
|
@HttpCode(HttpStatus.OK)
|
|
generateSlots() {
|
|
return this.slotGeneratorService.generateSlots()
|
|
}
|
|
|
|
// Schedule preview & publish
|
|
|
|
@Get('schedule/preview')
|
|
getSchedulePreview(@Query('date') date: string) {
|
|
return this.timeSlotService.getSchedulePreview(date)
|
|
}
|
|
|
|
@Post('schedule/publish')
|
|
@HttpCode(HttpStatus.OK)
|
|
publishDaySlots(@Body() dto: PublishDaySlotsDto) {
|
|
return this.timeSlotService.publishDaySlots(dto)
|
|
}
|
|
}
|