Files
mp-pilates/BOOKING_README.md
richarjiang b6986ba30c 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>
2026-04-05 12:18:49 +08:00

396 lines
11 KiB
Markdown

# Booking Page Documentation
## 📚 Overview
This folder contains comprehensive documentation for the WeChat Mini-Program booking system in the mp-pilates project (Uni-app + Vue 3).
### 📄 Documentation Files
1. **BOOKING_PAGE_ANALYSIS.md** ⭐ START HERE
- Complete file-by-file breakdown of all components
- Data flow diagrams
- API contract documentation
- Color scheme and styling details
- Potential issues and problems
2. **COMPONENT_HIERARCHY.md**
- Visual component tree structure
- State management flow (Pinia stores)
- API sequence diagrams
- State machine for slot cards
- Data transformations
3. **QUICK_REFERENCE.md**
- Code snippets for quick lookup
- Debugging tips and console commands
- Common issues and solutions
- Debugging checklist
- API examples
---
## 🎯 Quick Navigation
### I want to understand...
**...the overall flow**
→ Read: BOOKING_PAGE_ANALYSIS.md → "Complete Data Flow Diagram" section
**...how the UI is structured**
→ Read: COMPONENT_HIERARCHY.md → "Component Tree" + "UI Layout Breakdown"
**...where specific code is**
→ Read: QUICK_REFERENCE.md → "Finding Specific Things"
**...how to debug an issue**
→ Read: QUICK_REFERENCE.md → "Common Issues & Solutions"
**...the API contracts**
→ Read: QUICK_REFERENCE.md → "API Contract Summary"
**...the store state**
→ Read: COMPONENT_HIERARCHY.md → "State Management Flow"
---
## 🏗️ Project Structure
```
packages/app/src/
├── pages/
│ └── booking/
│ └── index.vue # Main booking page (311 lines)
├── components/
│ ├── DateSelector.vue # Date picker (50 lines)
│ ├── TimePeriodFilter.vue # Time period filter (50 lines)
│ ├── SlotCard.vue # Individual slot card (230 lines)
│ └── BookingConfirmPopup.vue # Booking confirmation modal (430 lines)
├── stores/
│ ├── booking.ts # Booking state (72 lines)
│ └── user.ts # User/membership state (110 lines)
└── utils/
├── request.ts # API request utilities (80 lines)
└── format.ts # Date formatting utilities (50 lines)
packages/shared/src/
├── types/
│ ├── time-slot.ts # TimeSlot types
│ ├── api.ts # API response types
│ └── booking.ts # Booking types
├── constants.ts # TIME_PERIODS, etc
└── enums.ts # Enums (TimeSlotStatus, etc)
```
---
## 🔄 Data Flow at a Glance
```
Page Load
[Check login + load memberships]
Store: fetchSlots(today)
API: GET /time-slot/available?date=TODAY
State: bookingStore.slots = [TimeSlotWithBookingStatus[], ...]
Computed: filteredSlots (optionally filtered by period)
Render: SlotCard components
User interaction:
- Tap date → loadSlots(newDate)
- Filter period → filteredSlots re-computed
- Book slot → onBookTap() → popup
- Confirm → createBooking() → refresh slots
- Cancel → cancelBooking() → refresh slots
```
---
## 🎭 Key Components
### 1. pages/booking/index.vue
**Role:** Main page that orchestrates everything
**State:** selectedDate, selectedPeriod, showConfirmPopup, pendingSlot
**Stores:** bookingStore, userStore
**Key computed:** scrollHeight, filteredSlots
### 2. components/SlotCard.vue
**Role:** Displays individual time slot
**Props:** slot (TimeSlotWithBookingStatus)
**Emits:** book, cancel
**States:** 4 button states based on status + isBookedByMe
### 3. components/DateSelector.vue
**Role:** Horizontal date picker
**Props:** modelValue (YYYY-MM-DD)
**Data:** dateRange (7 days from today)
**Display:** Shows weekday, day number, month
### 4. components/TimePeriodFilter.vue
**Role:** Horizontal tab filter
**Props:** modelValue (MORNING|AFTERNOON|EVENING|null)
**Constants:** TIME_PERIODS from shared
### 5. components/BookingConfirmPopup.vue
**Role:** Modal for confirming booking
**Props:** visible, slot, memberships
**State:** selectedMembershipId (auto-selected on show)
**Logic:** Auto-select first membership when popup opens
### 6. stores/booking.ts
**Actions:**
- fetchSlots(date) → GET /time-slot/available?date=
- createBooking(dto) → POST /booking
- cancelBooking(bookingId) → PUT /booking/:id/cancel
- fetchMyBookings(status?) → GET /booking/my
- fetchUpcomingBookings() → GET /booking/my/upcoming
### 7. stores/user.ts
**Computed:**
- loggedIn: !!token.value
- hasValidMembership: activeMemberships.length > 0
- activeMemberships: memberships filtered by ACTIVE status
---
## 📊 State Types
### TimeSlotWithBookingStatus
```typescript
{
id: string // UUID
date: "2026-04-05" // YYYY-MM-DD
startTime: "09:00" // HH:MM
endTime: "10:00" // HH:MM
capacity: 1 // Max slots
bookedCount: 0 // Currently booked
status: "OPEN" | "FULL" | "CLOSED"
source: "MANUAL" | "TEMPLATE"
templateId: null
isBookedByMe: boolean // User has booked this
myBookingId: string | null // Booking ID (for cancel)
}
```
### MembershipWithCardType
```typescript
{
id: string
cardType: { name: string, ... }
status: "ACTIVE" | "EXPIRED" | "USED_UP"
remainingTimes: number | null
expireDate: "2026-12-31"
}
```
---
## 🎨 Visual States
### Slot Card Button States
| Condition | Button | Color | Action |
|-----------|--------|-------|--------|
| OPEN, not booked | "可预约" | Tan (#c9a87c) | Show popup |
| OPEN, booked by me | "已预约" + "取消" link | Tan + Red | Show cancel confirm |
| FULL | "已约满" | Gray (#f0f0f0) | Disabled |
| CLOSED | "已关闭" | Gray (#f0f0f0) | Disabled |
### Capacity Badge Colors
| Condition | Background | Text | Meaning |
|-----------|------------|------|---------|
| <80% booked | #f0faf3 | #4caf50 | Green - Plenty of spots |
| ≥80% booked | #fff8ed | #f59e0b | Orange - Almost full |
| FULL | #fef0f0 | #ef4444 | Red - No spots |
| CLOSED | #f5f5f5 | #999 | Gray - Unavailable |
---
## 🔐 Authentication
- Token stored in localStorage
- Automatically included in request headers
- 401 response → Clear token + show "please login" toast
- onBookTap checks loggedIn → shows login modal if needed
- onBookTap checks hasValidMembership → shows purchase modal if needed
---
## 📡 API Endpoints
### GET /time-slot/available?date=YYYY-MM-DD
```
Query: date (required, YYYY-MM-DD format)
Returns: TimeSlotWithBookingStatus[]
Auth: Bearer token required
```
### POST /booking
```
Body: { timeSlotId, membershipId }
Returns: BookingWithDetails
Auth: Bearer token required
```
### PUT /booking/:bookingId/cancel
```
Path: bookingId
Returns: BookingWithDetails (with status: CANCELLED)
Auth: Bearer token required
```
### GET /membership/my
```
Returns: MembershipWithCardType[]
Auth: Bearer token required
```
---
## ⚠️ Known Issues
### 1. GET Request Body Issue
- File: `utils/request.ts`, `get()` function
- Problem: Data passed as body instead of query params
- Impact: Might not work on all platforms
### 2. Error Handling
- File: `stores/booking.ts`, `fetchSlots()`
- Problem: Network error → empty array instead of error message
- Impact: Users can't tell if error or truly no slots
### 3. Loading State
- File: `pages/booking/index.vue`
- Problem: Skeleton only appears on initial load
- Impact: Date changes appear instant (confusing on slow network)
### 4. Date Math
- File: `utils/format.ts`, `getDateRange()`
- Problem: Uses ms arithmetic (86400000ms per day)
- Impact: Doesn't account for DST transitions
---
## 🧪 Testing Checklist
### Happy Path
- [ ] Load page → today's slots display
- [ ] Tap date → slots change for that date
- [ ] Filter by period → slots filtered correctly
- [ ] Tap "可预约" → popup shows
- [ ] Confirm booking → slot shows "已预约"
- [ ] Tap "取消" → booking cancelled, slot resets
- [ ] Pull to refresh → slots reload
### Edge Cases
- [ ] No slots for date → empty state appears
- [ ] Not logged in → login modal on book tap
- [ ] No valid membership → purchase modal on book tap
- [ ] Network error → ??? (currently shows empty)
- [ ] Slot becomes FULL → button updates to disabled
- [ ] Multiple memberships → can select different card
---
## 📝 File Sizes
| File | Lines | Purpose |
|------|-------|---------|
| pages/booking/index.vue | 311 | Main page orchestration |
| components/BookingConfirmPopup.vue | 430 | Booking modal |
| components/SlotCard.vue | 230 | Slot display |
| stores/booking.ts | 72 | Booking state |
| utils/request.ts | 80 | API client |
| components/DateSelector.vue | 50 | Date picker |
| components/TimePeriodFilter.vue | 50 | Period filter |
| utils/format.ts | 50 | Date utilities |
---
## 🎓 Learning Path
**Level 1: Overview**
1. Read this file
2. Look at BOOKING_PAGE_ANALYSIS.md → "Complete Data Flow Diagram"
**Level 2: Components**
1. Read COMPONENT_HIERARCHY.md → "Component Tree"
2. Read BOOKING_PAGE_ANALYSIS.md → "File-by-File Analysis"
**Level 3: Implementation**
1. Read QUICK_REFERENCE.md → "Where Slots Come From"
2. Read actual source files in order:
- stores/booking.ts
- pages/booking/index.vue
- components/SlotCard.vue
- components/BookingConfirmPopup.vue
**Level 4: Debugging**
1. Read QUICK_REFERENCE.md → "Debugging Tips"
2. Read QUICK_REFERENCE.md → "Common Issues & Solutions"
**Level 5: Deep Dive**
1. Read COMPONENT_HIERARCHY.md → "State Management Flow"
2. Read COMPONENT_HIERARCHY.md → "API Calls Sequence"
3. Study utils/request.ts for request handling
---
## 🔗 Related Documentation
- Backend: `/packages/server/src/time-slot/`
- Shared types: `/packages/shared/src/types/`
- Auth: `/packages/app/src/utils/auth.ts`
- User store: `/packages/app/src/stores/user.ts`
---
## 📞 Quick Answers
**Q: Why doesn't the page load?**
A: Check 1) Is API returning data? 2) Is token valid? 3) Check console for errors
**Q: Why doesn't filtering work?**
A: Check 1) Is selectedPeriod.value being set? 2) Is slot.startTime correct format?
**Q: Why doesn't the booking button work?**
A: Check 1) Is slot.status === OPEN? 2) Is isBookedByMe === false? 3) Is user logged in?
**Q: How do I add error handling?**
A: See QUICK_REFERENCE.md → "Issue 1: Slots not loading" → Solution
**Q: How do I test the booking flow?**
A: See "Testing Checklist" section above
---
## 🚀 Common Tasks
### Add loading indicator during date change
→ Use bookingStore.loadingSlots in template
### Show error message for API failures
→ Add error state to bookingStore, show in template
### Change colors/styling
→ Edit style blocks in .vue files (see color scheme in BOOKING_PAGE_ANALYSIS.md)
### Modify time period ranges
→ Edit TIME_PERIODS in packages/shared/src/constants.ts
### Change initial date or time range
→ Edit pages/booking/index.vue onMounted() or DATE_SELECTOR_DAYS constant
### Add/remove date selector days
→ Edit DATE_SELECTOR_DAYS in packages/shared/src/constants.ts
---
Generated: 2026-04-05
Last Updated: BOOKING_PAGE_ANALYSIS.md