# 13 — Admin clinic dashboard: rebuild the home screen

Source: `notes/sysytem_review1.md` §1. Page: `/{locale}/admin/clinic/dashboard`
(route `admin.dashboard.clinic.home`, `ClinicHomeController@index`).

## Build status (2026-09-28)

Built with the recommended decisions D1–D6. Notes:
- Service: `App\Services\Clinic\Dashboard\ClinicDashboardService`; `ClinicReportService::getDashboard()` removed,
  `doctorBreakdown()` made public and reused for the top-doctors table.
- Icons: the bundled keenicons font has no `ki-profile-user`, `ki-hospital` or `ki-stethoscope`; used `ki-people`
  (patients), `ki-security-user` (doctors), `ki-bank` (clinics), `ki-category` (specialties).
- Offer subscriptions have no permission of their own; the card is shown with `clinic.offers.view`.
- Specialty card color is `dark` (Metronic has no `purple` theme color).
- Bookings index: `?date=Y-m-d` filter (validated) and a "clear filters" button.
- Revenue figures are left out of the page's chart JSON for admins without `clinic.reports.view`.
- Tests: `tests/Feature/Clinic/ClinicDashboardTest.php`. `AdminDashboardRouteSmokeTest` already failed before this
  work (it does not disable the localization middleware, every request redirects to `/en`).

Goal (from the request):

1. **Counter cards**: patients, doctors, clinics (العيادات), specialties, offers, offer subscription requests, revenue.
2. **Bookings section** on its own: today's total bookings, how many are paid, how many are unpaid, how many are
   follow-ups, and a breakdown by status with totals.
3. **Table** summarising today's bookings.
4. **Statistics with charts**, each card with an icon and its own color.

---

## 1. What exists today

| Piece | Where | State |
|---|---|---|
| Controller | `Admin\Clinic\ClinicHomeController` → `ClinicReportService::getDashboard($admin)` | 4 plain cards + 2 tables |
| View | `resources/views/dashboard/admin/clinic/home/index.blade.php` (130 lines) | Cards: today total, today pending, month revenue, active patients. Tables: upcoming 8 bookings, top 5 doctors |
| Branch scoping | `ClinicBranchScope` (global scope on `Booking`, `Followup`); `Offer`/`ClinicProfile` have `branch_id` but **no** global scope; `Doctor` has a branch scope method | Super Admin (`branch_id = null`) sees everything, Clinic Admin/Receptionist see their branch only |
| Booking fields | `clinic_bookings`: `type` (`visit`, `online`, `followup`), `status` (`pending`, `confirmed`, `completed`, `cancelled`), `price`, `scheduled_at` | `pending` is labelled **"بانتظار الدفع"** in `lang/ar/clinic.php` |
| Payments | `clinic_payments` (one per booking), `status = paid` only via the online gateway (`Payment::markPaid()`) | Staff-confirmed bookings (`confirmByStaff`) have **no payment row** |
| Offer subscriptions | `SiteOfferSubscription` (`status`: `new` / `contacted`), no `branch_id` | Sidebar already counts `new` ones |
| Charts | ApexCharts is inside `assets/plugins/global/plugins.bundle.js` | Available, no extra asset needed |
| Icons | Keenicons (`ki-duotone ki-…`), already used in 51 dashboard views | Available |
| Bookings index | `Admin\Clinic\BookingController@index` | Filters by `status` only, **no date filter** |
| Tests | `AdminDashboardRouteSmokeTest` checks the view loads for Receptionist / Clinic Admin | Keep passing |

---

## 2. Target layout (top to bottom)

```
┌ Toolbar: title · today's date · [حجز جديد] [مريض جديد] ─────────────────────────────┐
├ A. Counters (7 cards, 4 per row on desktop, 2 on tablet, 1 on phone) ──────────────┤
│  المرضى · الأطباء · العيادات · التخصصات · العروض · طلبات اشتراك العروض · الإيرادات  │
├ B. Today's bookings section ───────────────────────────────────────────────────────┤
│  [إجمالي اليوم] [مدفوع] [غير مدفوع] [إعادة كشف]           ← 4 colored tiles        │
│  Status strip: معلّق n · مؤكد n · مكتمل n · ملغى n  (count + amount each, + total)   │
├ C. Today's bookings table (full width) ────────────────────────────────────────────┤
│  time · patient · doctor · clinic · type · amount · payment · status · [عرض]        │
│  status filter chips above the table · "عرض كل حجوزات اليوم" link                   │
├ D. Charts ─────────────────────────────────────────────────────────────────────────┤
│  D1 Bookings last 30 days (area, per day)   │ D2 Status split this month (donut)   │
│  D3 Revenue last 12 months (bar)            │ D4 Bookings by specialty (bar)       │
│  D5 Booking type split (donut)              │ Top 5 doctors (existing table, kept) │
└────────────────────────────────────────────────────────────────────────────────────┘
```

### A. Counter cards

Each card: colored icon circle, big number, label, a small secondary line, and a link to its list page.
A card is shown only when the admin has the matching view permission.

| Card | Number | Secondary line | Icon (keenicons) | Color | Link / permission |
|---|---|---|---|---|---|
| المرضى | users with a booking or a `patientProfile` | new this month | `ki-profile-user` | primary (blue) | patients.index / `clinic.patients.view` |
| الأطباء | doctors | active count | `ki-stethoscope` (fallback `ki-user-square`) | info (cyan) | doctors.index / `clinic.doctors.view` |
| العيادات | clinic profiles | active count | `ki-hospital` | success (green) | clinics.index / `clinic.clinics.view` |
| التخصصات | specialties | active count | `ki-abstract-26` | purple | specialties.index / `clinic.specialties.view` |
| العروض | offers | running now (active and within dates) | `ki-discount` | warning (orange) | offers.index / `clinic.offers.view` |
| طلبات اشتراك العروض | subscriptions | `new` (not contacted yet) highlighted in red | `ki-notification-status` | danger (red) | offer-subscriptions.index |
| الإيرادات | this month's revenue | today's revenue + % change vs last month (green up / red down) | `ki-dollar` | dark / success | reports page / `clinic.reports.view` |

Exact icon names are checked against the keenicons font before use.

### B. Today's bookings section

Four tiles, then a status strip:

| Tile | Definition (see decision D1/D3) | Color |
|---|---|---|
| إجمالي حجوزات اليوم | `scheduled_at` today, all statuses except cancelled (cancelled shown in the strip) | primary |
| مكتمل الدفع | today, status `confirmed` or `completed` | success |
| لم يتم الدفع | today, status `pending` | warning |
| إعادة كشف (متابعة) | today, `type = followup` | info |

Status strip: one chip per status (`pending`, `confirmed`, `completed`, `cancelled`) with **count and total amount**
(`sum(price)`), and a final "الإجمالي" chip. Clicking a chip filters the table in section C.

### C. Today's bookings table

- All of today's bookings ordered by time (cap at 50 rows; the link below the table opens the full list).
- Columns: time, patient (+ phone), doctor, clinic, type badge, amount, payment badge (paid / unpaid / online-paid),
  status badge, "عرض" button.
- Status chips above the table filter rows on the client (no reload).
- "عرض كل حجوزات اليوم" → bookings index with `?date=YYYY-MM-DD`. **Needs a small change**: add a `date` filter to
  `BookingController@index` (and a date input on that page).
- Empty state: icon + "لا توجد حجوزات اليوم".

### D. Charts (ApexCharts)

| # | Chart | Data | Type |
|---|---|---|---|
| D1 | Bookings per day, last 30 days | count by `DATE(scheduled_at)`, cancelled excluded; missing days filled with 0 | area |
| D2 | Status split, this month | count by `status` | donut, status colors same as badges |
| D3 | Revenue per month, last 12 months | sum of revenue by month (D2 definition) | bar |
| D4 | Bookings by specialty, this month | count joined through `doctor.specialty`, top 8 | horizontal bar |
| D5 | Booking type split, this month | `visit` / `online` / `followup` | donut |

Charts read their colors from Bootstrap CSS variables (`--bs-primary`, …) so they follow the light/dark theme and RTL.
Data is passed once as JSON in the page (`@json`), no extra AJAX endpoint.

---

## 3. Decisions

| # | Question | Recommended | Why |
|---|---|---|---|
| D1 | What counts as "مكتمل الدفع" (paid)? | Status `confirmed` or `completed`; "unpaid" = `pending` | Staff-confirmed bookings have no payment row, so `clinic_payments.status = paid` alone would miss every at-clinic payment. The system already labels `pending` as "بانتظار الدفع" |
| D2 | What is "الإيرادات" (revenue)? | `sum(clinic_bookings.price)` of `confirmed` + `completed` bookings, by `scheduled_at` | Same reason: covers online and at-clinic. Alternative: only `clinic_payments` paid (online only) — would under-report |
| D3 | What is "متابعة"? | Today's bookings with `type = followup` | Matches "إعادة الكشف" in the booking form. (Pending `Followup` records that are not booked yet could be a later extra card) |
| D4 | Branch-scoped admins (Clinic Admin / Receptionist) | Every number is filtered to their branch: bookings via global scope; offers and clinics by `branch_id`; doctors via their clinic; patients = those who booked in the branch; specialties and offer subscriptions stay global (no `branch_id`) | Same rule the page uses today |
| D5 | Revenue card for Receptionist | Hidden unless the admin has `clinic.reports.view` | Money figures are sensitive |
| D6 | Live refresh | No auto-refresh in this round (page load only) | Keeps it simple; can poll later like the bells |

---

## 4. Implementation tasks (one commit each)

1. **Service** — new `App\Services\Clinic\Dashboard\ClinicDashboardService` (keeps `ClinicReportService` for the
   reports page; move `doctorBreakdown` there as public or reuse it).
   - `counters(Admin)`, `todaySummary(Admin)`, `todayBookings(Admin, limit 50)`, `charts(Admin)`.
   - Aggregate with grouped queries (`selectRaw('status, count(*), sum(price)') … groupBy('status')`) instead of
     one query per number. Date grouping must work on MySQL and SQLite (tests).
   - Controller passes the arrays to the view; route and permission unchanged.
2. **Bookings index date filter** — `?date=` on `BookingController@index` + date input in the filter bar.
3. **View** — rewrite `home/index.blade.php` using partials in `home/partials/`:
   `_counters`, `_today_summary`, `_today_table`, `_charts`. Small reusable `_stat_card` partial (icon, color,
   value, label, sub-line, link).
4. **Charts script** — `@push('scripts')` block that builds the 5 ApexCharts from the JSON data; guards for empty
   data (shows "لا توجد بيانات").
5. **Translations** — new keys under `clinic.home.*` in `lang/ar/clinic.php` and `lang/en/clinic.php`.
6. **Tests** (`tests/Feature/Clinic/ClinicDashboardTest.php`):
   - Super Admin sees all counters; Receptionist sees only permitted cards and branch-scoped numbers.
   - Paid / unpaid / follow-up counts and status totals for today are correct.
   - Today's table lists only today's bookings; cancelled appears with its badge.
   - Revenue card hidden without `clinic.reports.view`.
   - Chart series have 30 / 12 points.
   - Existing `AdminDashboardRouteSmokeTest` still passes; run on MySQL too (`erp_2_test`).
7. **Manual check** in the browser (AR + EN, RTL, light + dark, phone width) and update this file with a
   "Build status" section.

---

## 5. Out of scope

- Doctor-side dashboard (separate app).
- Date range picker on the home page (the reports page already has one).
- Auto-refresh / live counters.
