# SportsDashboard Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The SportsDashboard module provides **analytics and KPI dashboards** for the sports/activity operations at three levels of granularity:
- **Club level** — overall revenue, player demographics, discipline breakdown, facility utilization
- **Sport/Discipline level** — revenue, players, coaches, sessions, and linked academies for a specific sport
- **Facility level** — revenue, bookings, utilization rate, popular time slots for a specific facility

It also provides:
- Period-over-period revenue comparison (current vs. previous period)
- PDF export of dashboard data at any level
- Date range filtering (day, week, month, year, 3-year, 5-year, custom)

This is a **read-only analytics module** — it does NOT create or modify any data. All data comes from tables owned by other modules.

---

## 2. Directory & File Structure

```
app/Modules/SportsDashboard/
├── bootstrap.php                              # Permissions only (no menu, no events)
├── Routes.php                                 # 4 routes
├── Controllers/
│   └── SportsDashboardController.php         # Club, sport, facility dashboards + PDF export
├── Services/
│   └── DashboardMetricsService.php           # All analytics queries
└── Views/
    ├── club_dashboard.php                    # Club-wide analytics view
    ├── sport_dashboard.php                   # Single sport analytics view
    └── facility_dashboard.php                # Single facility analytics view
```

---

## 3. Database Tables Queried (not owned)

This module does NOT own any tables. It queries the following tables from other modules:

| Table | Owner Module | Data Retrieved |
|-------|--------------|----------------|
| payments | Payments | Revenue (filtered by type: activity_booking, pool_booking, sports_subscription) |
| players | PlayerAffairs | Demographics (member vs non-member counts) |
| disciplines | SportsActivity | Sport/discipline catalog for drill-down |
| activity_subscriptions | SportsActivity | Active players per discipline |
| facilities | Facilities | Facility list, names |
| facility_time_slots | Facilities | Total available slots (for utilization calc) |
| facility_zone_schedules | SportsActivity | Session counts per discipline/coach |
| reservations | Reservations | Booking counts, popular time slots, revenue |
| academies | Academies | Linked academies per discipline |
| academy_enrollments | Academies | Player counts per academy |
| coaches | Coaches | Coach list for discipline drill-down |

---

## 4. Routes

| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /sports-dashboard | clubLevel | sports_dashboard.view |
| GET | /sports-dashboard/sport/{id} | sportLevel | sports_dashboard.view |
| GET | /sports-dashboard/facility/{id} | facilityLevel | sports_dashboard.view |
| GET | /sports-dashboard/export | export (PDF) | sports_dashboard.export |

---

## 5. Events

This module does NOT dispatch or consume any events. It is purely read-only.

---

## 6. Cross-Module Dependencies

### 6.1 SportsDashboard IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Shared | `PdfExportService` — renders HTML to PDF for export |

All other data access is via direct SQL queries to tables owned by other modules (see section 3).

### 6.2 Other modules that reference SportsDashboard:

| Module | What it references |
|--------|-------------------|
| Disciplines | Has its own `SportsDashboardController` at the same `/sports-dashboard` route |
| PlaygroundAdmin | Has its own `ClubSportsDashboardController` at `/sports-dashboard` route |

**Route collision warning:** Three modules register routes at `/sports-dashboard`:
1. `SportsDashboard/Controllers/SportsDashboardController` (this module)
2. `Disciplines/Controllers/SportsDashboardController`
3. `PlaygroundAdmin/Controllers/ClubSportsDashboardController`

Only the **last-loaded** module's route wins (depends on bootstrap order). This is a known conflict.

---

## 7. Permissions

| Key | Description |
|-----|-------------|
| sports_dashboard.view | View sports dashboard at all levels |
| sports_dashboard.export | Export dashboard data as PDF |

---

## 8. Key Metrics & Calculations

### 8.1 Revenue
```
Source: payments table
Filter: payment_type IN ('activity_booking', 'pool_booking', 'sports_subscription')
        AND is_voided = 0
        AND payment_date BETWEEN start AND end
Optional: related_entity_type='facility' + related_entity_id (for facility-level)
          related_entity_type='discipline' + related_entity_id (for sport-level)
```

### 8.2 Period Comparison
```
Current period: [start, end]
Previous period: [end - duration - 1 day, start - 1 day]
Change %: (current - previous) / previous * 100
Direction: 'up' if change >= 0, else 'down'
```

### 8.3 Player Demographics
```
Source: players table (is_archived=0)
Split: player_type = 'member' vs 'non_member'
Optional discipline filter: players IN (active activity_subscriptions for that discipline)
```

### 8.4 Facility Utilization
```
Booked slots = COUNT(reservations WHERE status != 'cancelled')
Total slots = facility_time_slots COUNT * days in period
Utilization = booked_slots / total_slots * 100
```

### 8.5 Popular Time Slots
```
Source: reservations (not cancelled)
Group by: HOUR(start_time)
Metrics: COUNT(*) as booking_count, SUM(total_amount) as revenue
Limit: Top 10
```

---

## 9. Date Range Presets

| Period Key | Range |
|-----------|-------|
| day | Single day |
| week | End - 7 days |
| month | End - 1 month (default) |
| year | End - 1 year |
| 3year | End - 3 years |
| 5year | End - 5 years |
| custom | User-specified from/to |

---

## 10. High-Risk Areas

### 10.1 Route Collision
- Three modules compete for `/sports-dashboard` route
- Actual behavior depends on module load order (alphabetical sort of module directories)
- This creates confusion about which controller handles requests

### 10.2 disciplines Table Does Not Exist
- `sportLevel()` queries `disciplines` table: `SELECT * FROM disciplines WHERE id = ?`
- The actual table in the database is `sa_disciplines` (SportsActivity prefix)
- This query will fail with a MySQL error if this controller is ever reached
- The `getDisciplineBreakdown()` also queries `disciplines` table — same issue

### 10.3 Performance Concerns
- `getFacilityUtilization()` runs N+1 queries: one for all facilities, then one per facility for time_slots
- `getPopularTimeSlots()` does full table scans without proper indexing
- No caching layer — every page load runs all analytics queries fresh

### 10.4 PdfExportService Dependency
- Export functionality depends on `App\Shared\Services\PdfExportService`
- If that service requires external binaries (wkhtmltopdf, etc.), PDF export may fail silently

### 10.5 Hardcoded Payment Types
- Revenue calculation filters on `'activity_booking','pool_booking','sports_subscription'`
- If new payment types are added (e.g., for new sports services), they won't appear in dashboard metrics

---

## 11. Menu Registration

**No sidebar menu registered.** The module's permissions are registered but no menu entry is created in `bootstrap.php`. Access is likely via links from other sports-related modules or a shared dashboard page.

---

## 12. Export Functionality

The export endpoint (`GET /sports-dashboard/export`) generates an inline PDF with:
- Query params: `level` (club/sport/facility), `reference_id`, `period`, `from`, `to`
- Builds RTL Arabic HTML with inline CSS
- Renders KPIs, tables for disciplines/facilities/coaches/time-slots
- Uses `PdfExportService::renderToPdf()` for conversion
- Filename pattern: `sports_dashboard_{level}_{datetime}.pdf`
