feat(admin): implement full day-by-day schedule editor with live preview

## 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>
This commit is contained in:
richarjiang
2026-04-05 12:18:49 +08:00
parent 9c5dd4a911
commit b6986ba30c
29 changed files with 7810 additions and 19 deletions

View File

@@ -35,6 +35,9 @@ export type {
TimeSlot,
TimeSlotWithBookingStatus,
CreateManualSlotDto,
ScheduleSlotPreview,
PublishDaySlotItem,
PublishDaySlotsDto,
Booking,
BookingWithDetails,
CreateBookingDto,

View File

@@ -2,7 +2,7 @@ export type { User, UserProfileResponse, UpdateProfileDto, UserStatsResponse } f
export type { CardType, CreateCardTypeDto, UpdateCardTypeDto } from './card-type'
export type { Membership, MembershipWithCardType } from './membership'
export type { WeekTemplate, WeekTemplateInput } from './week-template'
export type { TimeSlot, TimeSlotWithBookingStatus, CreateManualSlotDto } from './time-slot'
export type { TimeSlot, TimeSlotWithBookingStatus, CreateManualSlotDto, ScheduleSlotPreview, PublishDaySlotItem, PublishDaySlotsDto } from './time-slot'
export type { Booking, BookingWithDetails, CreateBookingDto } from './booking'
export type { Order, OrderWithDetails, CreateOrderDto, PaymentParams, CreateOrderResponse } from './order'
export type { StudioConfig, UpdateStudioConfigDto } from './studio'

View File

@@ -27,3 +27,35 @@ export interface CreateManualSlotDto {
readonly endTime: string
readonly capacity?: number
}
/** 排课预览项(已发布的 TimeSlot 或模板派生的预览) */
export interface ScheduleSlotPreview {
/** 已发布则有 ID模板预览为 null */
readonly id: string | null
readonly date: string
readonly startTime: string
readonly endTime: string
readonly capacity: number
readonly bookedCount: number
readonly status: TimeSlotStatus
readonly source: TimeSlotSource
readonly templateId: string | null
/** true = DB 中已有 TimeSlot 记录 */
readonly isPublished: boolean
}
/** 发布某天排课时的单个时段 */
export interface PublishDaySlotItem {
/** 保留/修改已有时段时传入 */
readonly existingSlotId?: string
readonly startTime: string
readonly endTime: string
readonly capacity: number
}
/** 发布某天排课的请求体 */
export interface PublishDaySlotsDto {
/** YYYY-MM-DD */
readonly date: string
readonly slots: readonly PublishDaySlotItem[]
}