# Phase 6: Dashboards (Filament Widgets + Manager API)

## Objective
Deliver comprehensive dashboards accessible from **both** Filament admin (widgets) and the restaurant manager mobile API. Combines real-time operational status and financial analytics in a single phase.

---

## Filament Admin Widgets

### 6.1 Restaurant Dashboard Page

**File:** `app/Filament/Pages/RestaurantDashboardPage.php`

A dedicated Filament page (or section within RestaurantResource) with the following widgets:

### 6.2 LiveStatusWidget

**File:** `app/Filament/Widgets/Restaurant/LiveStatusWidget.php`

- Total tables / Available / Occupied / Reserved
- Current covers (sum of seated party sizes)
- Active carts (WAITING_PAYMENT orders)
- Auto-refreshes every 30 seconds (Livewire polling)

### 6.3 TodaySummaryWidget

**File:** `app/Filament/Widgets/Restaurant/TodaySummaryWidget.php`

- Today's revenue (total PAID orders)
- Today's covers (total party_size of COMPLETED reservations)
- Today's reservations (total count by status)
- Comparison with yesterday and same day last week

### 6.4 RevenueChartWidget

**File:** `app/Filament/Widgets/Restaurant/RevenueChartWidget.php`

- Line chart: revenue over selected period (7D, 14D, 30D)
- Period selector
- Comparison with previous period

### 6.5 PopularItemsWidget

**File:** `app/Filament/Widgets/Restaurant/PopularItemsWidget.php`

- Top 10 most sold menu items
- With image, name, category, quantity sold, revenue
- Period filter

### 6.6 AffluenceWidget

**File:** `app/Filament/Widgets/Restaurant/AffluenceWidget.php`

- Bar chart: covers by hour for today
- Heatmap: covers by hour × day of week for selected period

### 6.7 ReservationStatsWidget

**File:** `app/Filament/Widgets/Restaurant/ReservationStatsWidget.php`

- Total reservations, Confirmed %, No-show %, Cancellation %
- Trend sparklines

---

## API Endpoints (Restaurant Manager — Real-time)

### 6.8 Live Status

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/LiveStatusController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/live-status`
- Returns:
```json
{
  "tables": {
    "total": 20,
    "available": 12,
    "occupied": 6,
    "reserved": 2,
    "maintenance": 0
  },
  "covers": {
    "current": 24,
    "max": 80
  },
  "active_carts": 3,
  "pending_reservations": 2,
  "current_service_period": "dinner"
}
```

**Logic:**
- Count tables by status
- Sum party_size of SEATED reservations for current covers
- Count WAITING_PAYMENT orders for active carts
- Count PENDING reservations for today
- Determine current service period from restaurant config

### 6.9 Today Summary

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/TodaySummaryController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/today-summary`
- Returns:
```json
{
  "revenue": { "amount": 345000, "currency": "EUR" },
  "revenue_change_vs_yesterday": 12.5,
  "covers_total": 67,
  "covers_change_vs_yesterday": -3.2,
  "reservations": {
    "total": 15,
    "confirmed": 8,
    "seated": 3,
    "completed": 4,
    "pending": 0,
    "no_show": 0,
    "cancelled": 0
  },
  "average_ticket": { "amount": 5149, "currency": "EUR" },
  "tips_total": { "amount": 18500, "currency": "EUR" }
}
```

### 6.10 Service Snapshot

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/ServiceSnapshotController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/service-snapshot`
- Query params: `service_period` (defaults to current)
- Returns:
```json
{
  "service_period": "dinner",
  "time_range": { "start": "19:00", "end": "22:30" },
  "revenue": { "amount": 185000, "currency": "EUR" },
  "covers": 34,
  "orders_count": 12,
  "average_ticket": { "amount": 5441, "currency": "EUR" },
  "top_items": [
    { "name": "Filet de Boeuf", "quantity": 8 },
    { "name": "Tiramisu Maison", "quantity": 6 }
  ]
}
```

### 6.11 Upcoming Reservations

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/UpcomingReservationsController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/upcoming-reservations`
- Query params: `limit` (default 10)
- Returns next N confirmed/pending reservations for today:
```json
{
  "data": [
    {
      "id": "reservation_public_id",
      "customer_name": "Marie Dupont",
      "party_size": 4,
      "start_at": "20:00",
      "tables": [{ "number": 5, "zone": "terrace" }],
      "special_requests": "Anniversary dinner",
      "status": "confirmed",
      "has_preorder": true
    }
  ]
}
```

---

## API Endpoints (Restaurant Manager — Analytics)

### 6.12 Revenue Analytics

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/RevenueController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/revenue`
- Query params: `period` (today|7d|14d|30d|custom), `from`, `to`
- Same pattern as Shop Manager RevenueController
- Returns: total, percentage_change, chart (daily breakdown)

### 6.13 Covers Analytics

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/CoversController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/covers`
- Query params: `period`
- Returns:
```json
{
  "total_covers": 423,
  "percentage_change": 8.3,
  "daily_average": 60.4,
  "chart": [
    { "date": "2026-03-07", "covers": 58, "reservations": 12 },
    { "date": "2026-03-08", "covers": 72, "reservations": 15 }
  ],
  "by_service_period": {
    "lunch": { "covers": 180, "percentage": 42.6 },
    "dinner": { "covers": 243, "percentage": 57.4 }
  }
}
```

### 6.14 Affluence by Hour

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/AffluenceController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/affluence`
- Query params: `period` (7d|14d|30d)
- Returns covers grouped by hour of day:
```json
{
  "period": "7d",
  "data": [
    { "hour": "12:00", "average_covers": 18.5, "peak_covers": 28 },
    { "hour": "13:00", "average_covers": 22.1, "peak_covers": 32 },
    { "hour": "19:00", "average_covers": 15.3, "peak_covers": 24 },
    { "hour": "20:00", "average_covers": 25.7, "peak_covers": 38 }
  ],
  "peak_hour": "20:00",
  "quietest_hour": "15:00"
}
```

### 6.15 Popular Items

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/PopularItemsController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/popular-items`
- Query params: `period`, `limit` (default 10), `type` (food|drink|all)
- Returns:
```json
{
  "data": [
    {
      "id": "item_public_id",
      "name": "Filet de Boeuf",
      "category": "Plats",
      "type": "food",
      "quantity_sold": 84,
      "revenue": { "amount": 2520000, "currency": "EUR" },
      "percentage_of_total": 18.3,
      "image_url": "..."
    }
  ]
}
```

### 6.16 Average Ticket

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/AverageTicketController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/average-ticket`
- Query params: `period`
- Returns:
```json
{
  "average_ticket": { "amount": 5200, "currency": "EUR" },
  "percentage_change": 4.1,
  "chart": [
    { "date": "2026-03-07", "average": 4800 },
    { "date": "2026-03-08", "average": 5600 }
  ],
  "by_service_period": {
    "lunch": { "average": { "amount": 3800, "currency": "EUR" } },
    "dinner": { "average": { "amount": 6200, "currency": "EUR" } }
  },
  "by_payment_method": {
    "stripe": { "average": { "amount": 5800, "currency": "EUR" }, "count": 45 },
    "cash": { "average": { "amount": 4200, "currency": "EUR" }, "count": 23 }
  }
}
```

### 6.17 Reservation Stats

**Controller:** `app/Http/Controllers/Api/Employee/RestaurantManager/Dashboard/ReservationStatsController.php`

- `GET /v1/places/{place}/restaurants/{restaurant}/dashboard/reservation-stats`
- Query params: `period`
- Returns:
```json
{
  "total_reservations": 156,
  "confirmation_rate": 89.1,
  "no_show_rate": 3.8,
  "cancellation_rate": 7.1,
  "average_party_size": 3.2,
  "average_lead_time_hours": 26.5,
  "chart": [
    { "date": "2026-03-07", "confirmed": 12, "no_show": 1, "cancelled": 0 }
  ],
  "by_service_period": {
    "lunch": { "total": 68, "no_show_rate": 5.9 },
    "dinner": { "total": 88, "no_show_rate": 2.3 }
  }
}
```

---

## Services

### 6.18 RestaurantDashboardService

**File:** `app/Services/RestaurantManager/RestaurantDashboardService.php`

Shared query logic, same pattern as Shop's DashboardService:
- `resolvePeriod(string, ?from, ?to)`: Period string → Carbon dates
- `previousPeriod(from, to)`: Previous equivalent period
- `paidOrdersQuery(restaurant, from, to)`: Base query for PAID orders
- `reservationsQuery(restaurant, from, to)`: Base query for reservations
- `sparkline(query, from, to, aggregate, column)`: Daily sparkline data
- `percentageChange(current, previous)`: % change calculation

### 6.19 RestaurantDashboardCacheService

**File:** `app/Services/RestaurantManager/RestaurantDashboardCacheService.php`

Same Redis caching strategy as Shop:
- TTL_PRECOMPUTED = 86400 (midnight refresh)
- TTL_DELTA = 300 (5 min for intra-day)
- Cache key: `restaurant_dashboard:{widget}:{restaurant_id}:{period}`

---

## Tests

**File:** `tests/Feature/Controllers/RestaurantManager/DashboardTest.php`

- [ ] Live status returns correct table counts
- [ ] Live status returns current covers
- [ ] Today summary calculates correct revenue
- [ ] Today summary compares with yesterday
- [ ] Service snapshot returns current period data
- [ ] Upcoming reservations returns chronologically
- [ ] Revenue analytics matches Shop pattern
- [ ] Covers analytics groups by date correctly
- [ ] Affluence groups by hour correctly
- [ ] Peak hour detection works
- [ ] Popular items returns top sellers
- [ ] Popular items filtered by type (food/drink)
- [ ] Average ticket calculation correct
- [ ] Average ticket by service period works
- [ ] Reservation stats calculates rates correctly
- [ ] All period filters work (today, 7d, 14d, 30d, custom)
- [ ] Empty data returns zeros, not errors
- [ ] Cache service caches and invalidates correctly
- [ ] Filament widgets display data matching API
- [ ] All endpoints return public_id, never id
- [ ] Non-authorized users get 403

---

## Files Created/Modified

| Action | File |
|---|---|
| Create | `app/Filament/Pages/RestaurantDashboardPage.php` |
| Create | 6 Filament widgets in `app/Filament/Widgets/Restaurant/` |
| Create | Controllers: 10 dashboard API controllers |
| Create | `app/Services/RestaurantManager/RestaurantDashboardService.php` |
| Create | `app/Services/RestaurantManager/RestaurantDashboardCacheService.php` |
| Create | `tests/Feature/Controllers/RestaurantManager/DashboardTest.php` |
| Modify | `routes/api.php` — add dashboard routes |

---

## Acceptance Criteria

- [ ] Filament: Dashboard page with all 6 widgets, auto-refreshing live status
- [ ] API: All 10 dashboard endpoints functional
- [ ] Real-time: Live status reflects current table/reservation state
- [ ] Analytics: Revenue, covers, affluence, popular items, average ticket, reservation stats
- [ ] Period filtering works identically to Shop dashboard
- [ ] Cache service uses same Redis strategy as Shop
- [ ] Empty state handled gracefully
- [ ] Queries optimized (aggregates, no N+1)
- [ ] Swagger annotations on all controllers
- [ ] All tests pass
- [ ] `vendor/bin/pint` passes on all new/modified files
- [ ] `vendor/bin/phpstan --level=7` passes on all new/modified files
