# Creating Admin Panels
This guide covers the complete process of creating admin panels in the Core PHP Framework, including menu registration, modal creation, and authorization integration.
## Overview
Admin panels in Core PHP use:
- **AdminMenuProvider** - Interface for menu registration
- **Livewire Modals** - Full-page components for admin interfaces
- **Authorization Props** - Built-in permission checking on components
- **HLCRF Layouts** - Composable layout system
## Menu Registration with AdminMenuProvider
### Implementing AdminMenuProvider
The `AdminMenuProvider` interface allows modules to contribute navigation items to the admin sidebar.
```php
'onAdminPanel',
];
public function onAdminPanel(AdminPanelBooting $event): void
{
// Register views and routes
$event->views('blog', __DIR__.'/View/Blade');
$event->routes(fn () => require __DIR__.'/Routes/admin.php');
// Register menu provider
app(AdminMenuRegistry::class)->register($this);
}
public function adminMenuItems(): array
{
return [
// Dashboard item in standalone group
[
'group' => 'dashboard',
'priority' => self::PRIORITY_HIGH,
'item' => fn () => [
'label' => 'Blog Dashboard',
'icon' => 'newspaper',
'href' => route('admin.blog.dashboard'),
'active' => request()->routeIs('admin.blog.dashboard'),
],
],
// Service item with entitlement
[
'group' => 'services',
'priority' => self::PRIORITY_NORMAL,
'entitlement' => 'core.srv.blog',
'item' => fn () => [
'label' => 'Blog',
'icon' => 'newspaper',
'href' => route('admin.blog.posts'),
'active' => request()->routeIs('admin.blog.*'),
'color' => 'blue',
'badge' => Post::draft()->count() ?: null,
'children' => [
['label' => 'All Posts', 'href' => route('admin.blog.posts'), 'icon' => 'document-text'],
['label' => 'Categories', 'href' => route('admin.blog.categories'), 'icon' => 'folder'],
['label' => 'Tags', 'href' => route('admin.blog.tags'), 'icon' => 'tag'],
],
],
],
// Admin-only item
[
'group' => 'admin',
'priority' => self::PRIORITY_LOW,
'admin' => true,
'item' => fn () => [
'label' => 'Blog Settings',
'icon' => 'gear',
'href' => route('admin.blog.settings'),
'active' => request()->routeIs('admin.blog.settings'),
],
],
];
}
}
```
### Menu Item Structure
Each item in `adminMenuItems()` follows this structure:
| Property | Type | Description |
|----------|------|-------------|
| `group` | string | Menu group: `dashboard`, `workspaces`, `services`, `settings`, `admin` |
| `priority` | int | Order within group (use `PRIORITY_*` constants) |
| `entitlement` | string | Optional workspace feature code for access |
| `permissions` | array | Optional user permission keys required |
| `admin` | bool | Requires Hades/admin user |
| `item` | Closure | Lazy-evaluated item data |
### Priority Constants
```php
use Core\Front\Admin\Contracts\AdminMenuProvider;
// Available priority constants
AdminMenuProvider::PRIORITY_FIRST // 0-9: System items
AdminMenuProvider::PRIORITY_HIGH // 10-19: Primary navigation
AdminMenuProvider::PRIORITY_ABOVE_NORMAL // 20-39: Important items
AdminMenuProvider::PRIORITY_NORMAL // 40-60: Standard items (default)
AdminMenuProvider::PRIORITY_BELOW_NORMAL // 61-79: Less important
AdminMenuProvider::PRIORITY_LOW // 80-89: Rarely used
AdminMenuProvider::PRIORITY_LAST // 90-99: End items
```
### Menu Groups
| Group | Description | Rendering |
|-------|-------------|-----------|
| `dashboard` | Primary entry points | Standalone items |
| `workspaces` | Workspace management | Grouped dropdown |
| `services` | Application services | Standalone items |
| `settings` | User/account settings | Grouped dropdown |
| `admin` | Platform administration | Grouped dropdown (Hades only) |
### Using MenuItemBuilder
For complex menus, use the fluent `MenuItemBuilder`:
```php
use Core\Front\Admin\Support\MenuItemBuilder;
public function adminMenuItems(): array
{
return [
MenuItemBuilder::make('Commerce')
->icon('shopping-cart')
->route('admin.commerce.dashboard')
->inServices()
->priority(self::PRIORITY_NORMAL)
->entitlement('core.srv.commerce')
->color('green')
->badge('New', 'green')
->activeOnRoute('admin.commerce.*')
->children([
MenuItemBuilder::child('Products', route('admin.commerce.products'))
->icon('cube'),
MenuItemBuilder::child('Orders', route('admin.commerce.orders'))
->icon('receipt')
->badge(fn () => Order::pending()->count()),
['separator' => true],
MenuItemBuilder::child('Settings', route('admin.commerce.settings'))
->icon('gear'),
])
->build(),
MenuItemBuilder::make('Analytics')
->icon('chart-line')
->route('admin.analytics.dashboard')
->inServices()
->entitlement('core.srv.analytics')
->adminOnly() // Requires admin user
->build(),
];
}
```
### Permission Checking
The `HasMenuPermissions` trait provides default permission handling:
```php
use Core\Front\Admin\Concerns\HasMenuPermissions;
class BlogMenuProvider implements AdminMenuProvider
{
use HasMenuPermissions;
// Override for custom global permissions
public function menuPermissions(): array
{
return ['blog.view'];
}
// Override for custom permission logic
public function canViewMenu(?object $user, ?object $workspace): bool
{
if ($user === null) {
return false;
}
// Custom logic
return $user->hasRole('editor') || $user->isHades();
}
}
```
## Creating Livewire Modals
Livewire modals are full-page components that provide seamless admin interfaces.
### Basic Modal Structure
```php
'required|string|max:255',
'content' => 'required|string',
'status' => 'required|in:draft,published,archived',
];
public function mount(?Post $post = null): void
{
$this->post = $post;
if ($post) {
$this->title = $post->title;
$this->content = $post->content;
$this->status = $post->status;
}
}
public function save(): void
{
$validated = $this->validate();
if ($this->post) {
$this->post->update($validated);
$message = 'Post updated successfully.';
} else {
Post::create($validated);
$message = 'Post created successfully.';
}
session()->flash('success', $message);
$this->redirect(route('admin.blog.posts'));
}
public function render(): View
{
return view('blog::admin.post-editor');
}
}
```
### Modal View with HLCRF
```blade
{{-- resources/views/admin/post-editor.blade.php --}}
Publishing Tips
You cannot edit this post.
@endcannot ``` ### Creating Policies ```php isHades()) { return true; } // Enforce workspace isolation if ($model instanceof Post && $user->workspace_id !== $model->workspace_id) { return false; } return null; // Continue to specific method } public function viewAny(User $user): bool { return $user->hasPermission('posts.view'); } public function view(User $user, Post $post): bool { return $user->hasPermission('posts.view'); } public function create(User $user): bool { return $user->hasPermission('posts.create'); } public function update(User $user, Post $post): bool { return $user->hasPermission('posts.edit') || $user->id === $post->author_id; } public function delete(User $user, Post $post): bool { return $user->hasRole('admin') || ($user->hasPermission('posts.delete') && $user->id === $post->author_id); } public function publish(User $user, Post $post): bool { return $user->hasPermission('posts.publish') && $post->status !== 'archived'; } } ``` ## Complete Module Example Here is a complete example of an admin module with menus, modals, and authorization. ### Directory Structure ``` Mod/Blog/ ├── Boot.php ├── Models/ │ └── Post.php ├── Policies/ │ └── PostPolicy.php ├── View/ │ ├── Blade/ │ │ └── admin/ │ │ ├── posts-list.blade.php │ │ └── post-editor.blade.php │ └── Modal/ │ └── Admin/ │ ├── PostsList.php │ └── PostEditor.php └── Routes/ └── admin.php ``` ### Boot.php ```php 'onAdminPanel', ]; public function boot(): void { // Register policy Gate::policy(Post::class, PostPolicy::class); } public function onAdminPanel(AdminPanelBooting $event): void { // Views $event->views('blog', __DIR__.'/View/Blade'); // Routes $event->routes(fn () => require __DIR__.'/Routes/admin.php'); // Menu app(AdminMenuRegistry::class)->register($this); // Livewire components $event->livewire('blog.admin.posts-list', View\Modal\Admin\PostsList::class); $event->livewire('blog.admin.post-editor', View\Modal\Admin\PostEditor::class); } public function adminMenuItems(): array { return [ [ 'group' => 'services', 'priority' => self::PRIORITY_NORMAL, 'entitlement' => 'core.srv.blog', 'permissions' => ['posts.view'], 'item' => fn () => [ 'label' => 'Blog', 'icon' => 'newspaper', 'href' => route('admin.blog.posts'), 'active' => request()->routeIs('admin.blog.*'), 'color' => 'blue', 'badge' => $this->getDraftCount(), 'children' => [ [ 'label' => 'All Posts', 'href' => route('admin.blog.posts'), 'icon' => 'document-text', 'active' => request()->routeIs('admin.blog.posts'), ], [ 'label' => 'Create Post', 'href' => route('admin.blog.posts.create'), 'icon' => 'plus', 'active' => request()->routeIs('admin.blog.posts.create'), ], ], ], ], ]; } protected function getDraftCount(): ?int { $count = Post::draft()->count(); return $count > 0 ? $count : null; } } ``` ### Routes/admin.php ```php prefix('admin/blog') ->name('admin.blog.') ->group(function () { Route::get('/posts', PostsList::class)->name('posts'); Route::get('/posts/create', PostEditor::class)->name('posts.create'); Route::get('/posts/{post}/edit', PostEditor::class)->name('posts.edit'); }); ``` ### View/Modal/Admin/PostsList.php ```php resetPage(); } #[Computed] public function posts() { return Post::query() ->when($this->search, fn ($q) => $q->where('title', 'like', "%{$this->search}%")) ->when($this->status, fn ($q) => $q->where('status', $this->status)) ->orderByDesc('created_at') ->paginate(20); } public function delete(int $postId): void { $post = Post::findOrFail($postId); $this->authorize('delete', $post); $post->delete(); session()->flash('success', 'Post deleted.'); } public function render(): View { return view('blog::admin.posts-list'); } } ``` ### View/Blade/admin/posts-list.blade.php ```blade| Title | Status | Date | Actions |
|---|---|---|---|
| {{ $post->title }} | {{ ucfirst($post->status) }} | {{ $post->created_at->format('M d, Y') }} | @can('update', $post) Edit @endcan @can('delete', $post) @endcan |
| No posts found. | |||