From c79069120032c12c4bed6aca06cf77fb95a27711 Mon Sep 17 00:00:00 2001 From: yann64 Date: Thu, 4 Jun 2026 17:21:50 +0200 Subject: [PATCH] =?UTF-8?q?=C3=89tape=2010=20:=20interface=20admin=20(tabl?= =?UTF-8?q?eau=20de=20bord=20+=20gestion=20utilisateurs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DashboardController : stats globales (sources par statut, relevés, utilisateurs, activité mensuelle 6 mois) - UserController : liste filtrée (nom/email/rôle) + édition de rôle avec protections (auto-demotion, dernier admin) - Vue admin/dashboard : compteurs par statut cliquables, graphique barres mensuel, sources à valider, relevés récents - Vue admin/utilisateurs : liste paginée avec sections et sources assignées, page d'édition avec radio-cards - Dashboard principal enrichi : bloc accès admin, mes sources assignées triées par urgence, mes derniers relevés - Navigation : ajout Tableau de bord admin et Utilisateurs dans le menu Administration Co-Authored-By: Claude Sonnet 4.6 --- .../Controllers/Admin/DashboardController.php | 60 ++++++++ app/Http/Controllers/Admin/UserController.php | 64 ++++++++ resources/views/admin/dashboard.blade.php | 139 ++++++++++++++++++ .../views/admin/utilisateurs/edit.blade.php | 82 +++++++++++ .../views/admin/utilisateurs/index.blade.php | 116 +++++++++++++++ resources/views/dashboard.blade.php | 135 ++++++++++++++++- resources/views/layouts/navigation.blade.php | 21 ++- routes/admin.php | 5 + 8 files changed, 613 insertions(+), 9 deletions(-) create mode 100644 app/Http/Controllers/Admin/DashboardController.php create mode 100644 app/Http/Controllers/Admin/UserController.php create mode 100644 resources/views/admin/dashboard.blade.php create mode 100644 resources/views/admin/utilisateurs/edit.blade.php create mode 100644 resources/views/admin/utilisateurs/index.blade.php diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php new file mode 100644 index 0000000..16f184f --- /dev/null +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -0,0 +1,60 @@ +groupBy('status') + ->pluck('total', 'status') + ->mapWithKeys(fn ($total, $status) => [ + SourceStatus::from($status)->value => (int) $total, + ]); + + $totalSources = $sourcesByStatus->sum(); + $totalReleves = Releve::count(); + + // Utilisateurs par rôle + $usersByRole = User::selectRaw('role, count(*) as total') + ->groupBy('role') + ->pluck('total', 'role'); + + $totalUsers = $usersByRole->sum(); + + // Sources en attente de validation + $sourcesAValider = Source::with(['sourceType', 'depot']) + ->where('status', SourceStatus::AValider) + ->orderByDesc('updated_at') + ->take(10) + ->get(); + + // Relevés récents (10 derniers) + $relevesRecents = Releve::with(['source.sourceType', 'createur']) + ->orderByDesc('created_at') + ->take(10) + ->get(); + + // Activité mensuelle des 6 derniers mois + $activiteMensuelle = Releve::selectRaw("to_char(date_trunc('month', created_at), 'Mon YYYY') as mois, count(*) as total") + ->where('created_at', '>=', now()->subMonths(5)->startOfMonth()) + ->groupByRaw("date_trunc('month', created_at)") + ->orderByRaw("date_trunc('month', created_at)") + ->get(); + + return view('admin.dashboard', compact( + 'sourcesByStatus', 'totalSources', 'totalReleves', + 'usersByRole', 'totalUsers', + 'sourcesAValider', 'relevesRecents', 'activiteMensuelle' + )); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..9ef6cbf --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,64 @@ +with('sections')->orderBy('name'); + + if ($request->filled('role')) { + $query->where('role', $request->input('role')); + } + + if ($request->filled('q')) { + $q = trim($request->get('q')); + $query->where(fn ($wq) => $wq + ->where('name', 'ilike', "%{$q}%") + ->orWhere('email', 'ilike', "%{$q}%") + ); + } + + $users = $query->paginate(25)->withQueryString(); + + return view('admin.utilisateurs.index', compact('users')); + } + + public function edit(User $user): View + { + $user->load('sections', 'sourcesAssignees'); + + return view('admin.utilisateurs.edit', compact('user')); + } + + public function update(Request $request, User $user): RedirectResponse + { + $data = $request->validate([ + 'role' => ['required', new Enum(UserRole::class)], + ]); + + if ($user->id === auth()->id()) { + return back()->with('error', 'Vous ne pouvez pas modifier votre propre rôle.'); + } + + if ($user->role === UserRole::Admin && $data['role'] !== UserRole::Admin->value) { + $adminCount = User::where('role', UserRole::Admin->value)->count(); + if ($adminCount <= 1) { + return back()->with('error', 'Impossible de retirer le rôle admin au dernier administrateur.'); + } + } + + $user->update(['role' => $data['role']]); + + return back()->with('success', 'Rôle mis à jour.'); + } +} diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php new file mode 100644 index 0000000..d510990 --- /dev/null +++ b/resources/views/admin/dashboard.blade.php @@ -0,0 +1,139 @@ + + +

Tableau de bord — Administration

+
+ +
+ + {{-- Compteurs globaux --}} +
+ @php + $statusCards = [ + ['label' => 'À faire', 'key' => 'a_faire', 'color' => 'bg-gray-50 border-gray-200 text-gray-700'], + ['label' => 'En cours', 'key' => 'en_cours', 'color' => 'bg-blue-50 border-blue-200 text-blue-700'], + ['label' => 'À valider', 'key' => 'a_valider', 'color' => 'bg-yellow-50 border-yellow-200 text-yellow-700'], + ['label' => 'Terminé', 'key' => 'termine', 'color' => 'bg-green-50 border-green-200 text-green-700'], + ]; + @endphp + @foreach($statusCards as $card) + + {{ $sourcesByStatus[$card['key']] ?? 0 }} + {{ $card['label'] }} + source{{ ($sourcesByStatus[$card['key']] ?? 0) > 1 ? 's' : '' }} + + @endforeach +
+ + {{-- Ligne de métriques secondaires --}} +
+
+
+ + + +
+
+

{{ number_format($totalReleves) }}

+

relevé{{ $totalReleves > 1 ? 's' : '' }} saisi{{ $totalReleves > 1 ? 's' : '' }}

+
+
+
+
+ + + +
+
+

{{ $totalUsers }}

+

utilisateur{{ $totalUsers > 1 ? 's' : '' }}

+
+
+
+

Répartition des rôles

+
+ @foreach(\App\Enums\UserRole::cases() as $role) + @php $count = (int)($usersByRole[$role->value] ?? 0); @endphp +
+ {{ $role->label() }} + {{ $count }} +
+ @endforeach +
+
+
+ + {{-- Activité mensuelle (6 derniers mois) --}} + @if($activiteMensuelle->isNotEmpty()) +
+

Relevés saisis — 6 derniers mois

+ @php $maxReleves = $activiteMensuelle->max('total') ?: 1; @endphp +
+ @foreach($activiteMensuelle as $mois) + @php $h = max(4, round(($mois->total / $maxReleves) * 96)); @endphp +
+ {{ $mois->total }} +
+ {{ $mois->mois }} +
+ @endforeach +
+
+ @endif + +
+ + {{-- Sources à valider --}} +
+
+

En attente de validation

+ Voir tout +
+ @forelse($sourcesAValider as $source) +
+
+ {{ $source->nom }} +

{{ $source->sourceType->nom }}

+
+ + {{ $source->updated_at->diffForHumans() }} + +
+ @empty +

Aucune source en attente.

+ @endforelse +
+ + {{-- Relevés récents --}} +
+
+

Derniers relevés saisis

+ Recherche +
+ @forelse($relevesRecents as $releve) +
+
+ + {{ $releve->nom ?? '—' }} + @if($releve->prenom) {{ $releve->prenom }} @endif + +

+ {{ $releve->source->nom }} · {{ $releve->createur?->name ?? '?' }} +

+
+ + {{ $releve->created_at->diffForHumans() }} + +
+ @empty +

Aucun relevé pour l'instant.

+ @endforelse +
+
+
+
diff --git a/resources/views/admin/utilisateurs/edit.blade.php b/resources/views/admin/utilisateurs/edit.blade.php new file mode 100644 index 0000000..90a80c5 --- /dev/null +++ b/resources/views/admin/utilisateurs/edit.blade.php @@ -0,0 +1,82 @@ + + +
+ ← Utilisateurs + / +

{{ $user->name }}

+
+
+ +
+ + @if(session('success')) +
{{ session('success') }}
+ @endif + @if(session('error')) +
{{ session('error') }}
+ @endif + + {{-- Informations --}} +
+

Informations

+
+
Nom
+
{{ $user->name }}
+
E-mail
+
{{ $user->email }}
+
Inscrit le
+
{{ $user->created_at->format('d/m/Y') }}
+
Sections
+
+ @if($user->sections->isNotEmpty()) + {{ $user->sections->pluck('nom')->join(', ') }} + @else + — + @endif +
+
Sources assignées
+
{{ $user->sourcesAssignees->count() }}
+
+
+ + {{-- Modifier le rôle --}} +
+

Rôle

+
+ @csrf @method('PUT') +
+ @foreach(\App\Enums\UserRole::cases() as $role) + + @endforeach +
+
+ + + Annuler + +
+
+
+
+
diff --git a/resources/views/admin/utilisateurs/index.blade.php b/resources/views/admin/utilisateurs/index.blade.php new file mode 100644 index 0000000..8631a6b --- /dev/null +++ b/resources/views/admin/utilisateurs/index.blade.php @@ -0,0 +1,116 @@ + + +

Gestion des utilisateurs

+
+ +
+ + {{-- Filtres --}} + @php $hasFilters = request()->anyFilled(['role', 'q']); @endphp +
+
+
+ + +
+
+ + +
+
+ + @if($hasFilters) + + Effacer + + @endif +
+
+
+ + {{-- Tableau --}} +
+ + + + + + + + + + + + + + @forelse($users as $user) + @php + $roleColors = [ + 'admin' => 'bg-red-100 text-red-700', + 'section_manager' => 'bg-blue-100 text-blue-700', + 'member' => 'bg-gray-100 text-gray-600', + ]; + $color = $roleColors[$user->role->value] ?? 'bg-gray-100 text-gray-600'; + @endphp + + + + + + + + + + @empty + + + + @endforelse + +
NomE-mailRôleSectionsSources assignéesInscrit le
+ {{ $user->name }} + @if($user->id === auth()->id()) + (vous) + @endif + {{ $user->email }} + + {{ $user->role->label() }} + + + @if($user->sections->isNotEmpty()) + {{ $user->sections->pluck('nom')->join(', ') }} + @else + — + @endif + {{ $user->sources_assignees_count ?: '—' }} + {{ $user->created_at->format('d/m/Y') }} + + @if($user->id !== auth()->id()) + + Modifier + + @endif +
+ Aucun utilisateur trouvé. +
+ @if($users->hasPages()) +
{{ $users->links() }}
+ @endif +
+
+
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 66028f2..d6daff5 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -1,17 +1,136 @@ -

- {{ __('Dashboard') }} -

+

Tableau de bord

-
-
-
-
- {{ __("You're logged in!") }} +
+ + @php $user = auth()->user(); @endphp + + {{-- Bloc admin : lien vers le dashboard admin --}} + @if($user->isAdmin()) +
+
+

Accès administrateur

+

Statistiques globales, gestion des utilisateurs et des sections.

+ + Tableau de bord admin → + +
+ @endif + + {{-- Mes sources assignées --}} + @php + $mesSources = $user->sourcesAssignees() + ->with('sourceType') + ->withCount('releves') + ->orderByRaw("CASE status + WHEN 'en_cours' THEN 0 + WHEN 'a_valider' THEN 1 + WHEN 'a_faire' THEN 2 + WHEN 'termine' THEN 3 + ELSE 4 END") + ->get(); + @endphp + + @if($mesSources->isNotEmpty()) +
+
+

Mes sources

+ Voir toutes +
+
+ + + + + + + + + + + + @foreach($mesSources as $source) + @php + $colors = [ + 'a_faire' => 'bg-gray-100 text-gray-600', + 'en_cours' => 'bg-blue-100 text-blue-700', + 'a_valider' => 'bg-yellow-100 text-yellow-700', + 'termine' => 'bg-green-100 text-green-700', + ]; + $c = $colors[$source->status->value] ?? 'bg-gray-100 text-gray-600'; + @endphp + + + + + + + + @endforeach + +
SourceTypeStatutRelevés
+ {{ $source->nom }} + {{ $source->sourceType->nom }} + + {{ $source->status->label() }} + + {{ $source->releves_count }} + Saisir → +
+ @else +
+ + + +

Vous n'êtes assigné à aucune source pour l'instant.

+ + Voir les sources disponibles + +
+ @endif + + {{-- Mes derniers relevés saisis --}} + @php + $mesReleves = \App\Models\Releve::with(['source.sourceType']) + ->where('created_by', $user->id) + ->orderByDesc('created_at') + ->take(8) + ->get(); + @endphp + + @if($mesReleves->isNotEmpty()) +
+
+

Mes derniers relevés

+ Recherche +
+
+ @foreach($mesReleves as $releve) +
+ + + {{ $releve->created_at->diffForHumans() }} + +
+ @endforeach +
+
+ @endif +
diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index c93819e..9d0f98d 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -40,7 +40,15 @@
+ class="absolute top-14 mt-1 w-56 bg-white rounded-md shadow-lg border border-gray-100 z-50"> + @if(auth()->user()->isAdmin()) + + Tableau de bord admin + + Utilisateurs + @endif Sections @if(auth()->user()->isAdmin()) @@ -135,6 +143,14 @@ Recherche @if(auth()->user()->isSectionManager()) + @if(auth()->user()->isAdmin()) + + Tableau de bord admin + + + Utilisateurs + + @endif Sections @@ -145,6 +161,9 @@ Types de sources + + Types de lieux + @endif @endif
diff --git a/routes/admin.php b/routes/admin.php index c7eb5da..4fa4f09 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -1,12 +1,17 @@ prefix('admin')->name('admin.')->group(function () { + Route::get('dashboard', [DashboardController::class, 'index'])->name('dashboard'); + + Route::resource('utilisateurs', UserController::class)->only(['index', 'edit', 'update']); Route::resource('lieu-types', LieuTypeController::class) ->parameters(['lieu-types' => 'lieuType']) ->except(['show']);