# AgencyDesk

Client portal plus agency workspace. Laravel 13, PHP 8.3+, MySQL. The UI is an approved HTML prototype reused as Blade. **Keep the markup and CSS classes identical to the prototype.** Don't restyle, rename classes or add a CSS framework.

## Commands

```bash
composer install
cp .env.example .env && php artisan key:generate
php artisan migrate:fresh --seed     # reset demo data (dates shift so "today" is the real today)
php artisan storage:link
php artisan serve                    # http://localhost:8000
php artisan route:list --path=admin  # or --path=portal
php artisan agencydesk:approval-reminders | agencydesk:payment-reminders | agencydesk:purge-trash
```

Demo logins all use the password `password`:
- lina@northwindcreative.com: admin
- karim@northwindcreative.com: project manager
- omar@northwindcreative.com: team member
- sarah@alnoor.ae: client owner

No Node or Vite build. The assets are plain files in `public/assets` (`css/style.css`, `js/app.js`, `js/knowledge-hub.js`, `img/`).

## Architecture

- **Multi-tenant.** Every business table has `agency_id`. Scope every query to the signed-in user's agency. Also scope clients to their `client_id`. Never trust IDs from the request.
- **Two sides.**
  - `/portal` routes are for clients: `side:client`, `App\Http\Controllers\Portal`, views in `resources/views/portal`.
  - `/admin` routes are for staff: `side:staff`, `App\Http\Controllers\Admin`, views in `resources/views/admin`.
  - Shared code is `AuthController`, `SharedController` and `resources/views/shared`.
- **Permissions.** Use `perm:<name>` middleware or `$user->allows('<name>')`. The matrix is `User::PERMISSIONS`: `view_projects`, `share_deliverables`, `manage_projects`, `manage_invoices`, `train_assistant`, `manage_workspace`.
  - Roles are admin, project_manager, team_member and accounts.
  - Admin and accounts see all projects (`seesAllProjects()`). Others only see projects they manage or are members of.
- **Scoping helpers.** Always use these.
  - Lists use `Model::visibleTo($user)` scopes on Project, ProjectFile, Approval, Invoice and Conversation. They handle agency, client and assigned-project rules.
  - Single records use `$this->own($model)` in controllers. It returns 404 across agency, client or unassigned project.
- **Responses.** `$this->done($request, 'Message', $redirectUrl = null, $icon = 'check')` returns JSON `{message, redirect}` for fetch calls. Otherwise it redirects back with a toast.
- **Business rules live in `app/Services`.**
  - `ApprovalService`: share, approve, request changes, new version, remind.
  - `FileService`: uploads, versions, share links.
  - `MessageService`: threads, attachments.
  - `PaymentService`: demo online payment, recording payments, invoice reminders.
  - `AssistantService`: answer and ask.
  - Keep controllers thin.
- **`App\Support\Portal`** holds the shared side-effect helpers:
  - `activity()` writes the activity log. Pass `agency_id` when there is no signed-in user, for example in console commands.
  - `notify()` sends in-app notifications. Categories are approval, message, invoice, payment, file and ai. It respects staff preferences through `wants()`.
  - `email()` sends one of the agency's editable email templates.
- **Shell.** `App\View\ShellComposer` builds the sidebar, badges, notifications dropdown and `window.AD_CONFIG` for `layouts.app`. `$shell` exists only inside the layout. In page views, use `agency()` and `auth()->user()`.
- **Agency settings.** Read switches with `$agency->setting('key')` and write them with `putSetting()`. Defaults are in `Agency::DEFAULTS`. Branding, currency, VAT and invoice format are real columns on `agencies`.
- **Helpers** (`app/helpers.php`):
  - money: `money`, `money_k`, `num`
  - dates: `d_short`, `d_long`, `d_smart`, `d_thread`, `t_smart`, `ago`
  - other: `file_size`, `badge`, `av`, `agency`, `greeting`, `plural`, `portal_route`

## Front-end conventions (`public/assets/js/app.js`)

- `AD.post(url, data)` is a fetch wrapper with CSRF and JSON. It shows a toast on error. `data` can be `FormData`.
- Wire behaviour with data attributes rather than new JS:
  - `<form data-ajax>` posts the form and reloads. Use `data-ajax="stay"` to not reload.
  - `data-action="url"` on a button sends a POST. Add `data-confirm` to ask first.
  - `data-toggle-url` on a checkbox or select posts `{key, value}`. Set the key with `data-key`.
  - Tabs, modals and filters use `data-tabs`, `data-open`, `data-close` and `data-filter-target`.
- Page-specific JS goes in `@push('scripts')` at the end of the view.
- The Knowledge Hub is drawn by `knowledge-hub.js` from `window.KB_CONFIG.state`. Each change returns the new `state`.

## Demo-only choices (agreed with the client, keep them)

- **Two-step code** is shown on screen. No SMS.
- **Payments** are simulated, with no gateway. Don't add Stripe or PayTabs unless asked.
- **Mail** uses `MAIL_MAILER=log`.
- **Local disk only**, no S3. Uploads go to `storage/app/private`. Logos and avatars go to the public disk.
- **Desk Assistant** uses keyword matching. `AssistantService::answer()` is the single place to plug in an AI provider. Uploaded KB documents and websites are stored but not read.
- **Arabic and right-to-left** are deferred.

## Known gaps / next work

- Settings that are saved but not used yet:
  - `session_timeout`
  - `welcome_message` (not shown on the client dashboard)
  - staff `daily_summary` and hourly message digest (no job sends them)
  - `portal_languages`
- Only tested on SQLite so far. Verify on MySQL.
- No feature tests yet. `tests/` only has Laravel's examples. Add tests for scoping (cross-agency, cross-client, unassigned staff) first.

## When changing things

- New tables need `agency_id` (constrained, cascade on delete). Add the model to the scoping rules above.
- Seed data lives in `database/seeders/DatabaseSeeder.php`. Build dates with its `d()` helper so they stay relative to today.
- After editing views, check a page with `php artisan serve` as both an admin and a client.
