diff --git a/docs/TODO.md b/docs/TODO.md index 50de501..761747e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -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