|
| 1 | +# Contest Enhancement Features |
| 2 | + |
| 3 | +This document describes the three major enhancements added to the contest system: Team Contests, Categories, and Route Selection per Contest Step. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The contest system has been enhanced with the following features: |
| 8 | +1. **Team Contests** - Users can form teams and compete together |
| 9 | +2. **Categories** - Contest rankings can be organized by age, gender, or custom criteria |
| 10 | +3. **Route Selection per Step** - Each contest step can have its own specific routes |
| 11 | + |
| 12 | +## 1. Team Contests |
| 13 | + |
| 14 | +### Features |
| 15 | +- Enable team mode for any contest |
| 16 | +- Create and manage multiple teams per contest |
| 17 | +- Users can join/leave teams |
| 18 | +- Two team scoring modes: |
| 19 | + - **Unique Routes**: Each route counts once, even if multiple team members climb it |
| 20 | + - **All Climbs**: Count all climbs by team members (route climbed by 3 members = 3x points) |
| 21 | +- Users can only be in one team per contest |
| 22 | + |
| 23 | +### Admin Usage |
| 24 | + |
| 25 | +1. **Enable Team Mode** |
| 26 | + - When creating or editing a contest, check "Enable Team Mode" |
| 27 | + - This activates team features for the contest |
| 28 | + |
| 29 | +2. **Choose Team Points Calculation** |
| 30 | + - When team mode is enabled, select the scoring mode: |
| 31 | + - **Unique Routes Only**: Each route counts once for the team (default) |
| 32 | + - **All Climbs**: Each team member's climb counts separately |
| 33 | + - Example with "All Climbs": Route 1 climbed by 3 members = 3× points, Route 2 climbed by 2 members = 2× points |
| 34 | + |
| 35 | +3. **Manage Teams** |
| 36 | + - Navigate to Admin > Site > Contests |
| 37 | + - Click the team icon (👥) for a contest with team mode enabled |
| 38 | + - Create teams by clicking "Create Team" |
| 39 | + - Add/remove team members |
| 40 | + - Delete teams as needed |
| 41 | + |
| 42 | +### User Experience |
| 43 | + |
| 44 | +1. **Joining a Team** |
| 45 | + - Visit the contest public page |
| 46 | + - Switch to "Team" view mode |
| 47 | + - Browse available teams |
| 48 | + - Click "Join Team" to join |
| 49 | + - Only one team membership per contest is allowed |
| 50 | + |
| 51 | +2. **Viewing Team Rankings** |
| 52 | + - Team rankings show all teams sorted by total points |
| 53 | + - Points calculation depends on the contest's team points mode: |
| 54 | + - **Unique mode**: Points from unique routes climbed by any team member |
| 55 | + - **All mode**: Sum of points from all climbs by all team members |
| 56 | + - In unique mode, if two team members climb the same route, points are only counted once |
| 57 | + - In all mode, each member's climb contributes to the team total |
| 58 | + |
| 59 | +### Database Schema |
| 60 | + |
| 61 | +**teams table:** |
| 62 | +- `id` - Primary key |
| 63 | +- `contest_id` - Foreign key to contests |
| 64 | +- `name` - Team name |
| 65 | +- `timestamps` |
| 66 | + |
| 67 | +**team_user pivot table:** |
| 68 | +- `team_id` - Foreign key to teams |
| 69 | +- `user_id` - Foreign key to users |
| 70 | +- Unique constraint on (team_id, user_id) |
| 71 | + |
| 72 | +**contests table (team-related fields):** |
| 73 | +- `team_mode` - Boolean, enables team features |
| 74 | +- `team_points_mode` - String ('unique' or 'all'), determines scoring method |
| 75 | + |
| 76 | +## 2. Categories |
| 77 | + |
| 78 | +### Features |
| 79 | +- Create unlimited categories per contest |
| 80 | +- Categories can be based on age, gender, or custom criteria |
| 81 | +- Users can join multiple categories |
| 82 | +- Separate rankings for each category |
| 83 | +- Category membership is optional |
| 84 | + |
| 85 | +### Admin Usage |
| 86 | + |
| 87 | +1. **Create Categories** |
| 88 | + - Navigate to Admin > Site > Contests |
| 89 | + - Click the categories icon (◆) for any contest |
| 90 | + - Click "Create Category" |
| 91 | + - Fill in: |
| 92 | + - **Name**: e.g., "Men 18-25", "Women Elite", "Youth" |
| 93 | + - **Type**: Age, Gender, or Custom (optional) |
| 94 | + - **Criteria**: Additional information (optional) |
| 95 | + |
| 96 | +2. **Manage Categories** |
| 97 | + - View category participants |
| 98 | + - Edit category details |
| 99 | + - Delete categories |
| 100 | + |
| 101 | +### User Experience |
| 102 | + |
| 103 | +1. **Joining Categories** |
| 104 | + - Visit the contest public page |
| 105 | + - Switch to "Categories" view mode |
| 106 | + - Browse available categories |
| 107 | + - Click "Join" on categories you want to participate in |
| 108 | + - You can join multiple categories |
| 109 | + |
| 110 | +2. **Viewing Category Rankings** |
| 111 | + - Select a category from the tabs |
| 112 | + - Rankings show only participants in that category |
| 113 | + - Rankings are re-calculated specifically for category members |
| 114 | + |
| 115 | +### Database Schema |
| 116 | + |
| 117 | +**contest_categories table:** |
| 118 | +- `id` - Primary key |
| 119 | +- `contest_id` - Foreign key to contests |
| 120 | +- `name` - Category name |
| 121 | +- `type` - Type: 'age', 'gender', or 'custom' |
| 122 | +- `criteria` - Additional criteria description |
| 123 | +- `timestamps` |
| 124 | + |
| 125 | +**contest_category_user pivot table:** |
| 126 | +- `contest_category_id` - Foreign key to contest_categories |
| 127 | +- `user_id` - Foreign key to users |
| 128 | +- Unique constraint on (contest_category_id, user_id) |
| 129 | + |
| 130 | +## 3. Route Selection per Contest Step |
| 131 | + |
| 132 | +### Features |
| 133 | +- Each contest step can have specific routes assigned |
| 134 | +- Routes are optional - steps use all contest routes if none assigned |
| 135 | +- Easy route selection with hierarchical view (Area > Sector > Line > Routes) |
| 136 | +- Route count shown for each step |
| 137 | + |
| 138 | +### Admin Usage |
| 139 | + |
| 140 | +1. **Manage Contest Steps** |
| 141 | + - Navigate to Admin > Site > Contests |
| 142 | + - Click the steps icon (≡) for any contest |
| 143 | + - Create steps with name, order, and time period |
| 144 | + |
| 145 | +2. **Assign Routes to a Step** |
| 146 | + - In the steps list, click "Manage Routes" for a step |
| 147 | + - A modal shows all contest routes organized by Area > Sector > Line |
| 148 | + - Check/uncheck routes to assign them to the step |
| 149 | + - Changes are saved automatically |
| 150 | + |
| 151 | +3. **Route Assignment Behavior** |
| 152 | + - If a step has routes assigned, only those routes count for that step's ranking |
| 153 | + - If a step has no routes assigned, all contest routes are used |
| 154 | + - This allows flexibility for different contest formats |
| 155 | + |
| 156 | +### User Experience |
| 157 | + |
| 158 | +1. **Viewing Step Information** |
| 159 | + - Contest steps are shown as tabs on the contest page |
| 160 | + - Each step shows its name and status (Active/Upcoming/Past) |
| 161 | + - The route count displayed updates based on step-specific routes |
| 162 | + |
| 163 | +2. **Rankings per Step** |
| 164 | + - Rankings are calculated only for routes assigned to that step |
| 165 | + - Time period is based on the step's start and end times |
| 166 | + - Overall ranking uses all contest routes and the full contest period |
| 167 | + |
| 168 | +### Database Schema |
| 169 | + |
| 170 | +**contest_step_route pivot table:** |
| 171 | +- `id` - Primary key |
| 172 | +- `contest_step_id` - Foreign key to contest_steps |
| 173 | +- `route_id` - Foreign key to routes |
| 174 | +- Unique constraint on (contest_step_id, route_id) |
| 175 | +- `timestamps` |
| 176 | + |
| 177 | +## Combined Usage Examples |
| 178 | + |
| 179 | +### Example 1: Youth Team Competition |
| 180 | +1. Create contest with team mode enabled |
| 181 | +2. Create age category "Youth Under 16" |
| 182 | +3. Create teams for different climbing gyms |
| 183 | +4. Students join teams and the youth category |
| 184 | +5. View rankings in: |
| 185 | + - Team mode: See which gym team is winning |
| 186 | + - Category mode: See youth rankings |
| 187 | + - Individual mode: See all participants |
| 188 | + |
| 189 | +### Example 2: Multi-Stage Contest |
| 190 | +1. Create contest with multiple steps: |
| 191 | + - "Qualification" (first 50 routes) |
| 192 | + - "Semi-Finals" (25 harder routes) |
| 193 | + - "Finals" (10 hardest routes) |
| 194 | +2. Assign specific routes to each step |
| 195 | +3. Create categories: "Men", "Women", "Youth" |
| 196 | +4. Participants can: |
| 197 | + - See their qualification ranking |
| 198 | + - Advance to semi-finals based on qualification |
| 199 | + - View separate men/women/youth rankings |
| 200 | + |
| 201 | +### Example 3: Age Group Competition |
| 202 | +1. Create contest without team mode |
| 203 | +2. Create categories: |
| 204 | + - "Under 14" |
| 205 | + - "14-17" |
| 206 | + - "18-29" |
| 207 | + - "30-44" |
| 208 | + - "45+" |
| 209 | +3. Users join their appropriate age category |
| 210 | +4. View separate rankings for each age group |
| 211 | + |
| 212 | +## Technical Details |
| 213 | + |
| 214 | +### Ranking Calculations |
| 215 | + |
| 216 | +**Individual Rankings:** |
| 217 | +- Sum of points from unique routes climbed |
| 218 | +- Filtered by contest or step time period |
| 219 | +- Filtered by official/free mode |
| 220 | +- Sorted by total points descending |
| 221 | + |
| 222 | +**Team Rankings:** |
| 223 | +- **Unique mode (default)**: Sum of points from unique routes climbed by any team member |
| 224 | + - If multiple team members climb the same route, points counted once |
| 225 | +- **All mode**: Sum of points from all routes climbed by all team members |
| 226 | + - Each team member's climb counted separately |
| 227 | + - Route climbed by N members = N × route points |
| 228 | +- Filtered by contest time period and mode |
| 229 | +- Sorted by total points descending |
| 230 | + |
| 231 | +**Category Rankings:** |
| 232 | +- Individual rankings filtered to only include category members |
| 233 | +- Re-ranked within the category |
| 234 | +- Same point calculation as individual rankings |
| 235 | +- Same point calculation as individual rankings |
| 236 | + |
| 237 | +### Step-Specific Routes |
| 238 | +The ranking logic checks if a step has routes assigned: |
| 239 | +```php |
| 240 | +$routeIds = $step->routes->count() > 0 |
| 241 | + ? $step->routes->pluck('id') // Use step routes |
| 242 | + : $this->contest->routes->pluck('id'); // Use all contest routes |
| 243 | +``` |
| 244 | + |
| 245 | +## API/Livewire Methods |
| 246 | + |
| 247 | +### Contest Model |
| 248 | +- `getTeamRankingForStep($stepId = null)` - Get team rankings |
| 249 | +- `getCategoryRankings($categoryId, $stepId = null)` - Get category-specific rankings |
| 250 | +- `getRankingForStep($stepId = null)` - Enhanced to use step-specific routes |
| 251 | + |
| 252 | +### Team Model |
| 253 | +- `getTotalPoints()` - Calculate team's total points based on contest's team_points_mode |
| 254 | + - Returns sum of unique routes (default) or all climbs (when team_points_mode = 'all') |
| 255 | + |
| 256 | +### Public View Component |
| 257 | +- `setViewMode($mode)` - Switch between 'individual', 'team', 'category' |
| 258 | +- `joinTeam($teamId)` - Join a team |
| 259 | +- `leaveTeam($teamId)` - Leave a team |
| 260 | +- `joinCategory($categoryId)` - Join a category |
| 261 | +- `leaveCategory($categoryId)` - Leave a category |
| 262 | + |
| 263 | +## Testing |
| 264 | + |
| 265 | +Comprehensive test suite included in `tests/Feature/ContestEnhancementsTest.php`: |
| 266 | +- Team mode functionality |
| 267 | +- Team creation and user membership |
| 268 | +- Team ranking calculations (unique mode) |
| 269 | +- Team ranking calculations (all mode with duplicates) |
| 270 | +- Category creation and user membership |
| 271 | +- Category creation and user membership |
| 272 | +- Route assignment to steps |
| 273 | +- Team ranking calculations |
| 274 | +- Category ranking filtering |
| 275 | + |
| 276 | +Run tests with: |
| 277 | +```bash |
| 278 | +php artisan test --filter ContestEnhancementsTest |
| 279 | +``` |
| 280 | + |
| 281 | +## Migration |
| 282 | + |
| 283 | +All features are backward compatible. Existing contests will: |
| 284 | +- Have `team_mode` set to `false` by default |
| 285 | +- Have no teams or categories |
| 286 | +- Have steps with no specific routes (use all contest routes) |
| 287 | + |
| 288 | +To enable new features on existing contests: |
| 289 | +1. Edit the contest and enable team mode if desired |
| 290 | +2. Create categories through the categories manager |
| 291 | +3. Assign routes to existing steps through the steps manager |
0 commit comments