feat: complete administration and reporting

This commit is contained in:
2026-08-04 17:40:52 +01:00
parent d829f34580
commit 7a4ac48aaf
42 changed files with 2595 additions and 76 deletions

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Domain\Administration\Services\NotificationTemplateService;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class NotificationTemplateController extends Controller
{
public function edit(NotificationTemplateService $service): Response
{
return Inertia::render('admin/notification-templates', [
'definitions' => $service->definitions(),
'templates' => $service->templates(),
'variables' => ['{reference}', '{requester}', '{starts_at}', '{ends_at}', '{comment}'],
]);
}
public function update(Request $request, NotificationTemplateService $service): RedirectResponse
{
$rules = [];
foreach (array_keys($service->definitions()) as $event) {
$rules["templates.{$event}.subject"] = ['required', 'string', 'max:200'];
$rules["templates.{$event}.message"] = ['required', 'string', 'max:2000'];
}
/** @var array{templates:array<string,array{subject:string,message:string}>} $data */
$data = $request->validate($rules);
$service->update($data['templates'], $request->user());
return back()->with('success', 'Modelos de notificação atualizados.');
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Domain\Administration\Services\ReservationPolicy;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class ReservationPolicyController extends Controller
{
public function edit(ReservationPolicy $policy): Response
{
return Inertia::render('admin/reservation-policy', [
'definitions' => $policy->definitions(),
'values' => $policy->values(),
]);
}
public function update(Request $request, ReservationPolicy $policy): RedirectResponse
{
$rules = [];
foreach ($policy->definitions() as $key => $definition) {
$rules[str_replace('.', '\\.', $key)] = ['required', 'integer', 'min:'.$definition['min'], 'max:'.$definition['max']];
}
$request->validate($rules);
/** @var array<string, int> $values */
$values = collect(array_keys($policy->definitions()))
->mapWithKeys(fn (string $key): array => [$key => (int) $request->all()[$key]])
->all();
$policy->update($values, $request->user());
return back()->with('success', 'Regras de reserva atualizadas e aplicadas a novos pedidos.');
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Domain\Identity\Enums\RoleName;
use App\Domain\Identity\Models\Role;
use App\Domain\Identity\Services\UserAdministrationService;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
use Inertia\Inertia;
use Inertia\Response;
class UserController extends Controller
{
public function index(Request $request): Response
{
$filters = $request->validate([
'search' => ['nullable', 'string', 'max:100'],
'role' => ['nullable', Rule::enum(RoleName::class)],
'status' => ['nullable', Rule::in(['active', 'inactive'])],
]);
return Inertia::render('admin/users/index', [
'users' => User::query()
->with('roles:id,name,description')
->when($filters['search'] ?? null, fn ($query, string $search) => $query
->where(fn ($query) => $query
->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%")
->orWhere('department', 'like', "%{$search}%")))
->when($filters['role'] ?? null, fn ($query, string $role) => $query
->whereHas('roles', fn ($query) => $query->where('name', $role)))
->when(($filters['status'] ?? null) === 'active', fn ($query) => $query->where('active', true))
->when(($filters['status'] ?? null) === 'inactive', fn ($query) => $query->where('active', false))
->orderBy('name')
->paginate(20)
->withQueryString(),
'roles' => Role::query()->orderBy('name')->get(['id', 'name', 'description']),
'filters' => $filters,
]);
}
public function store(Request $request, UserAdministrationService $service): RedirectResponse
{
$data = $this->validatedData($request, null);
$user = $service->create($request->user(), $data);
return back()->with('success', "Utilizador {$user->name} criado.");
}
public function update(Request $request, User $user, UserAdministrationService $service): RedirectResponse
{
$data = $this->validatedData($request, $user);
$service->update($user, $request->user(), $data);
return back()->with('success', "Utilizador {$user->name} atualizado.");
}
/**
* @return array{name:string,email:string,department:?string,snipe_user_id:?int,password:?string,active:bool,roles:list<string>}
*/
private function validatedData(Request $request, ?User $user): array
{
$roleNames = array_map(fn (RoleName $role): string => $role->value, RoleName::cases());
/** @var array{name:string,email:string,department:?string,snipe_user_id:?int,password:?string,active:bool,roles:list<string>} $data */
$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user?->id)],
'department' => ['nullable', 'string', 'max:255'],
'snipe_user_id' => ['nullable', 'integer', 'min:1'],
'password' => [$user === null ? 'required' : 'nullable', 'string', Password::min(12)],
'active' => ['required', 'boolean'],
'roles' => ['required', 'array', 'min:1'],
'roles.*' => ['required', 'string', 'distinct', Rule::in($roleNames)],
]);
return $data;
}
}