# CC Soccer D11 - Session Handoff

**Status:** Roster Builder UI complete with drag-and-drop and auto-save persistence

**Last Updated:** December 30, 2024

---

## **Current State - WORKING ✅**

### **Environment:**
- DDEV running Drupal 11
- Database: MySQL
- Git repo: Active, pushed to GitHub
- Site URL: http://ccsoccer-d11.ddev.site/
- Mailhog: http://ccsoccer-d11.ddev.site:8026

### **Core Systems Complete:**
- ✅ All 7 custom entities implemented and working
- ✅ Registration entity (custom, not contrib)
- ✅ Waitlist entity with override system
- ✅ Invitation entity for group/team invitations
- ✅ Commerce integration (products auto-created for seasons/tournaments)
- ✅ Notification system (email via Symfony Mailer, SMS via Clickatell)
- ✅ Complete checkout flow with custom panes
- ✅ Group management for seasons
- ✅ Team management for tournaments
- ✅ Order completion subscriber creating registrations
- ✅ Roles & permissions system with access control
- ✅ Captain access control for tournament teams
- ✅ **Roster Builder with drag-and-drop UI and auto-save**
- ✅ **Team Balancer algorithm for auto-generating balanced rosters**

### **Admin Pages Working:**
- `/admin/ccsoccer/season/{season}/roster` - **Roster Builder UI (NEW)**
- `/admin/ccsoccer/seasons` - Season management
- `/admin/ccsoccer/teams` - Team list
- `/admin/ccsoccer/registrations` - Registration list
- `/admin/ccsoccer/game-status` - Game status updates

### **User-Facing Pages Working:**
- `/register` - Main registration page with state-based cards
- `/my-registrations` - User's active registrations
- `/my-group/{registration}` - Group/team management
- `/tournament/{tournament}/team/{team}/manage` - Captain team management (UI TODO)

### **Test Data System:**
```bash
# Seed comprehensive test data (7 seasons + 3 tournaments)
ddev drush ccs-seed --test --force

# Seed with test users (board_test, slofriendly_test, etc)
ddev drush ccs-seed --users

# Test user credentials:
Username: testuser0 (or board_test, slofriendly_test, etc)
Password: password
```

---

## **Major Accomplishment This Session**

### **Implemented Roster Builder with Drag-and-Drop UI**

**Goal:** Build an admin interface to manually assign players to teams, with auto-balancing algorithm and persistent storage.

**Implementation:**

#### **1. Roster Builder Form (`RosterBuilderForm.php`)**

Full-featured drag-and-drop interface:
- **Sticky Workbench panel** - Shows unassigned players, stays visible while scrolling
- **Team columns** - Grid of teams with player cards
- **Live statistics** - Player count, avg skill, avg age per team
- **Group visualization** - Grouped players displayed in connected containers
- **Skill filter** - Filter player visibility by skill level (1-5)

**Route:** `/admin/ccsoccer/season/{season}/roster`

#### **2. Auto-Save on Drag/Drop**

Changes persist immediately to `Registration.team` field:
- No "Save" button needed
- Refresh page → assignments persist
- Brief toast notification shows "Saving..." then "Saved"

**Key Change:** Moved from `Team.players` field to `Registration.team` field for persistence.

#### **3. Team Balancer Service (`TeamBalancerService.php`)**

Algorithm for auto-generating balanced rosters:

**Primary Factor:** Skill level balance
- Uses `User.field_skill_level` (admin-assigned, 0-5)
- Falls back to `Registration.self_score` (player self-assessment, 1-5)
- Default: 3 if neither set

**Secondary Factor:** Age distribution
- Calculated from `User.field_dob`
- Default weight: 90% skill / 10% age

**Hard Constraints:**
- Groups stay together (players with same `group_id`)
- Coed leagues: minimum 1 woman per team
- Maximum 1 goalie preference per team
- Even player distribution across teams

**Team Count:**
- Auto-calculated: `ceil(total_players / league.team_size)`
- Admin can override with specific number

#### **4. Group Display**

Players in groups are visually connected:
- Yellow-bordered container wrapping grouped players
- Clickable `⋮⋮⋮` link at bottom opens group management page
- Dragging group moves all members together

#### **5. Drush Commands**

```bash
# Preview roster suggestions (dry run)
ddev drush ccsoccer:suggest-rosters 8

# Apply roster suggestions
ddev drush ccsoccer:suggest-rosters 8 --apply

# Clear all assignments to workbench
ddev drush ccsoccer:suggest-rosters 8 --clear

# Override number of teams
ddev drush ccsoccer:suggest-rosters 8 --teams=14

# Adjust balancing weights
ddev drush ccsoccer:suggest-rosters 8 --skill-weight=80 --age-weight=20

# View current roster state
ddev drush ccsoccer:roster-state 8

# Add test data (gender, age, skill) to users
ddev drush ccsoccer:enhance-test-data
```

---

## **Files Created/Modified This Session**

### **New Files:**

**Form:**
- `src/Form/RosterBuilderForm.php` - Main drag-and-drop UI form

**Controller:**
- `src/Controller/RosterBuilderController.php` - AJAX endpoints (move, save, suggest)

**Service:**
- `src/Service/TeamBalancerService.php` - Roster balancing algorithm

**Assets:**
- `js/roster-builder.js` - Drag-and-drop with auto-save
- `css/roster-builder.css` - UI styling

### **Modified Files:**

**Routing:**
- `ccsoccer.routing.yml` - Added routes:
  - `ccsoccer.roster_builder` - Main UI form
  - `ccsoccer.roster_builder_move` - Auto-save endpoint
  - `ccsoccer.roster_builder_save` - Batch save (compatibility)
  - `ccsoccer.roster_builder_suggest` - Run algorithm

**Libraries:**
- `ccsoccer.libraries.yml` - Added `roster-builder` library

**Services:**
- `ccsoccer.services.yml` - Registered `ccsoccer.team_balancer` service

**Drush Commands:**
- `src/Drush/Commands/CcsoccerCommands.php` - Added roster commands

**Entity:**
- `src/Entity/Registration.php` - Added `self_score` field

**Subscriber:**
- `src/EventSubscriber/OrderCompleteSubscriber.php` - Saves `self_score` on checkout

---

## **Architecture Decisions**

### **Persistence: Registration.team vs Team.players**

**Decision:** Store team assignments in `Registration.team` field, not `Team.players`.

**Rationale:**
- Registration is the source of truth for player→team relationship
- Enables auto-save without "Save Changes" button
- Survives page refresh
- Each Registration has exactly one team (or NULL for workbench)
- No need to sync two fields

**Implementation:**
- `getRosterState()` reads from Registration.team
- `applyAssignments()` saves to Registration.team
- `clearRosters()` sets Registration.team to NULL
- `move()` endpoint saves individual moves

### **Group Handling**

**Decision:** Groups are displayed as containers, dragged as a unit.

**Rationale:**
- Visual clarity - admin sees grouped players together
- Prevents accidental group separation
- Link to group management page for details

**Implementation:**
- `renderPlayersGrouped()` separates groups from individuals
- `buildGroupContainer()` wraps group members
- JS handles group drag separately from individual drag

### **Skill Source Priority**

**Decision:** Prioritize admin-assigned skill over self-assessment.

**Order:**
1. `User.field_skill_level` (admin-assigned, 0-5)
2. `Registration.self_score` (player self-assessment, 1-5)
3. Default: 3

**Rationale:**
- Admin knowledge is more accurate
- Self-assessment is better than nothing
- Default ensures algorithm always works

---

## **Key Code Patterns**

### **Auto-Save Pattern (JavaScript)**

```javascript
function handleDrop(e) {
  // 1. Move element in DOM immediately
  dropzone.appendChild(elementToMove);
  
  // 2. Update stats immediately
  updateAllStats();
  
  // 3. Save to server asynchronously
  saveMove(playerIds, targetTeamId);
}

function saveMove(playerIds, teamId) {
  fetch(moveUrl, {
    method: 'POST',
    body: JSON.stringify({ player_ids: playerIds, team_id: teamId }),
  })
  .then(response => response.json())
  .then(data => {
    showStatus(data.success ? 'Saved' : 'Error', data.success ? 'success' : 'error');
  });
}
```

### **Reading from Registration.team (PHP)**

```php
// In getRosterState()
foreach ($registrations as $reg) {
  $team_id = $reg->get('team')->target_id;
  if ($team_id) {
    $players_by_team[$team_id][] = $player_data;
  } else {
    $workbench_players[] = $player_data;
  }
}
```

### **Balancing Algorithm Overview**

```php
// Phase 1: Distribute groups (round-robin across teams)
foreach ($groups as $group) {
  $best_team = findBestTeamForUnit($group, $team_states);
  assignToTeam($group, $best_team);
}

// Phase 2: Fill with individuals (skill-balanced)
foreach ($individuals as $player) {
  $best_team = findBestTeamForUnit($player, $team_states);
  assignToTeam($player, $best_team);
}
```

---

## **Testing the Roster Builder**

### **Access the UI:**
```
http://ccsoccer-d11.ddev.site/admin/ccsoccer/season/8/roster
```

### **Test Drag and Drop:**
1. Drag individual player from workbench to team
2. Verify "Saving..." → "Saved" toast appears
3. Refresh page → player should still be on team

### **Test Group Drag:**
1. Find a group (yellow bordered container)
2. Drag entire group to different team
3. All group members should move together
4. Click `⋮⋮⋮` link → should open group management

### **Test Suggest Rosters:**
1. Click "Suggest Rosters" button
2. Page reloads with balanced assignments
3. Check team stats are roughly equal

### **Test Clear All:**
1. Click "Clear All" button
2. Confirm dialog
3. All players move to workbench

### **Verify Persistence:**
```bash
# Check registration team assignments
ddev drush sqlq "SELECT r.id, r.player, r.team, u.name FROM ccsoccer_registration r JOIN users_field_data u ON r.player = u.uid WHERE r.season = 8 AND r.team IS NOT NULL LIMIT 10"
```

---

## **Known Issues / TODO**

### **Roster Builder - Minor Issues:**
- Group link shows encoded characters (`&#8942;`) - works but not pretty in source

### **Captain UI - Not Yet Built:**
**Status:** Access control complete, routes exist, controller methods are stubs

**What's Missing:**
- Team roster display with member details
- Invitation form and handler
- Remove player confirmation and logic
- Team notification form functionality

**Files with TODO Comments:**
- `src/Controller/TournamentController.php` - All methods have detailed TODO
- `src/Form/TournamentTeamNotificationForm.php` - Stub form

### **Schedule Builder - Not Yet Started:**
Route exists but form is stub:
- `/admin/ccsoccer/season/{season}/schedule`

---

## **Commands to Remember**

### **Daily Development:**
```bash
# Start DDEV
ddev start

# Clear cache (do often!)
ddev drush cr

# Fresh test data with users
ddev drush ccs-seed --test --users --force

# Enhance test users with gender/age/skill
ddev drush ccsoccer:enhance-test-data
```

### **Roster Builder:**
```bash
# Open roster builder UI
open http://ccsoccer-d11.ddev.site/admin/ccsoccer/season/8/roster

# Generate rosters via Drush
ddev drush ccsoccer:suggest-rosters 8 --apply

# View current state
ddev drush ccsoccer:roster-state 8

# Clear all assignments
ddev drush ccsoccer:suggest-rosters 8 --clear --apply
```

### **Database Queries:**
```bash
# Check team assignments (Registration.team)
ddev drush sqlq "SELECT r.id, r.player, r.team, t.name as team_name FROM ccsoccer_registration r LEFT JOIN team t ON r.team = t.id WHERE r.season = 8 AND r.team IS NOT NULL"

# Check players in workbench
ddev drush sqlq "SELECT r.id, r.player, u.name FROM ccsoccer_registration r JOIN users_field_data u ON r.player = u.uid WHERE r.season = 8 AND r.team IS NULL"

# Check team stats
ddev drush sqlq "SELECT t.id, t.name, COUNT(r.id) as players FROM team t LEFT JOIN ccsoccer_registration r ON t.id = r.team WHERE t.season = 8 GROUP BY t.id ORDER BY t.name"
```

---

## **Collaboration Notes**

**Team:**
- Caleb - Lead developer
- Andrew (abmeade@hotmail.com) - Roster building and schedule generation

**Git Workflow:**
- **ALWAYS** `git pull` before `git push` (Andrew may have pushed changes)
- Commit working states frequently
- Clear commit messages

**After Pulling Changes:**
```bash
# Clear cache
ddev drush cr

# If entity fields changed, may need database update
ddev drush updb

# Import any new config
ddev drush config:import --partial --source=modules/custom/ccsoccer/config/install -y
```

---

## **Test Data Quick Reference**

### **Season 8 (TEST: Coed League):**
- 121 players registered
- 26 teams available
- Team size target: 8 players
- Type: Coed (requires women on each team)

### **Season 9 (TEST: Mens 35+ League):**
- Team size target: 14 players
- Type: Mens35+ (no gender requirement)

### **Test User Credentials:**

| Username | Password | Role | Can Access Roster Builder? |
|----------|----------|------|---------------------------|
| `admin` | (existing) | Administrator | ✅ Yes |
| `board_test` | `password` | Board Member | ❌ No (view reports only) |
| `slofriendly_test` | `password` | Slofriendly | ❌ No (tournaments only) |

**Note:** Roster Builder requires `generate rosters` permission (admin only by default).

---

## **Success Metrics**

**This Session:**
- ✅ Roster Builder form with drag-and-drop UI
- ✅ Auto-save to Registration.team field
- ✅ Team Balancer algorithm working
- ✅ Group visualization with management link
- ✅ Live team statistics
- ✅ Drush commands for roster operations
- ✅ Persistent storage survives page refresh
- ✅ Skill filter for player visibility

**Next Session Priorities:**
1. Schedule Builder UI
2. Captain team management UI
3. Test with real season data

---

## **Important Reminders**

- **Registration.team is source of truth** - not Team.players
- **Auto-save on drag/drop** - no Save button needed
- **Groups drag as a unit** - can't separate grouped players in UI
- **Suggest Rosters clears first** - existing assignments are replaced
- **Test with Season 8** - has most test data (121 players)

---

**End of Handoff - Roster Builder Complete**

Current State: Drag-and-drop roster builder with auto-save working
Next Priority: Schedule Builder or Captain UI
Breaking Point: Clean - roster builder fully functional
