# WeChat Mini-Program Admin Scheduling/ๆŽ’่ฏพ่ฎพ็ฝฎ - Complete Exploration Report **Date**: 2026-04-05 **Project**: mp-pilates (WeChat mini-program for pilates studio bookings) --- ## ๐Ÿ“‹ Executive Summary This is a **Pilates studio booking management system** with a comprehensive admin scheduling UI. The "ๆŽ’่ฏพ่ฎพ็ฝฎ" (Schedule Setup) feature allows admins to: 1. Define recurring weekly class templates (ๆ—ถ้—ดๆจกๆฟ) 2. Manually add time slots for specific dates 3. Close slots (ไธดๆ—ถ่ฐƒๆ•ด โ†’ ๅ…ณ้—ญๆ—ถๆฎต) 4. Batch generate slots from templates The architecture uses: - **Frontend**: Vue 3 + TypeScript (WeChat mini-program with Taro/UNI framework) - **Backend**: NestJS + Prisma ORM - **State Management**: Pinia (Vue state management) - **Database**: Likely PostgreSQL/MySQL with Prisma --- ## ๐Ÿ—‚๏ธ File Structure ### Frontend Admin Pages ``` packages/app/src/pages/admin/ โ”œโ”€โ”€ index.vue # Admin dashboard with nav grid โ”œโ”€โ”€ week-template.vue # ๐Ÿ“… Scheduling/ๆŽ’่ฏพ่ฎพ็ฝฎ - Main feature โ”œโ”€โ”€ slot-adjust.vue # ๐Ÿ”ง Temporary adjustments (3 tabs) โ”œโ”€โ”€ members.vue # ๐Ÿ‘ฅ Member management โ”œโ”€โ”€ orders.vue # ๐Ÿ“‹ Order management โ”œโ”€โ”€ card-types.vue # ๐Ÿ’ณ Card type management โ””โ”€โ”€ studio.vue # ๐Ÿข Studio settings ``` ### Stores ``` packages/app/src/stores/ โ””โ”€โ”€ admin.ts # Pinia store with all admin API calls ``` ### Backend API ``` packages/server/src/ โ”œโ”€โ”€ time-slot/ โ”‚ โ”œโ”€โ”€ time-slot.controller.ts # Admin & member endpoints for slots โ”‚ โ”œโ”€โ”€ time-slot.service.ts # Business logic for slots โ”‚ โ”œโ”€โ”€ slot-generator.service.ts # Template-based slot generation โ”‚ โ””โ”€โ”€ dto/ โ”‚ โ”œโ”€โ”€ week-template.dto.ts # Input validation โ”‚ โ”œโ”€โ”€ create-manual-slot.dto.ts โ”‚ โ””โ”€โ”€ query-slots.dto.ts โ”œโ”€โ”€ studio/ โ”‚ โ””โ”€โ”€ studio.controller.ts # Studio config (admin endpoints) โ””โ”€โ”€ scheduler/ # Cron scheduler for auto-generation ``` ### Shared Types ``` packages/shared/src/types/ โ”œโ”€โ”€ week-template.ts # WeekTemplate interface โ”œโ”€โ”€ time-slot.ts # TimeSlot interface โ””โ”€โ”€ constants.ts # WEEKDAY_LABELS, SLOT_GENERATION_DAYS ``` --- ## ๐Ÿ”‘ Key Components ### 1. **Admin Dashboard (index.vue)** **File**: `packages/app/src/pages/admin/index.vue` **Features**: - Display stats: today's bookings, total orders, total bookings - Navigation grid to 6 admin modules: - ๐Ÿ“… **ๆŽ’่ฏพ่ฎพ็ฝฎ** โ†’ `/pages/admin/week-template` - ๐Ÿ”ง **ไธดๆ—ถ่ฐƒๆ•ด** โ†’ `/pages/admin/slot-adjust` - ๐Ÿ‘ฅ **ไผšๅ‘˜็ฎก็†** โ†’ `/pages/admin/members` - ๐Ÿ“‹ **่ฎขๅ•็ฎก็†** โ†’ `/pages/admin/orders` - ๐Ÿ’ณ **ๅก็ง็ฎก็†** โ†’ `/pages/admin/card-types` - ๐Ÿข **ๅทฅไฝœๅฎค่ฎพ็ฝฎ** โ†’ `/pages/admin/studio` **Key Functions**: ```typescript - navigate(path): Navigates to admin pages - loadStats(): Fetches dashboard statistics via adminStore.fetchDashboardStats() ``` **State**: ```typescript const stats = ref({ todayBookings: 0, totalOrders: 0, totalBookings: 0 }) const statsLoading = ref(false) ``` --- ### 2. **Week Template Management (week-template.vue)** โœจ MAIN SCHEDULING UI **File**: `packages/app/src/pages/admin/week-template.vue` **Route**: `/pages/admin/week-template` #### Purpose Manage recurring weekly schedule templates. These are used to **auto-generate** time slots for future weeks. #### Data Structure ```typescript interface WeekTemplate { readonly id: string readonly dayOfWeek: number // 1=Mon, 2=Tue, ..., 7=Sun (ISO format) readonly startTime: string // HH:MM format readonly endTime: string // HH:MM format readonly capacity: number // Max bookings per slot readonly isActive: boolean // Enable/disable template readonly createdAt: string readonly updatedAt: string } ``` #### UI Sections 1. **Toolbar** - Display template count: "ๅ…ฑ N ๆกๆจกๆฟ" - "+ ๆ–ฐๅขžๆ—ถๆฎต" (Add new slot) button 2. **Template List** (Grouped by weekday) - Days are sorted (Monday โ†’ Sunday) - Each day shows count: "3 ไธชๆ—ถๆฎต" - Each template row displays: - Time range: "09:00 โ€“ 10:00" - Capacity: "10 ไบบ" - Actions: - Toggle button: "ๅฏ็”จ"/"ๅœ็”จ" (Enable/Disable) - "็ผ–่พ‘" (Edit) - "ๅˆ ้™ค" (Delete) - Inactive templates are grayed out (opacity: 0.5) 3. **Modal for Add/Edit** - Star date picker (1-7 for weekday) - Start time picker - End time picker - Capacity input (number) - Validation: time and capacity required - Cancel/Confirm buttons 4. **Save Bar** (Fixed at bottom) - Only shows when `isDirty` flag is true - "ไฟๅญ˜ๅ…จ้ƒจๆ›ดๆ”น" button with loading state #### Key Functions ```typescript async fetchTemplates() - Fetches all templates from backend - Groups by dayOfWeek for display - Clears isDirty flag async handleSave() - Maps local template state to API payload - Calls adminStore.saveWeekTemplates(payload) - Refreshes templates after save - Shows success/error toast function openAdd() - Opens modal for creating new template - Clears form function openEdit(tpl) - Opens modal in edit mode - Populates form with existing values function submitForm() - Validates form (time and capacity required) - Creates or updates template in memory - Sets isDirty = true (triggers save bar) function toggleTemplate(tpl) - Toggles isActive flag - Sets isDirty = true function deleteTemplate(tpl) - Shows confirmation modal - Removes from array - Sets isDirty = true ``` #### Local State Management ```typescript const templates = ref([]) const loading = ref(false) const saving = ref(false) const isDirty = ref(false) // Tracks unsaved changes const showModal = ref(false) const editTarget = ref(null) const form = ref({ dayIdx: 0, // Selected day index (0-6) startTime: '09:00', endTime: '10:00', capacityStr: '10', }) const grouped = computed(() => { // Groups templates by dayOfWeek for rendering return Object.fromEntries( Object.entries(map).sort(([a], [b]) => Number(a) - Number(b)) ) }) ``` #### Example: Adding a Monday 9AM-10AM class 1. User taps "+ ๆ–ฐๅขžๆ—ถๆฎต" 2. Modal opens, form is reset to defaults 3. User selects "ๅ‘จไธ€" (Monday) from picker 4. User confirms times and capacity 5. New template object is pushed to `templates` array 6. `isDirty` is set to true โ†’ save bar appears 7. User taps "ไฟๅญ˜ๅ…จ้ƒจๆ›ดๆ”น" 8. Store calls `PUT /admin/week-template` with all templates 9. Backend deletes all old templates and creates new ones 10. Frontend refetches and displays updated list --- ### 3. **Slot Adjustment (slot-adjust.vue)** - Temporary Slot Management **File**: `packages/app/src/pages/admin/slot-adjust.vue` **Route**: `/pages/admin/slot-adjust` #### Purpose Handle temporary/manual time slot operations: 1. Add one-off slots for specific dates 2. Close available slots 3. Batch-generate slots from templates #### UI Structure (3 Tabs) ##### Tab 0: "ๆ–ฐๅขžๆ—ถๆฎต" (Add Manual Slot) - Date picker (defaults to today) - Start/End time pickers - Capacity input - Submit button: "ๆ–ฐๅขžๆ—ถๆฎต" - **Endpoint**: `POST /admin/time-slot/manual` ```typescript interface CreateManualSlotDto { date: string // YYYY-MM-DD startTime: string // HH:MM endTime: string // HH:MM capacity?: number // Defaults to DEFAULT_SLOT_CAPACITY (1) } ``` ##### Tab 1: "ๅ…ณ้—ญๆ—ถๆฎต" (Close Slots) - Date picker (defaults to today) - Loads all slots for selected date - Displays slot list with: - Time range - Status badge (OPEN/FULL/CLOSED) - Booked count: "X/Y" - Close button (if not already closed) - Confirmation modal when closing - **Endpoint**: `PUT /admin/time-slot/:id/close` **Slot Status Colors**: ``` OPEN โ†’ Green badge #27ae60 FULL โ†’ Orange badge #e67e22 CLOSED โ†’ Gray badge #999 ``` ##### Tab 2: "ๆ‰น้‡็”Ÿๆˆ" (Batch Generate) - Start date picker - End date picker (defaults to +7 days) - Hint: "ๅฐ†ๆ นๆฎๆŽ’่ฏพๆจกๆฟ๏ผŒ่‡ชๅŠจ็”Ÿๆˆๆ‰€้€‰ๆ—ฅๆœŸ่Œƒๅ›ดๅ†…็š„ๆ—ถๆฎต" - *"Will auto-generate slots for selected date range based on schedule template"* - Submit button: "ๆ‰น้‡็”Ÿๆˆ" - **Endpoint**: `POST /admin/generate-slots` **How it works**: 1. Frontend sends date range to backend 2. Backend fetches all **active** WeekTemplates 3. For each day in range, finds matching templates by weekday 4. Creates TimeSlot records with `source: TEMPLATE` 5. Uses `skipDuplicates: true` to avoid re-generating existing slots ```typescript // Backend example: If templates include: // - Monday: 09:00-10:00, 18:00-19:00 (2 templates) // - Wednesday: 10:00-11:00 (1 template) // // And user selects 2026-04-05 to 2026-04-11: // - Mon 04-06: 2 slots generated // - Wed 04-08: 1 slot generated // Total: 3 slots (if these dates fall in range) ``` #### Key Functions ```typescript async submitAddSlot() - POST /admin/time-slot/manual - Shows success/error toast async loadSlotsForClose() - Fetches slots for closeDate via adminStore.fetchSlotsByDate(date) - Sets slotsLoading flag async closeSlot(slot) - Confirmation modal - PUT /admin/time-slot/:id/close - Reloads slot list async submitGenerate() - POST /admin/generate-slots with date range - Shows toast with count of generated slots ``` #### Local State ```typescript const activeTab = ref(0) // 0=Add, 1=Close, 2=Generate const submitting = ref(false) const slotsLoading = ref(false) // Tab 0: Add form const addForm = ref({ date: formatDate(new Date()), startTime: '09:00', endTime: '10:00', capacityStr: '10', }) // Tab 1: Close slots const closeDate = ref(formatDate(new Date())) const daySlots = ref([]) // Tab 2: Generate form const genForm = ref({ startDate: formatDate(new Date()), endDate: formatDate(new Date(Date.now() + 7 * 86400000)), // +7 days }) ``` --- ### 4. **Admin Store (Pinia)** **File**: `packages/app/src/stores/admin.ts` #### API Methods Related to Scheduling ```typescript // โ”€โ”€ Week templates โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ async fetchWeekTemplates(): Promise // GET /admin/week-template // Returns all templates for current studio // Usage: Gets templates for display in week-template.vue async saveWeekTemplates(templates: WeekTemplateInput[]): Promise // PUT /admin/week-template // Body: { templates: [...] } // Replaces ALL templates with new set (delete all, create new) // Note: Backend uses transaction for atomicity // โ”€โ”€ Time slots โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ async fetchSlotsByDate(date: string): Promise // GET /admin/time-slots?date=YYYY-MM-DD // Returns all slots for a specific date // Used in slot-adjust.vue Tab 1 (close slots) async createManualSlot(dto: CreateManualSlotDto): Promise // POST /admin/time-slot/manual // Creates a one-off time slot // Used in slot-adjust.vue Tab 0 async closeSlot(id: string): Promise // PUT /admin/time-slot/:id/close // Changes slot status from OPEN to CLOSED // Used in slot-adjust.vue Tab 1 async generateSlots(startDate: string, endDate: string): Promise<{ count: number }> // POST /admin/generate-slots // Generates slots from active templates for date range // Used in slot-adjust.vue Tab 2 // Returns: { count: number of newly created slots } // โ”€โ”€ Dashboard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ async fetchDashboardStats(): Promise // GET /admin/stats // Returns: { todayBookings, totalOrders, totalBookings } // Used in index.vue ``` #### API Response Types ```typescript interface AdminStats { todayBookings: number totalOrders: number totalBookings: number } interface WeekTemplate { id: string dayOfWeek: number // 1-7 (ISO weekday) startTime: string // HH:MM endTime: string // HH:MM capacity: number isActive: boolean createdAt: string updatedAt: string } interface TimeSlot { id: string date: string // YYYY-MM-DD startTime: string endTime: string capacity: number bookedCount: number status: TimeSlotStatus // OPEN | FULL | CLOSED source: TimeSlotSource // TEMPLATE | MANUAL templateId: string | null createdAt: string updatedAt: string } ``` --- ## ๐Ÿ”Œ Backend Architecture ### Time Slot Controller **File**: `packages/server/src/time-slot/time-slot.controller.ts` #### Member Endpoints (Public) ``` GET /time-slot/available?date=YYYY-MM-DD - Get available slots for a date - Include booking status for current user GET /time-slot/:id - Get specific slot details ``` #### Admin Endpoints (Requires JWT + ADMIN role) ``` GET /admin/week-template - Returns all WeekTemplates - Ordered by: dayOfWeek ASC, startTime ASC PUT /admin/week-template - Request body: { templates: [...] } - Replaces all templates (transaction-based) - Validation: dayOfWeek 1-7, startTime/endTime strings POST /admin/time-slot/manual - Request body: { date, startTime, endTime, capacity? } - Creates manual slot with source=MANUAL - Capacity defaults to DEFAULT_SLOT_CAPACITY PUT /admin/time-slot/:id/close - Changes slot status to CLOSED - Returns updated slot POST /admin/generate-slots - Generates slots from active templates - Fetches templates where isActive=true - Creates slots for next SLOT_GENERATION_DAYS (14 days by default) - Uses skipDuplicates to make re-runs safe - Returns: { count: number } ``` ### Time Slot Service **File**: `packages/server/src/time-slot/time-slot.service.ts` Key methods: ```typescript async getWeekTemplates(): Promise // Returns all templates sorted by day/time async replaceWeekTemplates(items: Array<{...}>): Promise // Transaction-based replacement: // 1. Delete all existing templates // 2. Create new ones from items array // 3. Return count of created async createManualSlot(dto): Promise // Creates slot with source=MANUAL, status=OPEN async closeSlot(id: string): Promise // Updates status to CLOSED ``` ### Slot Generator Service **File**: `packages/server/src/time-slot/slot-generator.service.ts` Key method: ```typescript async generateSlots(daysAhead: number = 14): Promise // 1. Fetches all WeekTemplates where isActive=true // 2. For each of next N days: // - Calculate ISO weekday (1=Mon, 7=Sun) // - Find matching templates by dayOfWeek // - Create TimeSlot records with source=TEMPLATE, templateId=id // 3. Uses createMany with skipDuplicates=true // 4. Returns count of newly created slots // // Key: Converts JS getDay() (0=Sun) to ISO weekday (1=Mon, 7=Sun) async cleanupExpiredSlots(): Promise // Called by scheduler // Closes all OPEN slots with date < today async checkExpiredMemberships(): Promise // Called by scheduler // Expires memberships past end date or with 0 sessions left async completeBookings(): Promise // Called by scheduler // Marks CONFIRMED bookings as COMPLETED if slot date passed ``` --- ## ๐Ÿ“Š Data Flow: "ๆŽ’่ฏพ่ฎพ็ฝฎ" User Journey ### Scenario: Admin sets up class schedule for next week 1. **Admin opens dashboard** โ†’ `index.vue` - Taps "ๆŽ’่ฏพ่ฎพ็ฝฎ" nav item 2. **Admin navigates to Week Template page** โ†’ `week-template.vue` - `onMounted()` โ†’ `fetchTemplates()` - Frontend: `GET /admin/week-template` - Shows existing templates grouped by day - Example display: ``` ๅ‘จไธ€ 09:00-10:00 10ไบบ [ๅฏ็”จ] [็ผ–่พ‘] [ๅˆ ้™ค] 18:00-19:00 8ไบบ [ๅฏ็”จ] [็ผ–่พ‘] [ๅˆ ้™ค] ๅ‘จไธ‰ 10:00-11:00 12ไบบ [ๅฏ็”จ] [็ผ–่พ‘] [ๅˆ ้™ค] ``` 3. **Admin adds a new class** โ†’ Click "+ ๆ–ฐๅขžๆ—ถๆฎต" - Modal opens - Select day, time, capacity - Click "็กฎ่ฎค" - Template added to local `templates` array - **Save bar appears** at bottom 4. **Admin edits existing template** โ†’ Click "็ผ–่พ‘" - Modal opens with existing values - Modify time/capacity - Click "็กฎ่ฎค" - Updated in local array - Save bar shows if changed 5. **Admin disables a template** โ†’ Click "ๅœ็”จ" - `isActive` flipped to false - Template grayed out - Save bar shows 6. **Admin saves all changes** โ†’ Click "ไฟๅญ˜ๅ…จ้ƒจๆ›ดๆ”น" - Loading state - Frontend: `PUT /admin/week-template` with all templates - Backend transaction: ``` BEGIN TRANSACTION DELETE FROM week_template INSERT INTO week_template (day_of_week, start_time, end_time, capacity, is_active) VALUES (...) COMMIT TRANSACTION ``` - Success toast - Frontend refetches templates - Save bar disappears 7. **Backend scheduler auto-generates slots** - Nightly cron (scheduler module) - Calls `SlotGeneratorService.generateSlots(14)` - Queries active WeekTemplates - For each day in next 14 days: - Checks what templates apply (by ISO weekday) - Creates TimeSlot records - Uses `skipDuplicates` to avoid duplicates on re-run - Example output: ``` date: 2026-04-06 (Monday) 09:00-10:00 source=TEMPLATE templateId=abc123 18:00-19:00 source=TEMPLATE templateId=def456 date: 2026-04-08 (Wednesday) 10:00-11:00 source=TEMPLATE templateId=ghi789 ``` 8. **Members can see and book the generated slots** - Frontend: `GET /time-slot/available?date=2026-04-06` - Members choose a slot and confirm booking --- ## ๐Ÿ“… Constants & Utilities ### Shared Constants **File**: `packages/shared/src/constants.ts` ```typescript export const SLOT_GENERATION_DAYS = 14 // Number of days ahead to generate slots for export const DEFAULT_SLOT_CAPACITY = 1 // Default capacity if not specified (for private lessons) export const WEEKDAY_LABELS = ['', 'ๅ‘จไธ€', 'ๅ‘จไบŒ', 'ๅ‘จไธ‰', 'ๅ‘จๅ››', 'ๅ‘จไบ”', 'ๅ‘จๅ…ญ', 'ๅ‘จๆ—ฅ'] // Index 0 is unused, 1-7 map to weekdays // Used in dropdowns and display export const DEFAULT_CANCEL_HOURS_LIMIT = 2 // Hours before slot to allow free cancellation export const TIME_PERIODS = { MORNING: { label: 'ไธŠๅˆ', start: '06:00', end: '12:00' }, AFTERNOON: { label: 'ไธ‹ๅˆ', start: '12:00', end: '18:00' }, EVENING: { label: 'ๆ™šไธŠ', start: '18:00', end: '22:00' } } export const DATE_SELECTOR_DAYS = 7 ``` ### Format Utilities **File**: `packages/app/src/utils/format.ts` ```typescript formatDate(date: Date | string): string // Converts to YYYY-MM-DD format // Used for date pickers and API calls getWeekdayLabel(date: Date | string): string // Returns Chinese weekday (ๅ‘จไธ€-ๅ‘จๆ—ฅ) isToday(date: Date | string): boolean // Checks if date is today getDateRange(days: number): Array<{ date, weekday, isToday }> // Generates future N days' dates ``` ### Request Utility **File**: `packages/app/src/utils/request.ts` ```typescript function request(options: RequestOptions): Promise // Makes HTTP request with JWT auth // Auto-refreshes token on 401 function get(url: string, data?: Record): Promise function post(url: string, data?: Record): Promise function put(url: string, data?: Record): Promise function del(url: string, data?: Record): Promise // Base URL logic: // - Production: https://focus.richarjiang.com/api // - Development: http://localhost:3000/api ``` --- ## ๐Ÿ” Permission Model **Role**: `UserRole.ADMIN` ### Protected Endpoints All `/admin/*` endpoints require: 1. Valid JWT token 2. Header: `Authorization: Bearer ` 3. User role must be `ADMIN` Protected by: - `@UseGuards(JwtAuthGuard, RolesGuard)` - `@Roles(UserRole.ADMIN)` ### Auth Flow 1. Admin logs in via auth module 2. JWT token returned, stored in `uni.setStorageSync('token')` 3. All requests include token in Authorization header 4. If 401 response: clear token, show login prompt 5. If 4xx/5xx: show error toast --- ## ๐Ÿ› Current Implementation Notes ### Implemented Features โœ… - [x] Week template CRUD (Create, Read, Update via replace) - [x] Manual slot creation - [x] Close individual slots - [x] Batch slot generation from templates - [x] UI for all three slot adjustment tabs - [x] Local state change tracking (isDirty) - [x] Modal form for adding/editing templates - [x] Grouping templates by weekday - [x] Status badges for slots (OPEN/FULL/CLOSED) ### Missing/Stub Features โš ๏ธ - [ ] `fetchDashboardStats()` API endpoint appears to be stubbed - `index.vue` calls it but endpoint not found in backend - May need to implement in studio or payment controller - [ ] No client-side validation errors displayed on API failures - [ ] No confirmation before overwriting all templates - [ ] No undo/restore from past template versions ### Edge Cases to Watch ๐Ÿ” 1. **Timezone handling**: All dates are treated as UTC midnight - Slot generation uses `setUTCHours(0,0,0,0)` - Frontend format displays as YYYY-MM-DD (local string) 2. **Duplicate slot prevention**: - Backend uses `skipDuplicates: true` in createMany - Assumes date + startTime + endTime forms unique key 3. **Template replacement is atomic**: - All templates deleted, all new ones created in transaction - If one row fails, entire operation rolls back 4. **ISO weekday vs JS getDay()**: - Shared code uses ISO: 1=Mon, 7=Sun - Frontend picker displays Chinese labels - Backend slot-generator converts JS getDay() to ISO --- ## ๐Ÿ“ฑ UI Design Patterns ### Colors & Styling - **Primary**: `#1a1a2e` (dark navy) - **Accent**: `#c9a87c` (gold) - **Success**: `#27ae60` (green) - **Warning**: `#e67e22` (orange) - **Danger**: `#c0392b` (red) - **Background**: `#f5f3f0` (light beige) ### Component Patterns 1. **Skeleton loaders**: Shimmer animation for loading states 2. **Save bar**: Fixed bottom bar shows only when changes exist 3. **Toggle buttons**: Color indicates state (on=green, off=orange) 4. **Modals**: Bottom-sheet style with backdrop 5. **Pickers**: WeChat native pickers for date/time 6. **Badges**: Color-coded status indicators --- ## ๐Ÿš€ Deployment & Configuration ### Frontend - WeChat mini-program environment - Base URL logic in `packages/app/src/utils/request.ts`: ```typescript // Production https://focus.richarjiang.com/api // Development http://localhost:3000/api ``` ### Backend - NestJS server on port 3000 - Prisma ORM with database - JWT authentication - Role-based access control (RBAC) --- ## ๐Ÿ“š Related Files Summary | File | Purpose | Type | |------|---------|------| | `admin/index.vue` | Admin dashboard | Component | | `admin/week-template.vue` | Schedule templates | Component โญ | | `admin/slot-adjust.vue` | Manual slot ops | Component | | `stores/admin.ts` | Admin API calls | Store | | `time-slot.service.ts` | Slot business logic | Service | | `slot-generator.service.ts` | Template-based generation | Service | | `time-slot.controller.ts` | API endpoints | Controller | | `week-template.ts` | Type definitions | Type | | `constants.ts` | Shared constants | Config | | `format.ts` | Date/time utilities | Utility | --- ## ๐ŸŽฏ Key Takeaways 1. **"ๆŽ’่ฏพ่ฎพ็ฝฎ"** is the master schedule template management page 2. **Templates are ISO-weekday based** (1=Monday, 7=Sunday) 3. **Slot generation is automated** via backend scheduler, triggered by: - Nightly cron job - Or manual POST to `/admin/generate-slots` endpoint 4. **Save pattern**: Local changes tracked, one "save all" API call with full template array 5. **Timezone**: All operations use UTC midnight as boundaries 6. **Atomicity**: Backend uses Prisma transactions for template replacement 7. **Permissions**: All admin endpoints protected by JWT + ADMIN role guard