# PlaygroundAdmin Module — Architecture Map

> **Last updated:** 2026-06-10
> **Status:** Living document — incrementally updated as new information is discovered

---

## 1. Purpose & Responsibilities

The PlaygroundAdmin module manages **physical sports facilities (playgrounds)** within the club. It provides:
- Playground configuration CRUD (type, rows/lanes, operating hours, facility link)
- Monthly schedule planning (draft → active lifecycle)
- Hourly slot assignment (coach + academy + group per hour per row)
- Real-time "mirror" view showing what is happening on a playground right now (bookings, training, private matches)
- Attendance tracking (integrated with FacilityGrids)
- Per-playground and club-wide sports dashboards with revenue/utilization analytics
- Dashboard export to HTML reports

It does **NOT** directly manage:
- Reservations/bookings (handled elsewhere, queried by mirror/dashboard)
- Private match bookings (queried by mirror)
- Coaches or academies (Coaches/Academies modules)
- Facility CRUD (Facilities module — playground references a facility)
- Player registrations (SportsActivity module)

---

## 2. Directory & File Structure

```
app/Modules/PlaygroundAdmin/
├── bootstrap.php                          # Permission registration only (no menu, no events)
├── Routes.php                             # Web routes (27 routes)
├── Controllers/
│   ├── PlaygroundController.php          # CRUD + toggle active
│   ├── PlaygroundScheduleController.php  # Monthly schedule CRUD + slot editor
│   ├── PlaygroundMirrorController.php    # Real-time playground mirror view
│   ├── PlaygroundAttendanceController.php # Attendance recording + reminders
│   ├── PlaygroundDashboardController.php # Per-playground dashboard + export
│   └── ClubSportsDashboardController.php # Club-wide sports dashboard + export
├── Models/
│   ├── PlaygroundConfiguration.php       # Main model (types, search, row types)
│   ├── PlaygroundMonthlySchedule.php     # Schedule model (working days, hours)
│   └── PlaygroundScheduleSlot.php        # Slot model (date/hour/row assignment)
├── Services/
│   ├── ClubDashboardService.php          # Analytics queries (revenue, bookings, attendance, trends)
│   ├── DashboardExportService.php        # HTML report generation
│   └── PlaygroundMirrorService.php       # Mirror state builder (reservations + matches + training slots)
└── Views/
    ├── index.php                         # Playground list with type filter
    ├── create.php                        # Create playground form
    ├── show.php                          # Playground details
    ├── edit.php                          # Edit playground form
    ├── schedules.php                     # Monthly schedule list for a playground
    ├── schedule_edit.php                 # Create/edit schedule form
    ├── schedule_view.php                 # View schedule with daily slots
    ├── slot_editor.php                   # Edit a single hour slot (assign coach/academy)
    ├── mirror.php                        # Real-time mirror view
    ├── attendance.php                    # Attendance recording page
    ├── dashboard.php                     # Per-playground analytics dashboard
    └── club_dashboard.php               # Club-wide sports analytics
```

---

## 3. Database Schema (Production — Source of Truth)

### 3.1 `playground_configurations` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | int unsigned | NO | PRI | auto_increment | |
| facility_id | int unsigned | NO | MUL | | FK to facilities |
| playground_name_ar | varchar(200) | NO | | | Arabic name |
| playground_type | varchar(30) | NO | MUL | | football/tennis/bowling/etc. |
| has_rows | tinyint(1) | NO | | 0 | Whether playground has lanes/rows |
| row_count | int unsigned | YES | | NULL | Number of rows (bowling, swimming) |
| row_label_ar | varchar(50) | NO | | 'حارة' | Row label (lane, track, etc.) |
| grid_id | int unsigned | YES | | NULL | FK to facility_grids (for attendance) |
| operating_hours_json | json | YES | | NULL | `{start: 7, end: 22}` |
| notes | text | YES | | NULL | |
| is_active | tinyint(1) | NO | | 1 | Active flag |
| created_by | int unsigned | YES | | NULL | FK to employees |
| created_at | datetime | NO | | CURRENT_TIMESTAMP | |
| updated_at | datetime | YES | | NULL | |

### 3.2 `playground_monthly_schedules` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | int unsigned | NO | PRI | auto_increment | |
| playground_id | int unsigned | NO | MUL | | FK to playground_configurations |
| plan_month | date | NO | MUL | | Target month (YYYY-MM-01) |
| plan_name_ar | varchar(200) | NO | | | Schedule name |
| status | varchar(20) | NO | MUL | 'draft' | draft/active |
| working_days_json | json | YES | | NULL | Array of weekday numbers |
| day_start_time | time | NO | | 07:00:00 | Operating start |
| day_end_time | time | NO | | 22:00:00 | Operating end |
| notes | text | YES | | NULL | |
| created_by | int unsigned | YES | | NULL | FK to employees |
| activated_at | datetime | YES | | NULL | When made active |
| created_at | datetime | NO | | CURRENT_TIMESTAMP | |
| updated_at | datetime | YES | | NULL | |

### 3.3 `playground_schedule_slots` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| schedule_id | int unsigned | NO | MUL | | FK to playground_monthly_schedules |
| slot_date | date | NO | | | Specific date |
| slot_hour | time | NO | | | Hour block (e.g., 09:00:00) |
| row_number | int unsigned | YES | | NULL | Row/lane number (for multi-row) |
| coach_id | int unsigned | YES | MUL | NULL | FK to coaches |
| academy_id | int unsigned | YES | MUL | NULL | FK to academies |
| group_type | varchar(30) | YES | | NULL | Group classification |
| max_trainees | int unsigned | YES | | NULL | Capacity for this slot |
| age_group_ar | varchar(50) | YES | | NULL | Target age range |
| gender | varchar(10) | YES | | NULL | Gender restriction |
| notes | text | YES | | NULL | |
| created_at | datetime | NO | | CURRENT_TIMESTAMP | |
| updated_at | datetime | YES | | NULL | |

### 3.4 Related Tables (queried by PlaygroundAdmin)

| Table | Owner Module | Relationship |
|-------|--------------|--------------|
| facilities | Facilities | facility_id FK in configurations |
| coaches | Coaches | coach_id FK in slots |
| academies | Academies | academy_id FK in slots |
| reservations | (Bookings?) | Queried by mirror/dashboard (facility_id) |
| private_match_bookings | (PrivateMatch?) | Queried by mirror (facility_id) |
| free_time_entries | (FreeTime?) | Queried by club dashboard |
| facility_attendance | FacilityGrids | Queried by dashboard (grid_id) |
| facility_grids | FacilityGrids | Referenced via grid_id |
| training_sessions | SportsActivity | Queried by dashboard (coach activity) |

---

## 4. Routes (27 total)

### Playground CRUD (7 routes)
| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /playgrounds | PlaygroundController@index | playground.view |
| GET | /playgrounds/create | PlaygroundController@create | playground.manage |
| POST | /playgrounds | PlaygroundController@store | playground.manage |
| GET | /playgrounds/{id} | PlaygroundController@show | playground.view |
| GET | /playgrounds/{id}/edit | PlaygroundController@edit | playground.manage |
| POST | /playgrounds/{id} | PlaygroundController@update | playground.manage |
| POST | /playgrounds/{id}/toggle | PlaygroundController@toggle | playground.manage |

### Mirror View (3 routes)
| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /playgrounds/{id}/mirror | PlaygroundMirrorController@mirror | playground.view |
| GET | /playgrounds/{id}/mirror/{date} | PlaygroundMirrorController@mirrorDate | playground.view |
| GET | /playgrounds/{id}/mirror/state | PlaygroundMirrorController@mirrorState | playground.view |

### Monthly Schedules (8 routes)
| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /playgrounds/{id}/schedules | schedules | playground.schedule |
| GET | /playgrounds/{id}/schedules/create | createSchedule | playground.schedule |
| POST | /playgrounds/{id}/schedules | storeSchedule | playground.schedule |
| GET | /playgrounds/{id}/schedules/{schedId} | showSchedule | playground.schedule |
| GET | /playgrounds/{id}/schedules/{schedId}/edit | editSchedule | playground.schedule |
| POST | /playgrounds/{id}/schedules/{schedId} | updateSchedule | playground.schedule |
| GET | /playgrounds/{id}/schedules/{schedId}/slots/{date}/{hour} | slotEditor | playground.schedule |
| POST | /playgrounds/{id}/schedules/{schedId}/slots/{date}/{hour} | saveSlot | playground.schedule |

### Attendance (3 routes)
| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /playgrounds/{id}/attendance | index | playground.attendance |
| POST | /playgrounds/{id}/attendance | store | playground.attendance |
| POST | /playgrounds/{id}/attendance/remind | remind | playground.attendance |

### Dashboards (4 routes)
| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /playgrounds/{id}/dashboard | dashboard | playground.dashboard |
| GET | /playgrounds/{id}/dashboard/export | export | playground.dashboard |
| GET | /sports-dashboard | ClubSportsDashboardController@index | playground.dashboard |
| GET | /sports-dashboard/export | ClubSportsDashboardController@export | playground.dashboard |

---

## 5. Events Dispatched

| Event | Payload | Dispatched From |
|-------|---------|-----------------|
| `facility.payment_reminder` | `{entity_type: 'coach', entity_id, name, phone}` | PlaygroundAttendanceController::remind |

---

## 6. Events Consumed

None — bootstrap.php only registers permissions, no event listeners.

---

## 7. Cross-Module Dependencies

### 7.1 PlaygroundAdmin IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| FacilityGrids | `FacilityAttendanceService` — for attendance recording and retrieval |

### 7.2 Other modules that reference PlaygroundAdmin:

None found — PlaygroundAdmin is a leaf module with no inbound dependencies.

---

## 8. Permissions

| Key | Description (AR) | Description (EN) |
|-----|------------------|------------------|
| playground.view | عرض الملاعب | View Playgrounds |
| playground.manage | إدارة الملاعب | Manage Playgrounds |
| playground.schedule | جدولة الملاعب | Schedule Playgrounds |
| playground.attendance | حضور الملاعب | Playground Attendance |
| playground.dashboard | لوحة تحكم الملاعب | Playground Dashboard |

**Note:** No sidebar menu registered in bootstrap.php.

---

## 9. Core Business Flows

### 9.1 Playground Configuration

```
1. Create playground → requires facility_id, name, type
2. Type determines if has_rows (bowling, swimming → has lanes)
3. Row count and label configurable
4. grid_id links to FacilityGrids for attendance tracking
5. Toggle active/inactive without deletion
```

### 9.2 Monthly Schedule Lifecycle

```
1. Create schedule (draft) → playground_id, plan_month, working_days, start/end times
2. Assign slots: for each date + hour + row → assign coach/academy
3. Activate schedule → status='active', activated_at set
4. Only one active schedule per playground used for mirror/attendance
```

### 9.3 Slot Assignment (saveSlot)

```
1. DELETE all existing slots for schedule + date + hour
2. INSERT new slots from form data
3. Each slot = row_number + coach_id + academy_id + group_type + capacity + age/gender
```

### 9.4 Mirror View (Real-Time State)

```
1. Load playground and determine operating hours
2. For each hour in operating range:
   - Query reservations overlapping that hour (from reservations table)
   - Query private_match_bookings overlapping that hour
   - Query active schedule slots for that hour
3. Build grid: each hour = {reservations[], private_matches[], training_slots[], status}
4. Status: 'free' | 'booked' | 'training'
```

### 9.5 Club Dashboard Analytics

Aggregates across all playgrounds:
- Total revenue (reservations + free_time_entries)
- Bookings by facility
- Unique players
- Active coaches
- Average attendance rate
- Playground-level stats
- Monthly trend

Filter options: daily, weekly, monthly, yearly, 3years, 5years, custom range.

---

## 10. Playground Types

| Type Code | Arabic Label | Has Rows |
|-----------|-------------|----------|
| football | كرة قدم | No |
| tennis | تنس | No |
| bowling | بولينج | Yes |
| playstation | بلاي ستيشن | No |
| swimming | سباحة | Yes |
| basketball | كرة سلة | No |
| paddle | بادل | No |
| gym | جيم | No |
| ping_pong | تنس طاولة | No |

---

## 11. High-Risk Areas

### 11.1 Slot Save — Delete-and-Reinsert Pattern
- `saveSlot` deletes ALL slots for a schedule+date+hour then re-inserts
- No transaction wrapping — partial failure leaves gaps
- Concurrent editors on same slot could cause data loss

### 11.2 Mirror State Relies on Multiple External Tables
- Queries `reservations`, `private_match_bookings`, and `playground_schedule_slots`
- If any table schema changes, mirror breaks
- Time comparison uses string comparison (`start_time < nextHour AND end_time > hourStr`)
- Edge case: bookings spanning midnight or crossing operating hours

### 11.3 Dashboard Performance
- `ClubDashboardService::getClubWideData()` runs 7 separate SQL queries
- No caching — every dashboard load hits live DB
- `monthly_trend` query scans entire reservations table in date range
- 5-year range could be very slow on large datasets

### 11.4 Attendance Integration Coupling
- Attendance depends on `playground.grid_id` being set
- If grid_id is NULL, attendance features return empty
- `FacilityAttendanceService` is imported from FacilityGrids module

### 11.5 No Schedule Conflict Detection
- Multiple active schedules per playground are not prevented (code only queries `LIMIT 1`)
- Slot assignment doesn't check coach scheduling conflicts
- No validation that assigned coach is available on that date/hour

---

## 12. Known Patterns & Gotchas

1. **No sidebar menu** — module is accessible via direct URL or linked from other views
2. **bootstrap.php is minimal** — only registers permissions (no menu, no events)
3. **Static model properties** — `PlaygroundConfiguration` uses instance `$table` not `static $table` (non-standard vs other modules)
4. **LAST_INSERT_ID() pattern** — used in store methods instead of `$db->insert()` return value
5. **Export is HTML** — dashboard export generates downloadable HTML (not PDF/Excel)
6. **Mirror state endpoint** — `/mirror/state` returns JSON (AJAX polling for real-time updates)
7. **Operating hours from JSON** — `operating_hours_json` stores `{start, end}` integers, defaults to 7-22
8. **Row label is Arabic** — `row_label_ar` defaults to 'حارة' (lane), configurable per playground type
9. **Remind action** — dispatches `facility.payment_reminder` for coaches in active schedule (naming is misleading — it's a general reminder, not payment-specific)
