## 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>
396 lines
11 KiB
Markdown
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
|