# Session Handoff - January 3, 2026

## Current Status

### ✅ Completed This Session (January 3, 2026 - Late Evening)
1. **Masquerade Functionality** - Admin user impersonation using contrib masquerade module
2. **Custom MasqueradeBlock** - Footer block showing form or "masquerading as" state
3. **Admin Restriction Hook** - Prevents masquerading as other administrators
4. **Infrastructure as Code** - Update hook for permission and block placement (no manual steps)
5. **CSRF Token Fix** - Switch back link properly generates CSRF token

### ✅ Completed This Session (January 3, 2026 - Evening)
1. **Schedule Grid Unification** - Unified layout across admin Schedule Builder and public Schedule/My Schedule views
2. **ScheduleGridBuilder Service** - Created shared rendering service with three modes (admin/public/my_schedule)
3. **Phase 1: Schedule Builder Display** - Time/Date headers per slot, Field labels with rowspan, home/away rows
4. **Phase 2: Shared Rendering** - Extracted grid logic to service, created shared CSS
5. **Phase 3: Public Schedule Update** - Updated to use shared service, added /my-schedule route
6. **Navigation Persistence** - Fixed position retention when dragging from workbench
7. **Removed "Fill Empty Slots"** - Simplified to only "Generate New Schedule" button

### ✅ Completed This Session (January 3, 2026 - Morning)
1. **Teams Page Redesign** - Pill-style season tabs, responsive grid, player sorting, user highlighting
2. **Schedule Page Redesign** - Horizontal grid layout matching schedule builder design
3. **Schedule Navigation** - Prev/Next buttons for week navigation
4. **Pill Tab Caching Fix** - Disabled page caching on Teams and Schedule pages
5. **Future Weeks Padding** - Added 10 weeks of "No games" columns after last game

### ✅ Completed Previous Session (January 2, 2026)
1. **Season Entity Boolean Fields Fix** - Removed `setRequired(TRUE)` from visibility checkboxes
2. **Registration Page Filtering** - Only shows seasons with `registration_visible = TRUE`
3. **Menu Cache Invalidation** - Season changes now take effect immediately
4. **Menu Sort Order Fix** - Seasons display in correct `start_date DESC` order
5. **Season View Page** - Built comprehensive admin view page for seasons
6. **Menu Cleanup** - Removed redundant "Add Season" and "Add Tournament" links
7. **Dev Banner Repositioned** - Moved from page_top to admin toolbar integration

---

## Masquerade Functionality ✅ (NEW)

### Overview
Allows administrators to impersonate non-admin users for testing and player support. Uses the contrib `drupal/masquerade` module with a custom footer block.

### Features
- **Footer block** displays masquerade form (when not masquerading) or "You are masquerading as [user]" with switch back link
- **Admin-only** - Only users with `masquerade as any user` permission see the block
- **Cannot masquerade as other admins** - Custom `hook_masquerade_access()` prevents this
- **CSRF protected** - Switch back link includes proper CSRF token
- **Audit logging** - Masquerade module logs all switch events to watchdog
- **Infrastructure as code** - Permission and block placement via update hook

### Files Created/Modified

| File | Action | Description |
|------|--------|-------------|
| `ccsoccer.module` | MODIFIED | Added `ccsoccer_masquerade_access()` hook + theme hook for block |
| `ccsoccer.libraries.yml` | MODIFIED | Added `masquerade` library |
| `ccsoccer.install` | MODIFIED | Added `ccsoccer_update_9016()` + updated `hook_uninstall()` |
| `src/Plugin/Block/MasqueradeBlock.php` | CREATED | Custom block plugin |
| `templates/ccsoccer-masquerade-block.html.twig` | CREATED | Block template |
| `css/masquerade.css` | CREATED | Dark theme styling for footer |

### Key Functions

**`ccsoccer_masquerade_access()` in ccsoccer.module**
```php
/**
 * Implements hook_masquerade_access().
 *
 * Prevents masquerading as administrator users.
 */
function ccsoccer_masquerade_access($user, $target_account) {
  // Prevent masquerading as administrators (except UID 1 can masquerade as anyone).
  if ($user->id() != 1 && $target_account->hasRole('administrator')) {
    return FALSE;
  }
  // Return NULL to let other access checks decide.
  return NULL;
}
```

**`ccsoccer_update_9016()` in ccsoccer.install**
```php
/**
 * Add masquerade permission to administrator role and place masquerade block in footer.
 */
function ccsoccer_update_9016() {
  // Grant masquerade permission to administrators.
  $role = \Drupal\user\Entity\Role::load('administrator');
  if ($role) {
    $role->grantPermission('masquerade as any user');
    $role->save();
  }

  // Create the masquerade block in footer.
  $existing_block = \Drupal\block\Entity\Block::load('ccsoccer_masquerade');
  if (!$existing_block) {
    $block = \Drupal\block\Entity\Block::create([
      'id' => 'ccsoccer_masquerade',
      'theme' => 'olivero',
      'region' => 'footer_bottom',
      'plugin' => 'ccsoccer_masquerade_block',
      'settings' => [
        'id' => 'ccsoccer_masquerade_block',
        'label' => 'Masquerade',
        'label_display' => '0',
        'provider' => 'ccsoccer',
      ],
      'visibility' => [],
      'weight' => 0,
    ]);
    $block->save();
  }

  return t('Added masquerade permission to administrators and placed block in footer.');
}
```

### MasqueradeBlock Key Code

```php
public function build() {
  $is_masquerading = $this->masquerade->isMasquerading();

  if ($is_masquerading) {
    // Generate switch back URL with CSRF token
    $switch_back_url = Url::fromRoute('masquerade.unmasquerade');
    $token = $this->csrfToken->get($switch_back_url->getInternalPath());
    $switch_back_url->setOptions(['query' => ['token' => $token]]);

    return [
      '#theme' => 'ccsoccer_masquerade_block',
      '#is_masquerading' => TRUE,
      '#masquerade_user' => $masquerade_user->getAccountName(),
      '#switch_back_url' => $switch_back_url->toString(),
      // ...
    ];
  }

  // Not masquerading - check permission and show form
  if (!$this->currentUser->hasPermission('masquerade as any user')) {
    return [];
  }

  $masquerade_form = $this->formBuilder->getForm('Drupal\masquerade\Form\MasqueradeForm');
  // ...
}
```

### Template (ccsoccer-masquerade-block.html.twig)

```twig
<div class="ccsoccer-masquerade-block">
  {% if is_masquerading %}
    <div class="masquerade-status">
      <span class="masquerade-label">You are masquerading as</span>
      <span class="masquerade-username">{{ masquerade_user }}</span>
    </div>
    <div class="masquerade-actions">
      <span class="quick-switches-label">Quick switches:</span>
      <ul class="quick-switches-list">
        <li><a href="{{ switch_back_url }}" class="switch-back-link">Switch back</a></li>
      </ul>
    </div>
  {% else %}
    <div class="masquerade-form-wrapper">
      {{ masquerade_form }}
      <div class="masquerade-help">
        Enter the username to masquerade as.
      </div>
    </div>
  {% endif %}
</div>
```

### Composer Dependency

```bash
ddev composer require drupal/masquerade
ddev drush en masquerade
```

### Testing

```bash
# Apply changes
ddev drush cr
ddev drush updb -y

# Test as admin:
# 1. Log in as administrator
# 2. See masquerade form in footer
# 3. Type a non-admin username, click "Switch"
# 4. See "You are masquerading as [user]" with "Switch back" link
# 5. Click "Switch back" to return to admin
# 6. Try to masquerade as another admin - should fail
```

### Git Commit Message

```
feat: Add masquerade functionality for admin user impersonation

- Install and integrate drupal/masquerade contrib module
- Create custom MasqueradeBlock for footer display
- Add hook_masquerade_access() to prevent masquerading as other admins
- Add ccsoccer_update_9016() to grant permission and place block
- Style masquerade UI to match site footer theme

Allows administrators to impersonate non-admin users for testing
and player support. Includes "Switch back" link with CSRF protection.
```

---

## Schedule Grid Unification ✅

### Overview
Unified the schedule grid layout across three views:
- **Admin Schedule Builder** (`/admin/ccsoccer/season/{id}/schedule`) - With drag-drop
- **Public Schedule** (`/schedule`) - View all games, highlights user's team
- **My Schedule** (`/my-schedule`) - Only shows logged-in user's games

### Visual Layout (All Views)
```
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ 6:00 PM │  JAN 6  │  JAN 13 │  JAN 20 │  JAN 27 │  ← Time/Date header row
├─────────┼─────────┼─────────┼─────────┼─────────┤
│         │ Galaxy  │ Quakes⚽│ Dynamo  │ Rapids  │  ← Home team (pink)
│ Field 1 ├─────────┼─────────┼─────────┼─────────┤
│         │Sounders │  Dash   │  Pride  │Sporting │  ← Away team (white)
├─────────┼─────────┼─────────┼─────────┼─────────┤
│         │ Dynamo  │Red Stars│ Galaxy  │  Dash   │
│ Field 2 ├─────────┼─────────┼─────────┼─────────┤
│         │Redbulls │  Fire   │ Rapids  │Angel City│
├─────────┼─────────┼─────────┼─────────┼─────────┤  ← Dark spacer between slots
│ 7:00 PM │  JAN 6  │  JAN 13 │  JAN 20 │  JAN 27 │  ← Next time slot header
└─────────┴─────────┴─────────┴─────────┴─────────┘
```

### Key Features
- **Time/Date header row** repeats at start of each time slot section
- **Field labels** in left column with `rowspan="2"` spanning home+away rows
- **Home team row** has pink background (`#f8d7da`)
- **Away team row** has white background
- **Dark spacer** (4px) between time slot sections
- **⚽ soccer ball icon** next to user's team (logged-in users)
- **"No games"** displayed in bye weeks and future weeks
- **Prev/Next navigation** with auto-scroll to current week

---

## ScheduleGridBuilder Service ✅

**File:** `src/Service/ScheduleGridBuilder.php`

### Service Registration
```yaml
# ccsoccer.services.yml
ccsoccer.schedule_grid_builder:
  class: Drupal\ccsoccer\Service\ScheduleGridBuilder
  arguments: ['@entity_type.manager']
```

### Mode Constants
```php
const MODE_ADMIN = 'admin';       // Drag-drop enabled
const MODE_PUBLIC = 'public';     // View all games, highlight user's team
const MODE_MY_SCHEDULE = 'my_schedule';  // Only user's games shown
```

### Public Methods
| Method | Purpose |
|--------|---------|
| `buildGrid($scheduleState, $allWeeks, $options)` | Main grid HTML generation |
| `calculateAllWeeks($season, $scheduleState)` | Get all weeks including bye/future |
| `calculateCurrentWeekIndex($allWeeks)` | Find current week for auto-scroll |
| `getUserTeamIds($userId)` | Get team IDs for user highlighting |

### Options Array
```php
$options = [
  'mode' => ScheduleGridBuilder::MODE_PUBLIC,
  'user_team_ids' => [123 => TRUE, 456 => TRUE],  // Keys are team IDs
  'season_id' => 99,
  'season_name' => 'Coed Winter 2026',
  'current_week_index' => 5,
  'enable_drag_drop' => FALSE,  // Auto-set based on mode
];
```

---

## Files Created/Modified (Evening Session)

### New Files
| File | Purpose |
|------|---------|
| `src/Service/ScheduleGridBuilder.php` | Shared grid rendering service |
| `css/schedule-grid.css` | Shared grid CSS (navigation, table, colors) |

### Modified Files
| File | Changes |
|------|---------|
| `src/Controller/ContentController.php` | Use ScheduleGridBuilder, add mySchedulePage() |
| `src/Form/ScheduleBuilderForm.php` | Remove "Fill Empty Slots" button |
| `js/schedule-navigation.js` | Updated selectors for `.date-header-cell` |
| `js/schedule-builder.js` | Added navigation persistence on workbench drag |
| `css/schedule-builder.css` | Trimmed to admin-only styles |
| `ccsoccer.routing.yml` | Added `/my-schedule` route |
| `ccsoccer.services.yml` | Added schedule_grid_builder service |
| `ccsoccer.libraries.yml` | Added schedule-grid library |

---

## Routes

### Public Schedule Routes
| Route | Path | Access | Description |
|-------|------|--------|-------------|
| `ccsoccer.schedule_all` | `/schedule` | Public | All games, highlights user's team |
| `ccsoccer.my_schedule_all` | `/my-schedule` | Logged in | Only user's games |
| `ccsoccer.schedule_ical` | `/schedule/{season}/ical` | Public | iCal export |
| `ccsoccer.schedule_pdf` | `/schedule/{season}/pdf` | Public | PDF export |

### Admin Schedule Routes
| Route | Path | Access | Description |
|-------|------|--------|-------------|
| `ccsoccer.schedule_builder` | `/admin/ccsoccer/season/{season}/schedule` | Admin | Schedule Builder form |

---

## CSS Architecture

### Library Dependencies
```yaml
# Schedule Builder (admin) uses both:
schedule-builder:
  css:
    theme:
      css/schedule-grid.css: {}      # Shared styles
      css/schedule-builder.css: {}   # Admin-only (drag-drop, workbench)

# Public Schedule uses:
schedule-grid:
  css:
    theme:
      css/schedule-grid.css: {}      # Shared styles
  js:
    js/schedule-navigation.js: {}

# Masquerade block:
masquerade:
  css:
    theme:
      css/masquerade.css: {}         # Footer masquerade styling
```

### CSS Class Summary
| Class | Purpose |
|-------|---------|
| `.schedule-grid` | Main container |
| `.grid-scroll-container` | Overflow hidden wrapper |
| `.schedule-table` | The table element |
| `.time-date-row` | Row with time + date headers |
| `.time-header-cell` | Left column time (dark red) |
| `.date-header-cell` | Date column headers (grey) |
| `.field-label-cell` | Left column field label (green) |
| `.game-cell.home-team` | Home team cell (pink) |
| `.game-cell.away-team` | Away team cell (white) |
| `.slot-spacer` | Dark gap between time slots |
| `.team-name.draggable` | Admin drag-drop styling |
| `.user-team-icon` | Soccer ball (⚽) icon |

---

## Navigation Persistence Fix ✅

### Problem
When dragging a team from workbench to a game cell in later weeks (e.g., MAR 24), the page would reload and reset to the default position (JAN 6 visible).

### Solution
Store navigation offset in `sessionStorage` before reload, restore on init.

### Implementation (schedule-builder.js)
```javascript
// Before reload (in handleDropOnCell):
sessionStorage.setItem('scheduleBuilderOffset_' + config.seasonId, state.offset.toString());

// On init (in initScheduleGrid):
const savedOffset = sessionStorage.getItem('scheduleBuilderOffset_' + config.seasonId);
if (savedOffset !== null) {
  state.offset = parseInt(savedOffset, 10);
  sessionStorage.removeItem('scheduleBuilderOffset_' + config.seasonId);
}
```

---

## "Fill Empty Slots" Removal ✅

Removed the "Fill Empty Slots" button from Schedule Builder. Now only "Generate New Schedule" is available.

### Files Changed
- `src/Form/ScheduleBuilderForm.php`
  - Removed `$form['actions']['fill']` definition
  - Removed `fillEmptySubmit()` method

---

## Teams Page Redesign ✅

**Route:** `/teams` (authenticated users only)

### Features Implemented:
- **Pill-style season tabs** - Switch between Coed and Men's 35+ leagues
- **Responsive multi-column grid** - 4 columns on large screens, scaling down to 1 on mobile
- **Empty team filtering** - Only shows teams with players assigned
- **Alphabetical player sorting** - Players sorted A-Z within each team
- **Current user highlighting** - Green background on logged-in user's name
- **User's team highlighting** - Border highlight on teams the user belongs to
- **Cache disabled** - `max-age => 0` for proper tab switching

---

## Testing Commands

```bash
# Clear cache after changes
ddev drush cr

# Run database updates (for masquerade setup)
ddev drush updb -y

# Test admin Schedule Builder
# Visit: /admin/ccsoccer/season/100/schedule
# - Check Time/Date headers per slot
# - Check drag-drop works
# - Navigate with Prev/Next
# - Drag from workbench, verify position persists after reload

# Test public Schedule
# Visit: /schedule?season=99
# - Check layout matches admin
# - Check ⚽ icon next to your team
# - Check Prev/Next navigation

# Test My Schedule
# Visit: /my-schedule?season=99 (must be logged in)
# - Only your games should show team names
# - Other cells should be empty (pink/white background only)

# Test Masquerade
# 1. Log in as administrator
# 2. See masquerade form in footer
# 3. Type a non-admin username, click "Switch"
# 4. See "You are masquerading as [user]" with "Switch back" link
# 5. Click "Switch back" to return to admin
# 6. Try to masquerade as another admin - should fail
```

---

## Known Issues / Future Work

### From Previous Sessions (Still Pending)
- **Season-filtered views** - Menu items like View Registrations/Teams/Games go to global collections
- **Tournament menus** - Will add when tournament schedule/roster builder routes exist
- **Additional season actions** - May need Notifications, Reporting, Player Management, Credit Management

### Potential Enhancements
- Add tournament view page similar to season view page
- Add links to filtered registrations/teams/games from season view page
- Add quick stats to season list builder
- Test iCal and PDF export links on schedule page
- Consider adding My Schedule link to user menu

---

## Visibility Flags Behavior Summary

| Flag | Purpose | Where Used |
|------|---------|------------|
| `active` | Season appears in admin menus | `hook_menu_links_discovered_alter()` |
| `registration_visible` | Season appears on public `/register` page | `RegistrationController::available()` |
| `roster_visible` | Players can view team rosters on `/teams` | `ContentController::teamsPage()` |
| `schedule_visible` | Players can view schedule on `/schedule` | `ContentController::schedulePage()` |

---

## Session Continuity

### Current Architecture Patterns
- Public pages in `ContentController.php`
- Shared rendering in `ScheduleGridBuilder` service
- Entity view pages in entity-specific controllers (e.g., `SeasonController.php`)
- Global CSS via `content-pages` library
- Shared schedule CSS via `schedule-grid` library
- Admin-specific schedule CSS via `schedule-builder` library
- Entity-specific CSS via dedicated libraries (e.g., `season-view`)
- Dynamic admin menus via hook in `.module`
- Cache invalidation on entity save for menu updates
- Drupal behaviors with `once()` for JavaScript initialization
- `max-age => 0` for pages with dynamic query parameters
- `sessionStorage` for cross-reload state persistence
- **Infrastructure as code** - Permissions and block placement via update hooks (no manual admin steps)
- **Contrib module integration** - Using masquerade module with custom block wrapper

### For Next Session
- Consider building tournament view page
- May want to add season-specific links to registrations/teams/games
- Continue with registration/tournament features as needed
- Test iCal/PDF export functionality
- Consider adding My Schedule to user menu

---

**End of Session - Masquerade Functionality Complete!**
