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:
271
SCHEDULING_FLOW_DIAGRAM.md
Normal file
271
SCHEDULING_FLOW_DIAGRAM.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Admin Scheduling Flow Diagram
|
||||
|
||||
## Component Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Admin Dashboard │
|
||||
│ (pages/admin/index.vue) │
|
||||
│ │
|
||||
│ 📅 排课设置 🔧 临时调整 👥 会员 📋 订单 💳 卡 🏢 工作室
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
└─► 📅 排课设置 (Week Template)
|
||||
└─────────────────────────────────────────┐
|
||||
│ pages/admin/week-template.vue │
|
||||
│ ================================ │
|
||||
│ │
|
||||
│ 1. Fetch Templates (onMounted) │
|
||||
│ └─ GET /admin/week-template │
|
||||
│ │
|
||||
│ 2. Display grouped by day (Mon-Sun) │
|
||||
│ │
|
||||
│ 3. Add/Edit/Delete/Toggle locally │
|
||||
│ └─ isDirty flag = true │
|
||||
│ │
|
||||
│ 4. Save All Changes (bottom bar) │
|
||||
│ └─ PUT /admin/week-template │
|
||||
│ (Full template array) │
|
||||
│ │
|
||||
│ 5. Backend transaction: │
|
||||
│ - DELETE all templates │
|
||||
│ - CREATE new templates │
|
||||
└────────────────────────────────────────┘
|
||||
|
||||
└─► 🔧 临时调整 (Slot Adjustment - 3 Tabs)
|
||||
└─────────────────────────────────────────┐
|
||||
│ pages/admin/slot-adjust.vue │
|
||||
│ ================================ │
|
||||
│ │
|
||||
│ TAB 0: 新增时段 (Add Manual Slot) │
|
||||
│ ├─ Date picker │
|
||||
│ ├─ Time pickers │
|
||||
│ ├─ Capacity input │
|
||||
│ └─ POST /admin/time-slot/manual │
|
||||
│ └─ Creates slot with source=MANUAL │
|
||||
│ │
|
||||
│ TAB 1: 关闭时段 (Close Slots) │
|
||||
│ ├─ Date picker │
|
||||
│ ├─ Fetch slots for date │
|
||||
│ │ └─ GET /admin/time-slots?date=XXX │
|
||||
│ ├─ Display with status badges │
|
||||
│ │ (OPEN/FULL/CLOSED) │
|
||||
│ └─ PUT /admin/time-slot/:id/close │
|
||||
│ │
|
||||
│ TAB 2: 批量生成 (Batch Generate) │
|
||||
│ ├─ Start/end date pickers │
|
||||
│ ├─ POST /admin/generate-slots │
|
||||
│ └─ Backend: │
|
||||
│ 1. Fetch active WeekTemplates │
|
||||
│ 2. For each day in range: │
|
||||
│ - Get ISO weekday (1-7) │
|
||||
│ - Find matching templates │
|
||||
│ - Create TimeSlot records │
|
||||
│ 3. Returns { count: N } │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow: Template → Slots
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ ADMIN TEMPLATE SETUP │
|
||||
│ (weeks/admin/week-template.vue) │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────┐
|
||||
│ Admin configures templates: │
|
||||
│ │
|
||||
│ 周一: 09:00-10:00 (10 ppl) │
|
||||
│ 周一: 18:00-19:00 (8 ppl) │
|
||||
│ 周三: 10:00-11:00 (12 ppl) │
|
||||
│ 周五: 18:00-20:00 (15 ppl) │
|
||||
└───────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────┐
|
||||
│ PUT /admin/week-template │
|
||||
│ (All templates replaced) │
|
||||
└───────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Backend: Delete all, Create new (atomic) │
|
||||
└────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Scheduler (nightly cron or manual trigger)│
|
||||
│ POST /admin/generate-slots (14 days) │
|
||||
└────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ SlotGeneratorService.generateSlots() │
|
||||
│ │
|
||||
│ For each active template: │
|
||||
│ For date in next 14 days: │
|
||||
│ If template.dayOfWeek == date.dayOfWeek:
|
||||
│ CREATE TimeSlot { │
|
||||
│ date, startTime, endTime, │
|
||||
│ capacity, source=TEMPLATE, │
|
||||
│ templateId │
|
||||
│ } │
|
||||
└────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ GENERATED TIME SLOTS │
|
||||
│ │
|
||||
│ 2026-04-06 (Mon): │
|
||||
│ 09:00-10:00 (10 ppl, OPEN) │
|
||||
│ 18:00-19:00 (8 ppl, OPEN) │
|
||||
│ │
|
||||
│ 2026-04-08 (Wed): │
|
||||
│ 10:00-11:00 (12 ppl, OPEN) │
|
||||
│ │
|
||||
│ 2026-04-11 (Fri): │
|
||||
│ 18:00-20:00 (15 ppl, OPEN) │
|
||||
│ ... (more dates) │
|
||||
└────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Members can book available slots │
|
||||
│ GET /time-slot/available?date=YYYY-MM-DD
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Component State (week-template.vue)
|
||||
```typescript
|
||||
templates: LocalTemplate[] ◄─ Main data array
|
||||
loading: boolean ◄─ Fetch state
|
||||
saving: boolean ◄─ Save state
|
||||
isDirty: boolean ◄─ "Save bar" trigger
|
||||
showModal: boolean ◄─ Modal visibility
|
||||
editTarget: LocalTemplate | null ◄─ Which template is being edited
|
||||
form: { ◄─ Modal form data
|
||||
dayIdx: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
capacityStr: string
|
||||
}
|
||||
grouped: Computed<Record<number, LocalTemplate[]>> ◄─ Grouped by dayOfWeek
|
||||
```
|
||||
|
||||
### Store State (stores/admin.ts)
|
||||
```typescript
|
||||
weekTemplates: WeekTemplate[] ◄─ Cached from server
|
||||
cardTypes: CardType[]
|
||||
studioConfig: StudioConfig | null
|
||||
// ...other admin state
|
||||
```
|
||||
|
||||
## API Endpoints Summary
|
||||
|
||||
### Week Templates
|
||||
```
|
||||
GET /admin/week-template Fetch all templates
|
||||
PUT /admin/week-template Replace all templates
|
||||
```
|
||||
|
||||
### Time Slots
|
||||
```
|
||||
GET /admin/time-slots?date=YYYY-MM-DD Fetch slots for date
|
||||
POST /admin/time-slot/manual Create manual slot
|
||||
PUT /admin/time-slot/:id/close Close a slot
|
||||
POST /admin/generate-slots Generate slots from templates
|
||||
```
|
||||
|
||||
### Public Endpoints
|
||||
```
|
||||
GET /time-slot/available?date=YYYY-MM-DD For members
|
||||
GET /time-slot/:id For members
|
||||
```
|
||||
|
||||
## Entity Relationships
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ WeekTemplate │
|
||||
├─────────────────────┤
|
||||
│ id │
|
||||
│ dayOfWeek (1-7) │
|
||||
│ startTime │
|
||||
│ endTime │
|
||||
│ capacity │
|
||||
│ isActive │
|
||||
└─────────────────────┘
|
||||
│ (1:N)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐ ┌──────────────────┐
|
||||
│ TimeSlot │ │ Booking (M:1) │
|
||||
├─────────────────────┤ ├──────────────────┤
|
||||
│ id │◄─────│ timeSlotId │
|
||||
│ date │ │ userId │
|
||||
│ startTime │ │ status │
|
||||
│ endTime │ └──────────────────┘
|
||||
│ capacity │
|
||||
│ bookedCount │
|
||||
│ status │
|
||||
│ source (TEMPLATE/ │
|
||||
│ MANUAL) │
|
||||
│ templateId (FK) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
## Weekday Mapping
|
||||
|
||||
### Frontend Picker (dayOptions)
|
||||
```
|
||||
Index 0: 周一 (Monday) ──► dayOfWeek = 1
|
||||
Index 1: 周二 (Tuesday) ──► dayOfWeek = 2
|
||||
Index 2: 周三 (Wednesday) ──► dayOfWeek = 3
|
||||
Index 3: 周四 (Thursday) ──► dayOfWeek = 4
|
||||
Index 4: 周五 (Friday) ──► dayOfWeek = 5
|
||||
Index 5: 周六 (Saturday) ──► dayOfWeek = 6
|
||||
Index 6: 周日 (Sunday) ──► dayOfWeek = 7
|
||||
```
|
||||
|
||||
### Backend Conversion (slot-generator.service.ts)
|
||||
```typescript
|
||||
JS getDay(): 0=Sun, 1=Mon, 2=Tue, ..., 6=Sat
|
||||
│
|
||||
▼ toIsoWeekday()
|
||||
│
|
||||
ISO weekday: 1=Mon, 2=Tue, ..., 7=Sun
|
||||
```
|
||||
|
||||
## Timeline Example
|
||||
|
||||
```
|
||||
TODAY: 2026-04-05 (Sunday)
|
||||
|
||||
Admin actions:
|
||||
1. Sets up weekly templates for Mon-Fri
|
||||
2. Taps "保存全部更改"
|
||||
3. PUT /admin/week-template sent
|
||||
|
||||
Backend scheduler (daily at midnight):
|
||||
4. Runs generateSlots(14)
|
||||
5. Tomorrow is 2026-04-06 (Monday)
|
||||
6. Generates slots for Apr 6-19 (next 14 days)
|
||||
7. Creates TimeSlots based on active templates:
|
||||
|
||||
Generated slots:
|
||||
2026-04-06 (Mon): 09:00-10:00, 18:00-19:00
|
||||
2026-04-07 (Tue): (none if no templates)
|
||||
2026-04-08 (Wed): 10:00-11:00
|
||||
2026-04-09 (Thu): (none if no templates)
|
||||
2026-04-10 (Fri): 18:00-20:00
|
||||
2026-04-11 (Sat): (none - weekend)
|
||||
2026-04-12 (Sun): (none - weekend)
|
||||
...repeats until 2026-04-19
|
||||
|
||||
Members can book from Apr 6 onwards
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user