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

11 KiB

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

{
  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

{
  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

  • 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