# 11 — Website & Admin Round 2 (`notes/web-site-2.md`)

Source: `notes/web-site-2.md` (18 items). Visual target: the screenshot pairs in
`E:\omnia\work\projectes\المركز الطبي\images\` — `original front\*.png` is the target design,
`web site\*.png` is what the site shows today. Static reference HTML: `...\bst-final\*.html`.

## Where each item stands today

| # | Item | Finding in the code |
|---|---|---|
| 1 | Image remove button outside the image card | **Template Settings** (confirmed) — `system/template_settings/index.blade.php:488-583`: the Remove button is `position-absolute top-0 end-0` on top of the thumbnail. |
| 2 | Payment Gateways link in Settings sidebar | Route exists (`admin.dashboard.finance.setup.payment_gateways.index`). Today it only shows under Finance ▸ Settings (`main-sidebar.blade.php:636`). |
| 3 | Firebase notification config in Integrations | `IntegrationProvider::definitions()` has email/sms/whatsapp/chat only. Nothing in `app/` sends push yet. Update validation caps every field at 2000 chars, but a service-account JSON is longer than that. |
| 4 | `/admin/mdata` in tabs + Visiting Hours repeater | `maindata/insert.blade.php` is one long form. Visiting hours are **hard-coded translation strings** (`site.footer.hours_*`) in the footer and top bar. `site_data` has no hours column. |
| 5 | Contact form (site) → JsValidation | `StoreContactRequest` exists; the site layout does not load `jsvalidation.js`. |
| 6 | All clinic-flow admin forms → JsValidation | Already on: banners, bookings, clinics, doctors, faqs, medical options, offers, patients, specialties, staff create, branches, Site banner/statistics/feedback/gallery/polices/contact. **Missing:** About (create/edit/settings), Blogs, Blog Categories, Main Data, Doctor schedule, Pricing (create/edit), Partners, Integrations edit, Settings ▸ Manage. |
| 7 | Rebuild `/admin/contact` | Datatable of name/email/phone plus a "Subject" button that opens a modal. No read/unread state, date, filters or show page. |
| 8 | Rebuild `/admin/about` index + details, show which one is live | The site renders `SiteAbout::latest('id')->first()` (`MainController:32,73`), so "active" is implicit. No status column. Details open only in a modal. |
| 9 | 500 on `/admin/statistics/{id}/edit` | `ResponseApi::prepare_data()` returns an **array**; the view reads `$one_data->id`. |
| 10 | UI to edit translation values | 32 PHP files per locale in `lang/ar` + `lang/en`, plus `ar.json`/`en.json`. No package installed. |
| 11 | Services CRUD (title, description, image, ar/en) → `/services` | `/services` lists **Specialties** (name + image only). The target design shows a description on every card. `App\Models\setting\Service` is an unrelated ERP settings table. |
| 12 | `/clinic/search` = `doctors.html` with specialty tabs | Today: two dropdowns + a Slick slider (`doctor-slider`). Target: pill tabs (icon + name), 4-column grid, cards with rating + branch + price. The card partial already supports rating/branch, but the controller never eager-loads `clinics.branch`, and there's no price. Specialty has `photo_path` only (no icon). |
| 13 | Blog cards show category after publisher | `SiteBlog::category()` exists; `partials/blog-card` doesn't print it. |
| 14 | Blog details sidebar: categories that filter the list | No categories widget; `blogIndex()` has no category filter. |
| 15 | 500 on `/admin/banner/{id}/edit` | Same cause as #9. The same pattern is in **feedback, polices, gallery** edit too. |
| 16 | Offers → subscribe form (not appointment) | Offer cards link to `route('appointment')`. The target page (`offers-subscribe.html`) has an app-download column + subscribe form (name, phone, address, contact method, offer read-only, message). Nothing stores offer subscriptions. |
| 17 | AR/RTL: خدماتنا الطبية / قبل وبعد / آراء مرضانا don't show | All three are Slick sliders (`specialist-slider`, `service-slider-7`, `testmonial-slider`). `main.js` starts them without `rtl: true` while `<html dir="rtl">`, so Slick renders them blank. |
| 18 | Remove `breadcumb-menu` from all site pages | 10 pages already commented out (uncommitted). **Still active:** `faqs`, `policy`, `service-details`. |

### Gaps visible in the screenshots but not in the notes

- **Every page**: top bar is missing working hours. Footer shows 1 phone / 1 address / only the X icon; target shows 2 phones, 2 addresses, Facebook/Instagram/LinkedIn/X. This is data plus the hard-coded hours from #4.
- **Contact**: branch cards are missing the address line; the map block under the form is missing.
- **About**: the statistics strip (30+ years, 10+ services…) and the "Why Choose BST" section are missing; vision cards should clamp their text and add a Read More link.
- **Appointment**: layout is off. Target has an app-download column + a "Book An Appointment" header card with name/phone/address/branch/specialist/doctor/type/date fields.

---

## Build order (one commit per track)

**A** bugs & quick wins → **B** Main Data + hours → **C** admin content screens → **D** Services
→ **E** website pages → **F** validation sweep → **G** translation editor → **H** screenshot gaps.

Screens built in B–E get JsValidation as they are built; F covers whatever is left.

---

## Track A — Bugs & quick wins (#9, #15, #18, #2, #1, #17)

- [x] **#9 / #15** Fix at the source: the Site `edit()` methods pass `prepare_data()` arrays into views written for objects. Convert to an object in each `edit()` (statistics, banner, feedback, polices, gallery; check events/projects/partners/advantages/videos too). Gallery's nested `images` must stay iterable. Open each edit page to confirm.
- [x] **#18** Delete (not comment out) the breadcrumb `<ul>` on all 13 pages, including `faqs`, `policy`, `service-details`. Drop the now-unused `site.nav.home` crumb only if nothing else uses it.
- [x] **#2** Add Payment Gateways under Settings, directly after Integrations (`main-sidebar.blade.php:~412`). Also add its route prefix to the Settings section's open/active check. Keep the Finance ▸ Settings entry.
- [x] **#1** Template Settings: take the Remove button off the thumbnail and put it on the same row as the file input (`input-group`: file input + Remove button). Apply to all four image blocks (logo fields, favicon, header image, other image fields).
- [x] **#17** `public/assets_web/js/main.js`: read `var isRtl = document.documentElement.dir === 'rtl';` once and pass `rtl: isRtl` to every `.slick({...})` call (service-slider-7, doctor-slider ×2, testmonial-slider, specialist-slider, others). Check `style-rtl.css` for slick direction overrides. Test home in `/ar`.

## Track B — Main Data rebuild + visiting hours (#4)

**Migration** `add_visiting_hours_to_site_data_table`: `visiting_hours` JSON nullable.
Rows are `[{day_from, day_to, time_in, time_out}]` (or a single `day` if you'd rather have one row per day).
Backfill from today's hard-coded values: Mon–Fri 08:00–20:00, Sat 09:00–18:00, Sun 09:00–18:00.

- [x] `SiteData`: fillable + `array` cast.
- [x] `maindata/insert.blade.php` → Metronic tabs: **General** (name, logos, short about, profile PDF) · **Contact** (email, phones, fax, address ar/en, map) · **Social** (facebook, instagram, linkedin, x, youtube, tiktok, snapchat, whatsapp) · **Visiting Hours** (repeater: day select, time in, time out, add/remove rows) · **SEO** (site + contact SEO).
- [x] `MainRequest`: rules for `visiting_hours.*.day`, `time_in`, `time_out` (`after:time_in`), then add `JsValidator::formRequest`.
- [x] Footer "Visiting Hours" table and the top-bar clock line read `visiting_hours`. Day names come from translations; keep the old strings as a fallback when the column is empty.

## Track C — Admin content screens (#7, #8, #3)

- [x] **#7 Contact messages**: `is_read` boolean + `read_at` migration. Index datatable: name, email, phone, subject (truncated), date, read/unread badge, filter by read state. Proper show page (marks the message read), delete. Unread count badge on the sidebar link.
- [x] **#8 About**: `is_active` boolean migration; backfill makes the latest row active. Activating one row turns off the others (single live record). Index: image thumb, title, **"Live on website" badge**, activate toggle, actions. Full details page (not a modal) with ar/en side by side. Site `about()` and `home()` read `where('is_active', true)`, falling back to the latest row.
- [x] **#3 Firebase**: new `CATEGORY_PUSH = 'push'` + provider `firebase_fcm` with fields `project_id`, `service_account_json` (textarea, must be valid JSON, max ~10k), and web-push fields `api_key`, `messaging_sender_id`, `app_id`, `vapid_key`. Give the edit view a textarea for `*_json` fields and raise the length cap for them only. Add hints and re-run `IntegrationProviderSeeder`. **Config form only**: sending push notifications is not wired anywhere yet and stays out of scope.

## Track D — Services CRUD (#11)

**Migration** `create_site_services_table`: `title` json, `description` json, `image`, `specialty_id` nullable FK → `clinic_specialties` (`nullOnDelete`), `is_active`, `sort_order`, audit columns, timestamps, soft deletes.

- [x] `App\Models\Site\SiteService` (HasTranslations), `SiteServiceController` following the Site admin pattern (datatable index, create/edit with **ar/en tabs**, image upload, active toggle, sort order), Store/Update FormRequests + JsValidator.
- [x] Sidebar entry under Site Management; permission keys in the seeder, matching the existing site permissions.
- [x] Admin form: optional **Specialty** select (Select2) next to title/description.
- [x] `/services` lists active `SiteService` ordered by `sort_order`: image, title, description, Read More.
- [x] Details page `/services/{service}`: service image, title, description, then the linked specialty's active doctors (reusing today's `service-details` doctor block), plus an "other services" sidebar. The old `/services/{specialty}` URL redirects to the matching service, or to `/services` if none, so existing links don't 404.
- [x] Home "خدماتنا الطبية" slider switches from specialties to services (Read More → service details).
- [x] Seeder: the 9 services from `bst-final/service.html` (Obesity Treatment … Obesity & Infertility), with ar translations and the reference images, each linked to its matching specialty where one exists.

## Track E — Website pages (#12, #13, #14, #16)

- [x] **#12 Doctors**: replace the dropdown form + slider with the `nav nav-tabs` pill markup from the note (icon + name per specialty). Each `tab-pane` holds a 4-column grid (`col-md-6 col-lg-3`, no `doctor-slider`). `?specialty_id=` picks the active tab; `branch_id` stays supported as a hidden filter.
  - Specialty icon: add `icon_path` to `clinic_specialties` + upload in the specialty form (+ API resource), falling back to `photo_path`.
  - Controller eager-loads `clinics.branch` and pricing so the card shows **rating · branch · price (EGP)** like the target.
- [x] **#13** `partials/blog-card`: meta row = publisher + category (folder icon) linking to `/blog?category={id}`. Eager-load `category` in `blogIndex()`.
- [x] **#14** `blog-details` sidebar: a "Categories" widget listing active categories with post counts. `blogIndex()` accepts `?category=` and keeps it in pagination links.
- [x] **#16 Offers**: build the reference section (app-download column + "Subscribe to Our Offer" form). Every offer card's button becomes **Subscribe**: it jumps to `#subscribe` and pre-fills the offer (read-only field + hidden `offer_id`).
  - New `site_offer_subscriptions` table (name, phone, address, contact_method phone|whatsapp, offer_id, message, status new/contacted, timestamps).
  - `POST /offers/subscribe` + `StoreOfferSubscriptionRequest` + JsValidator; success toast.
  - Admin list screen for subscriptions (filter by offer/status, mark contacted).
  - Home offer cards link to the same place.

## Track F — Validation sweep (#5, #6)

- [x] Site layout: add a `@stack('scripts')` slot after jQuery and load `vendor/jsvalidation/js/jsvalidation.js` only on pages that use it.
- [x] **#5** `contact.blade.php`: `JsValidator::formRequest(StoreContactRequest::class, '#contactForm')`. Error placement must fit the rounded `style2` inputs (and RTL); localized messages.
- [x] **#6** Add `JsValidator::formRequest` to every screen still missing it: About create/edit/settings, Blogs, Blog Categories, Doctor schedule, Pricing create/edit, Partners, Settings ▸ Manage. Add a FormRequest for Integrations (rules built from `configFields()`) and Pricing if they validate inline.
- [x] Spot-check that each form still submits normally (file inputs, Select2, repeaters).

## Track G — Translation editor (#10)

Package: **`spatie/laravel-translation-loader`** (2.8.x, supports Laravel 12). It stores edits in a
`language_lines` table and layers them **on top of** `lang/*.php`, so the files stay the default and
edits survive `git pull`/deploys. The web server never writes to `lang/`. The package has no UI, so we
build one screen in the admin's Metronic style.
*(Alternative considered: `barryvdh/laravel-translation-manager` ships its own non-Metronic UI and
"publishes" edits back into the PHP files on the server. Rejected for the deploy/overwrite risk.)*

- [x] `composer require spatie/laravel-translation-loader`, publish + run its migration, and swap the translation service provider as its README says. Keep the file loader as the fallback.
- [x] Screen under Settings ▸ **Translations**: pick a file group (`site`, `sidebar`, `dash_site`, …) → table of every key from the lang file, showing the **en** and **ar** file value, the current override, a search box, and inline edit. Saving writes a `language_lines` row. **Reset** deletes the override and falls back to the file.
- [x] Nested arrays use dotted keys. Placeholders like `:name` are protected: saving warns if one is dropped.
- [x] `permission:core.settings.update`; the package's cache is cleared on save.

## Track H — Screenshot gaps (confirmed, after A–G)

- [x] Top bar hours + footer multiple phones/addresses (from `branches` / Main Data) + social icons.
- [x] Contact: branch address line + map embed (per branch or `maplocation`).
- [x] About: statistics strip (from `SiteStatistics`) + "Why Choose BST" block + clamped vision cards with Read More.
- [x] Appointment: two-column layout with app download + "Book An Appointment" card and the full field set.

---

## Decisions taken with the user before building

| Decision | Choice |
|---|---|
| Translation editor (#10) | **Use a package**: `spatie/laravel-translation-loader` (DB overrides on top of the files) + a Metronic screen |
| Services Read More (#11) | **Link each service to a specialty**: details page = service text + that specialty's doctors; the home slider switches to services |
| Gaps seen only in the screenshots | **Included** as Track H, after the 18 listed items |
| Item #1 screen | **Template Settings** |

## Housekeeping

- `public/images/maindata/` and `public/images/template_settings/` are test uploads. Don't commit them; add both to `.gitignore`.
- The 10 uncommitted breadcrumb edits are folded into Track A's commit.
