docs: add Phase 1.6 Competition Heats System implementation plan

This commit is contained in:
Radosław Gierwiało
2025-11-14 15:21:00 +01:00
parent 61f504fa72
commit 0e5dc34cbf

View File

@@ -6,9 +6,10 @@
## 🎯 CURRENT STATUS
**Phase:** 1.5 (Email & WSDC & Profiles & Security) - ✅ COMPLETED
**Next Phase:** 2 (Core Features) - ⏳ PENDING
**Progress:** ~65% complete
**Phase:** 1.6 (Competition Heats System) - ⏳ IN PROGRESS
**Previous Phase:** 1.5 (Email & WSDC & Profiles & Security & QR Check-in) - ✅ COMPLETED
**Next Phase:** 2 (Core Features - Matches API + Ratings + WebRTC) - ⏳ PENDING
**Progress:** ~68% complete
### ✅ Completed
- Phase 0: Frontend mockup with all views
@@ -79,6 +80,118 @@
---
## 📌 Phase 1.6: Competition Heats System - ⏳ IN PROGRESS
**Estimated Time:** 6-8 hours
**Priority:** HIGH (blocking for proper matchmaking)
**Status:** Design phase completed, ready for implementation
### Business Logic Summary
- Users must declare their competition heats before matchmaking
- One user can compete in multiple divisions/heats (e.g., J&J Novice + Strictly Advanced)
- **Constraint:** Cannot compete in same role in same division+competition type (e.g., cannot have "J&J Novice Leader" twice)
- Role is optional (can be NULL = undeclared)
- Heat numbers: 1-9
- Format example: "J&J NOV 1 L" (Jack & Jill, Novice, Heat 1, Leader)
### Step 1: Database Schema (1-2h) ⏳
- [ ] Create migration for 3 new tables:
- `divisions` - Pre-defined competition divisions
- Columns: id, name (varchar), abbreviation (varchar 3), display_order (int)
- Seed data: Newcomer (NEW), Novice (NOV), Intermediate (INT), Advanced (ADV), All-Star (ALL), Champion (CHA)
- `competition_types` - Pre-defined competition types
- Columns: id, name (varchar), abbreviation (varchar 3)
- Seed data: Jack & Jill (J&J), Strictly (STR)
- `event_user_heats` - User's declared heats for event
- Columns: id, user_id, event_id, division_id, competition_type_id, heat_number (1-9), role (enum: Leader/Follower/NULL), created_at, updated_at
- **UNIQUE constraint:** (user_id, event_id, division_id, competition_type_id, role)
- Foreign keys: user_id → users.id, event_id → events.id, division_id → divisions.id, competition_type_id → competition_types.id
- Indexes: (user_id, event_id), (event_id)
- [ ] Update Prisma schema
- [ ] Run migration
- [ ] Verify seed data
### Step 2: Backend API (2-3h) ⏳
- [ ] Create routes and controllers:
- `GET /api/divisions` - List all divisions (public)
- `GET /api/competition-types` - List all competition types (public)
- `POST /api/events/:slug/heats` - Add/update user's heats (authenticated)
- Input: array of { divisionId, competitionTypeId, heatNumber, role? }
- Validation: unique constraint, heat number 1-9
- Replace all existing heats (upsert pattern)
- `GET /api/events/:slug/heats/me` - Get current user's heats (authenticated)
- `GET /api/events/:slug/heats/all` - Get all users' heats for sidebar (authenticated)
- Returns: userId, username, avatar, heats[]
- `DELETE /api/events/:slug/heats/:id` - Delete specific heat (authenticated)
- [ ] Validation middleware:
- Heat number 1-9
- Role enum (Leader/Follower/NULL)
- Unique constraint enforcement
- User must be event participant
- [ ] Unit tests (CRUD operations, validation, constraints)
### Step 3: Socket.IO Events (0.5h) ⏳
- [ ] Add event: `heats_updated` - Broadcast when user updates heats
- Payload: { userId, username, heats[] }
- Send to all users in event room
- [ ] Update active_users event to include heats data
### Step 4: Frontend Components (2-3h) ⏳
- [ ] Create HeatsBanner component (sticky between header and chat):
- Show only if user has no heats declared
- Form with dynamic heat entries (add/remove)
- Fields per entry: Competition Type (select), Division (select), Heat Number (1-9), Role (optional: Leader/Follower)
- "Save Heats" button → POST /api/events/:slug/heats
- On save success: hide banner, show success message
- [ ] Add "Edit Heats" button in EventChatPage header (next to "Leave Event")
- Opens modal with same form as banner
- Pre-fill with existing heats
- "Update Heats" button
- [ ] Update EventChatPage sidebar (Active Users):
- Display heat badges under username
- Format: "J&J NOV 1 L", "STR ADV 3" (no role if NULL)
- Max 3 visible badges, "+" indicator if more
- Add checkbox: "Hide users from my heats"
- Logic: Hide users with ANY matching (division + competition_type + heat_number)
- Disable UserPlus icon if user has no heats declared
- [ ] Create frontend API methods in services/api.js:
- divisionsAPI.getAll()
- competitionTypesAPI.getAll()
- heatsAPI.saveHeats(slug, heats[])
- heatsAPI.getMyHeats(slug)
- heatsAPI.getAllHeats(slug)
- heatsAPI.deleteHeat(slug, heatId)
- [ ] Socket.IO integration:
- Listen to `heats_updated` event
- Update active users list in real-time
### Step 5: Styling & UX (0.5-1h) ⏳
- [ ] Heat badges design (color-coded by division?)
- [ ] Banner responsive design (mobile + desktop)
- [ ] Modal for editing heats
- [ ] Loading states for heat operations
- [ ] Error handling & validation messages
- [ ] Empty states ("No heats declared yet")
### Step 6: Testing & Edge Cases (0.5-1h) ⏳
- [ ] Test unique constraint violation (frontend + backend)
- [ ] Test filter "Hide users from my heats"
- [ ] Test real-time updates when someone changes heats
- [ ] Test UserPlus button disabled for users without heats
- [ ] Test banner dismissal and re-opening via "Edit Heats"
- [ ] Test multiple heats display in sidebar
- [ ] Test role optional (NULL) handling
### Technical Notes
- **Abbreviations:**
- Divisions: NEW, NOV, INT, ADV, ALL, CHA
- Competition Types: J&J, STR
- Roles: L (Leader), F (Follower), empty (NULL)
- **Display format:** "{CompType} {Div} {Heat} {Role?}" → "J&J NOV 1 L"
- **Future enhancement:** When Matches API is implemented, editing heats with active match requires partner confirmation
---
## 📌 NEXT STEPS - Phase 2: Core Features
**Estimated Time:** 12-15 hours