# Admin Framework

Streamlined, attribute-based admin panel framework for the Custom PHP Framework.

## Overview

The Admin Framework provides a unified, consistent approach to building admin interfaces with:

- **Attribute-Driven Configuration** - Use `#[AdminResource]` for controller-level metadata
- **Pure Composition** - No inheritance, all functionality via dependency injection
- **Factory Pattern** - AdminTableFactory, AdminFormFactory for building components
- **Interactive JavaScript** - Auto-initializing data tables with sorting, searching, pagination
- **Auto-Generated APIs** - CRUD endpoints with AdminApiHandler
- **Consistent Rendering** - AdminPageRenderer for unified page layouts

## Architecture

```
┌─────────────────────┐
│  AdminResource      │  Controller Attribute
│  (Metadata)         │
└──────────┬──────────┘
           │
           ├──────────────────────┬──────────────────────┬─────────────────────┐
           │                      │                      │                     │
┌──────────▼──────────┐  ┌───────▼──────────┐  ┌───────▼──────────┐  ┌──────▼────────┐
│ AdminTableFactory   │  │ AdminFormFactory │  │ AdminPageRenderer│  │ AdminApiHandler│
│ (Build Tables)      │  │ (Build Forms)    │  │ (Render Pages)   │  │ (CRUD API)    │
└──────────┬──────────┘  └───────┬──────────┘  └───────┬──────────┘  └──────┬────────┘
           │                      │                      │                     │
           ├──────────────────────┴──────────────────────┘                     │
           │                                                                    │
┌──────────▼──────────┐  ┌────────────────────┐  ┌─────────────────────────┐ │
│ Table Module        │  │ FormBuilder Module │  │ Templates               │ │
│ (View/Table)        │  │ (View/FormBuilder) │  │ (admin-index/form)      │ │
└─────────────────────┘  └────────────────────┘  └─────────────────────────┘ │
                                                                               │
┌──────────────────────────────────────────────────────────────────────────┐ │
│ JavaScript AdminDataTable Module                                          │ │
│ - Auto-initialization via data attributes                                 │◄┘
│ - AJAX loading with sorting, searching, pagination                        │
└────────────────────────────────────────────────────────────────────────────┘
```

## Core Components

### 1. AdminResource Attribute

Controller-level attribute for admin resource configuration.

```php
#[AdminResource(
    name: 'users',              // Resource name (route prefix)
    singularName: 'User',       // Singular display name
    pluralName: 'Users',        // Plural display name
    icon: 'user',               // Icon identifier
    enableApi: true,            // Auto-generate CRUD API
    enableCrud: true            // Enable CRUD operations
)]
final readonly class UserAdminController
{
    // ...
}
```

### 2. AdminTableConfig Value Object

Type-safe table configuration.

```php
$tableConfig = AdminTableConfig::create(
    resource: 'users',
    columns: [
        'id' => [
            'label' => 'ID',
            'sortable' => true,
            'class' => 'text-center',
        ],
        'email' => [
            'label' => 'Email',
            'sortable' => true,
            'searchable' => true,
        ],
        'status' => [
            'label' => 'Status',
            'formatter' => 'status',  // badge, date, boolean, etc.
        ],
    ],
    sortable: true,
    searchable: true
);
```

**Available Formatters:**
- `date` - DateFormatter for date/time values
- `status` - StatusFormatter with badges
- `boolean` - BooleanFormatter (German: Ja/Nein with badges)
- `currency` - CurrencyFormatter
- `number` - NumberFormatter
- `masked` - MaskedFormatter for sensitive data

### 3. AdminFormConfig Value Object

Type-safe form configuration.

```php
$formConfig = new AdminFormConfig(
    resource: 'users',
    action: '/admin/users',
    method: Method::POST,
    fields: [
        'email' => [
            'type' => 'email',
            'label' => 'Email Address',
            'required' => true,
            'placeholder' => 'user@example.com',
        ],
        'name' => [
            'type' => 'text',
            'label' => 'Full Name',
            'required' => true,
        ],
        'role' => [
            'type' => 'select',
            'label' => 'Role',
            'options' => [
                'user' => 'User',
                'admin' => 'Administrator',
            ],
        ],
        'active' => [
            'type' => 'checkbox',
            'label' => 'Active',
        ],
    ]
);
```

**Supported Field Types:**
- `text` - Text input
- `email` - Email input
- `password` - Password input
- `textarea` - Multi-line text
- `file` - File upload
- `hidden` - Hidden field
- `select` - Dropdown select
- `checkbox` - Checkbox input

### 4. AdminTableFactory

Creates interactive tables using the existing Table module.

```php
final readonly class ExampleController
{
    public function __construct(
        private AdminTableFactory $tableFactory,
    ) {}

    public function index(): ViewResult
    {
        $tableConfig = AdminTableConfig::create(/* ... */);
        $data = $this->repository->findAll();

        $table = $this->tableFactory->create($tableConfig, $data);

        // Table automatically includes data attributes for JavaScript:
        // data-resource="users"
        // data-api-endpoint="/admin/api/users"
        // data-sortable="true"
        // data-searchable="true"
    }
}
```

### 5. AdminFormFactory

Creates forms using the existing FormBuilder module.

```php
final readonly class ExampleController
{
    public function __construct(
        private AdminFormFactory $formFactory,
    ) {}

    public function create(): ViewResult
    {
        $formConfig = new AdminFormConfig(/* ... */);
        $form = $this->formFactory->create($formConfig);

        // For edit forms, provide existing data:
        $formConfig = $formConfig->withData($user->toArray());
        $form = $this->formFactory->create($formConfig);
    }
}
```

### 6. AdminPageRenderer

Consistent rendering for admin pages.

```php
final readonly class ExampleController
{
    public function __construct(
        private AdminPageRenderer $pageRenderer,
    ) {}

    // Index/list page
    public function index(): ViewResult
    {
        return $this->pageRenderer->renderIndex(
            resource: 'users',
            table: $table,
            title: 'Users',
            actions: [
                [
                    'url' => '/admin/users/create',
                    'label' => 'Create User',
                    'icon' => 'plus',
                ],
            ]
        );
    }

    // Form page (create/edit)
    public function create(): ViewResult
    {
        return $this->pageRenderer->renderForm(
            resource: 'users',
            form: $form,
            title: 'Create User',
            subtitle: 'Add a new user to the system'
        );
    }

    // Detail page
    public function show(): ViewResult
    {
        return $this->pageRenderer->renderShow(
            resource: 'users',
            title: 'User Details',
            data: [
                'user' => $user->toArray(),
                'stats' => $userStats,
            ]
        );
    }
}
```

### 7. AdminApiHandler

Auto-generated CRUD API endpoints.

```php
final readonly class ExampleController
{
    public function __construct(
        private AdminApiHandler $apiHandler,
        private UserRepository $repository,
    ) {}

    #[Route('/admin/api/users', Method::GET)]
    public function apiList(HttpRequest $request): JsonResult
    {
        // Handles: page, per_page, sort_by, sort_dir, search
        return $this->apiHandler->handleList($request, $this->repository);
    }

    #[Route('/admin/api/users/{id}', Method::GET)]
    public function apiGet(string $id): JsonResult
    {
        return $this->apiHandler->handleGet($id, $this->repository);
    }

    #[Route('/admin/api/users', Method::POST)]
    public function apiCreate(HttpRequest $request): JsonResult
    {
        return $this->apiHandler->handleCreate($request, $this->repository);
    }

    #[Route('/admin/api/users/{id}', Method::PUT)]
    public function apiUpdate(string $id, HttpRequest $request): JsonResult
    {
        return $this->apiHandler->handleUpdate($id, $request, $this->repository);
    }

    #[Route('/admin/api/users/{id}', Method::DELETE)]
    public function apiDelete(string $id): JsonResult
    {
        return $this->apiHandler->handleDelete($id, $this->repository);
    }
}
```

### 8. JavaScript AdminDataTable Module

Interactive data table with AJAX operations.

**Auto-Initialization:**
```javascript
// Automatically initializes on elements with both attributes:
// [data-resource][data-api-endpoint]

document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('[data-resource][data-api-endpoint]').forEach(table => {
        new AdminDataTable(table).init();
    });
});
```

**Features:**
- ✅ AJAX loading with loading states
- ✅ Column sorting (click headers)
- ✅ Search/filtering (debounced)
- ✅ Pagination
- ✅ Empty state handling
- ✅ Error handling
- ✅ HTML escaping for security

**Data Attributes:**
```html
<table
    data-resource="users"
    data-api-endpoint="/admin/api/users"
    data-sortable="true"
    data-searchable="true"
    data-paginated="true"
    data-per-page="25"
>
    <!-- Table content -->
</table>
```

## Complete Example

See `src/Application/Admin/UserAdminController.php` for a complete working example.

### Step 1: Create Controller with AdminResource

```php
#[AdminResource(
    name: 'users',
    singularName: 'User',
    pluralName: 'Users',
    icon: 'user',
    enableApi: true,
    enableCrud: true
)]
final readonly class UserAdminController
{
    public function __construct(
        private AdminPageRenderer $pageRenderer,
        private AdminTableFactory $tableFactory,
        private AdminFormFactory $formFactory,
        private AdminApiHandler $apiHandler,
        private UserRepository $repository,
    ) {}
}
```

### Step 2: Implement Index Route

```php
#[Route('/admin/users', Method::GET)]
public function index(): ViewResult
{
    $tableConfig = AdminTableConfig::create(
        resource: 'users',
        columns: [
            'id' => ['label' => 'ID', 'sortable' => true],
            'email' => ['label' => 'Email', 'sortable' => true, 'searchable' => true],
            'name' => ['label' => 'Name', 'sortable' => true, 'searchable' => true],
            'created_at' => ['label' => 'Created', 'formatter' => 'date'],
            'status' => ['label' => 'Status', 'formatter' => 'status'],
        ]
    );

    $users = $this->repository->findAll();
    $table = $this->tableFactory->create($tableConfig, array_map(
        fn($user) => $user->toArray(),
        $users
    ));

    return $this->pageRenderer->renderIndex(
        resource: 'users',
        table: $table,
        title: 'Users',
        actions: [
            ['url' => '/admin/users/create', 'label' => 'Create User', 'icon' => 'plus'],
        ]
    );
}
```

### Step 3: Implement Create/Store Routes

```php
#[Route('/admin/users/create', Method::GET)]
public function create(): ViewResult
{
    $formConfig = new AdminFormConfig(
        resource: 'users',
        action: '/admin/users',
        method: Method::POST,
        fields: [
            'email' => ['type' => 'email', 'label' => 'Email', 'required' => true],
            'name' => ['type' => 'text', 'label' => 'Name', 'required' => true],
            'password' => ['type' => 'password', 'label' => 'Password', 'required' => true],
        ]
    );

    $form = $this->formFactory->create($formConfig);

    return $this->pageRenderer->renderForm(
        resource: 'users',
        form: $form,
        title: 'Create User'
    );
}

#[Route('/admin/users', Method::POST)]
public function store(HttpRequest $request): Redirect
{
    $data = $request->parsedBody->toArray();

    $this->repository->create([
        'email' => $data['email'],
        'name' => $data['name'],
        'password' => password_hash($data['password'], PASSWORD_DEFAULT),
    ]);

    return new Redirect('/admin/users', status: 303, flashMessage: 'User created');
}
```

### Step 4: Implement Edit/Update Routes

```php
#[Route('/admin/users/{id}/edit', Method::GET)]
public function edit(string $id): ViewResult
{
    $user = $this->repository->findById($id);

    $formConfig = (new AdminFormConfig(
        resource: 'users',
        action: "/admin/users/{$id}",
        method: Method::PUT,
        fields: [
            'email' => ['type' => 'email', 'label' => 'Email', 'required' => true],
            'name' => ['type' => 'text', 'label' => 'Name', 'required' => true],
        ]
    ))->withData($user->toArray());

    $form = $this->formFactory->create($formConfig);

    return $this->pageRenderer->renderForm(
        resource: 'users',
        form: $form,
        title: 'Edit User'
    );
}

#[Route('/admin/users/{id}', Method::PUT)]
public function update(string $id, HttpRequest $request): Redirect
{
    $data = $request->parsedBody->toArray();

    $this->repository->update($id, [
        'email' => $data['email'],
        'name' => $data['name'],
    ]);

    return new Redirect('/admin/users', status: 303, flashMessage: 'User updated');
}
```

### Step 5: Implement Delete Route

```php
#[Route('/admin/users/{id}', Method::DELETE)]
public function destroy(string $id): Redirect
{
    $this->repository->delete($id);
    return new Redirect('/admin/users', status: 303, flashMessage: 'User deleted');
}
```

### Step 6: Implement API Routes

```php
#[Route('/admin/api/users', Method::GET)]
public function apiList(HttpRequest $request): JsonResult
{
    return $this->apiHandler->handleList($request, $this->repository);
}

#[Route('/admin/api/users/{id}', Method::GET)]
public function apiGet(string $id): JsonResult
{
    return $this->apiHandler->handleGet($id, $this->repository);
}

#[Route('/admin/api/users', Method::POST)]
public function apiCreate(HttpRequest $request): JsonResult
{
    return $this->apiHandler->handleCreate($request, $this->repository);
}

#[Route('/admin/api/users/{id}', Method::PUT)]
public function apiUpdate(string $id, HttpRequest $request): JsonResult
{
    return $this->apiHandler->handleUpdate($id, $request, $this->repository);
}

#[Route('/admin/api/users/{id}', Method::DELETE)]
public function apiDelete(string $id): JsonResult
{
    return $this->apiHandler->handleDelete($id, $this->repository);
}
```

## Templates

### admin-index.view.php

Used by `AdminPageRenderer::renderIndex()`.

```php
<layout name="layouts/admin" />

<div class="admin-page">
    <div class="page-header">
        <h1>{{ title }}</h1>
        <div class="page-actions">
            <if condition="{{ actions }}">
                <for items="{{ actions }}" value="action">
                    <a href="{{ action.url }}" class="btn btn-primary">
                        <if condition="{{ action.icon }}">
                            <i class="icon-{{ action.icon }}"></i>
                        </if>
                        {{ action.label }}
                    </a>
                </for>
            </if>
        </div>
    </div>

    <if condition="{{ searchable }}">
        <div class="table-controls">
            <input type="text"
                   class="form-control search-input"
                   data-table-search="{{ resource }}"
                   placeholder="Search...">
        </div>
    </if>

    <div class="table-container">
        {{ table }}
    </div>

    <div class="pagination-container" data-table-pagination="{{ resource }}"></div>
</div>
```

### admin-form.view.php

Used by `AdminPageRenderer::renderForm()`.

```php
<layout name="layouts/admin" />

<div class="admin-page">
    <div class="page-header">
        <h1>{{ title }}</h1>
        <if condition="{{ subtitle }}">
            <p class="subtitle">{{ subtitle }}</p>
        </if>
    </div>

    <div class="form-container">
        <div class="card">
            <div class="card-body">
                {{ form }}
            </div>
        </div>
    </div>

    <div class="form-actions">
        <a href="/admin/{{ resource }}" class="btn btn-secondary">Cancel</a>
    </div>
</div>
```

## Framework Compliance

The Admin Framework follows all framework principles:

✅ **No Inheritance** - Pure composition, all controllers are `final readonly`
✅ **Readonly Everywhere** - All framework classes are `readonly`
✅ **Value Objects** - AdminTableConfig, AdminFormConfig instead of arrays
✅ **Explicit DI** - All dependencies via constructor injection
✅ **Attribute-Driven** - `#[AdminResource]` for metadata
✅ **Existing Module Integration** - Reuses Table and FormBuilder modules
✅ **Type Safety** - Strong typing throughout, no primitive obsession
✅ **Immutability** - Value objects with transformation methods

## Best Practices

1. **Use Value Objects for Configuration** - Always use AdminTableConfig and AdminFormConfig instead of raw arrays
2. **Leverage Formatters** - Use built-in formatters for dates, status badges, booleans, etc.
3. **Provide Rich Context** - Include helpful labels, placeholders, and help text in forms
4. **Enable JavaScript Features** - Use sortable, searchable, paginated for better UX
5. **Implement All API Endpoints** - Required for JavaScript table functionality
6. **Use Consistent Naming** - Resource name should match routes and repository
7. **Inject Dependencies** - Always inject factories, renderers, handlers via constructor
8. **Type-Safe Data** - Convert entities to arrays with `toArray()` method

## Testing

```php
// Test table creation
it('creates admin table with correct configuration', function () {
    $config = AdminTableConfig::create(
        resource: 'test',
        columns: ['id' => ['label' => 'ID']]
    );

    $factory = new AdminTableFactory();
    $table = $factory->create($config, [['id' => 1]]);

    expect($table)->toBeInstanceOf(Table::class);
    expect($table->render())->toContain('data-resource="test"');
});

// Test form creation
it('creates admin form with correct fields', function () {
    $config = new AdminFormConfig(
        resource: 'test',
        action: '/test',
        method: Method::POST,
        fields: [
            'name' => ['type' => 'text', 'label' => 'Name']
        ]
    );

    $factory = new AdminFormFactory(new FormIdGenerator());
    $form = $factory->create($config);

    expect($form)->toBeInstanceOf(FormBuilder::class);
    expect($form->build())->toContain('name="name"');
});

// Test API handler
it('handles list API requests', function () {
    $handler = new AdminApiHandler();
    $repository = new InMemoryUserRepository();

    $request = HttpRequest::fromGlobals();
    $result = $handler->handleList($request, $repository);

    expect($result)->toBeInstanceOf(JsonResult::class);
    expect($result->data['success'])->toBeTrue();
    expect($result->data)->toHaveKey('pagination');
});
```

## Troubleshooting

**Tables not loading data:**
- Check API endpoint is correct: `/admin/api/{resource}`
- Verify API route is registered with correct HTTP method
- Ensure repository implements `findAll()` method
- Check browser console for JavaScript errors

**Forms not submitting:**
- Verify form action URL matches route
- Check HTTP method matches (POST for create, PUT for update)
- Ensure CSRF token is included (handled by FormBuilder automatically)

**Search not working:**
- Enable `searchable: true` in AdminTableConfig
- Mark specific columns as searchable: `'searchable' => true`
- Implement search logic in repository `findAll($filters)` method

**Sorting not working:**
- Enable `sortable: true` in AdminTableConfig
- Mark specific columns as sortable: `'sortable' => true`
- Implement sorting in repository or API handler
