# Filament Admin Migration — Phased Plan

**Status**: foundation shipped 2026-05-23. Filament v3 installed, themed,
running at `/cp` (legacy Blade admin stays at `/admin`). Two proof
Resources scaffolded (`Difficulty`, `Level`). Remaining ~20 resources
phased below.

## What's done

| Item | File / Location |
|---|---|
| Filament v3 installed | `composer.json` (filament/filament:^3.0) |
| Panel provider + theme | `app/Providers/Filament/AdminPanelProvider.php` — primary `#960000`, font `Inter`, brand `AllGifted Math — Admin` |
| URL | `/cp` (parked away from legacy `/admin` until migration complete) |
| Auth gate | `App\Models\User::canAccessPanel` — restricts to role_id IN (1, 9, 10, 11) |
| Smoke test | `GET /cp/login` → HTTP 200, brand renders, 7 Filament routes registered |
| `DifficultyResource` | `app/Filament/Resources/DifficultyResource.php` — full CRUD |
| `LevelResource` | `app/Filament/Resources/LevelResource.php` — full CRUD |

## Naming convention

Filament Resources go in `app/Filament/Resources/{Name}Resource.php`.
Custom pages (for singletons or dashboards) go in `app/Filament/Pages/`.
Widgets in `app/Filament/Widgets/`.

## Migration order — phased by complexity

Pick from the top down — each phase is independently shippable.

### Phase A — simple lookup tables (1 hour each, mostly auto-generate)

| Old Blade view | New Filament Resource | Model | Complexity |
|---|---|---|---|
| `admin/difficulties/index.blade.php` | `DifficultyResource` ✅ | `Difficulty` | scaffolded |
| (no Blade — admin via API) | `LevelResource` ✅ | `Level` | scaffolded |
| `admin/configuration/index.blade.php` | `ConfigurationPage` (custom, singleton edit) | `Config` (singleton) | small — one form |
| `admin/settings/general.blade.php` | `SettingsPage` (subset of Configuration) | same | small |
| (lookup) | `StatusResource` | `Status` (5 rows) | trivial |
| (lookup) | `TestTypeResource` | `TestType` (4 rows) | trivial |
| (lookup) | `FieldResource` (with `index.blade.php` already livewire) | `Field` | small |

### Phase B — content resources (2-3 hours each, relations matter)

| Old Blade view | New Filament Resource | Notes |
|---|---|---|
| `admin/tracks/{index,show}.blade.php` | `TrackResource` | Belongs-to Field + Level; has skills via `skill_track` pivot. RelationManager for skills. |
| `admin/skills/index.blade.php` (+ 4 modals) | `SkillResource` | Belongs-to-many Track via `skill_track`. The 4 modals (duplicate, manage-tracks, generate-questions, variations) become Actions/HeaderActions. |
| `admin/questions/{index,show,preview}.blade.php` | `QuestionResource` | Belongs-to Skill + Difficulty + Type. QA workflow (qa_status enum) as a filter/action. The question_user table for analytics → RelationManager (read-only). |
| `admin/fields/show.blade.php` | already partially Livewire — wrap into `FieldResource::infolist()` | check existing Livewire component first |
| `admin/qa/{index,show,auth}.blade.php` | extend `QuestionResource` with a QA-status filter view + bulk action | the `qa_status` enum is the workflow state |

### Phase C — user-facing admin (the big one — 4-6 hours)

| Old Blade view | New Filament Resource | Notes |
|---|---|---|
| `admin/users/index.blade.php` | `UserResource` (list) | Filter by access_type, role, has-active-test, subscription_plan |
| `admin/users/show.blade.php` + 6 partial tabs | `UserResource` view page + 6 RelationManagers | One RelationManager per partial: `SkillUserRelationManager`, `TrackUserRelationManager`, `FieldUserRelationManager`, `QuestionUserRelationManager`, `TestUserRelationManager`, `QuizUserRelationManager` |
| `admin/users/test_questions.blade.php` | embed in `UserResource` view as another panel | drill-down into a specific test |
| `admin/users/partials/logs-table.blade.php` | `RecordLogResource` (separate) + RelationManager on UserResource | logs are queried elsewhere too |

### Phase D — dashboard + widgets (2-3 hours)

| Old Blade view | New Filament equivalent | Notes |
|---|---|---|
| `admin/dashboard.blade.php`, `admin/dashboard/index.blade.php` | Replace `Pages\Dashboard` widgets list | Use Filament Widgets for the stats |
| `admin/components/stats-row.blade.php` | `StatsOverviewWidget` | 4 stat cards (users, tests, kudos awarded, questions answered) |
| `admin/components/recent-activity.blade.php` | `ActivityFeedWidget` (custom) | recent question_user / attempt_ledger entries |
| `admin/components/system-status.blade.php` | small custom widget | health checks |
| `admin/components/welcome-header.blade.php` | drop (Filament has its own header) | |

### Phase E — non-resource pages + utilities (1-2 hours each)

| Old Blade view | New Filament location |
|---|---|
| `admin/assets/index.blade.php` | `AssetManagerPage` (custom) — wraps the existing `AssetController` upload flow |
| `admin/components/math-help.blade.php` | `MathHelpPage` (custom static page) |
| `admin/components/question-generation-modal.blade.php` | Action class invoked from `QuestionResource`/`SkillResource` |

## Routing cutover

Once every Resource lists above is migrated and tested:

1. Move `/admin` legacy routes in `routes/web.php:79+` to `/admin-legacy` (preserve for rollback).
2. Swap `AdminPanelProvider::path('cp')` → `path('admin')`.
3. Delete the old Blade views in `resources/views/admin/**`.
4. Delete legacy admin controllers in `app/Http/Controllers/Admin/**` if unused.

## Auth on the new admin

Filament uses Laravel's default `web` guard. The legacy admin uses the
same. Users with role_id IN (1, 9, 10, 11) can log in to both during
migration. No data migration needed.

For role-based UI restrictions inside Filament (e.g., QA Reviewer sees
only QuestionResource), use `Resource::canViewAny()` overrides or a
Filament Spatie shield install.

## Theme matching

Done in `AdminPanelProvider`:
- Primary color: `#960000` (matches `configs.main_color`)
- Font: `Inter` (matches `configs.primary_font`)
- Brand name: "AllGifted Math — Admin"

For a closer "keep the look and feel completely" match, additional knobs:
- Sidebar collapsible: `->sidebarCollapsibleOnDesktop()`
- Dark mode: `->darkMode(false)` to disable if the old admin was light-only
- Logo: `->brandLogo(asset('images/logo.png'))` if you want the brand image instead of text
- Custom CSS: Filament v3 lets you publish & extend the theme via Tailwind

The component-level visual idiom (cards, tables, forms) will be
Filament's, not Blade-Bootstrap. Functional parity preserved; pixel
parity not possible without rewriting Filament itself.

## Estimated effort

| Phase | Hours | Risk |
|---|---|---|
| A (simple lookups) | 4-5 (5 resources + Config page) | low |
| B (content) | 12-15 (4 resources, relations) | medium — pivots + custom modals |
| C (users) | 6-8 (1 resource + 6 RelationManagers) | medium — many relations |
| D (dashboard) | 4-6 (4-5 widgets) | medium — widget design |
| E (utilities) | 3-4 (3 pages) | low |
| Cutover | 1-2 | low — routing swap |
| **Total** | **~30-40 hours** | |

This is the realistic full-migration cost. Filament autogeneration
handles the boilerplate, but every resource needs a pass to add filters,
sort columns, restrict mass-assignment, hide sensitive fields, etc.

## Recommended next slice (after this checkpoint)

Pick one of:

1. **Phase A complete** — finish the 5 remaining simple Resources (Configuration, Settings, Status, TestType, Field). ~4 hours. Sets the pattern.
2. **One Phase B resource end-to-end** — pick QuestionResource as the highest-impact, demonstrates QA workflow + relations.
3. **Cutover plan walkthrough** — verify that moving the legacy admin to `/admin-legacy` doesn't break any other in-app links.

Each is independent and concrete.
