Usuwanie uzytkownika
This commit is contained in:
parent
a680be3fd3
commit
97adda56d3
@ -12,11 +12,11 @@ class DashboardController extends Controller
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$totalUsers = User::count();
|
||||
$adminCount = User::role('admin')->count();
|
||||
$managerCount = User::role('kierownik')->count();
|
||||
$workerCount = User::role('pracownik')->count();
|
||||
$recentUsers = User::latest()->take(5)->get();
|
||||
$totalUsers = User::whereNull('deleted_at')->count();
|
||||
$adminCount = User::role('admin')->whereNull('deleted_at')->count();
|
||||
$managerCount = User::role('kierownik')->whereNull('deleted_at')->count();
|
||||
$workerCount = User::role('pracownik')->whereNull('deleted_at')->count();
|
||||
$recentUsers = User::whereNull('deleted_at')->latest()->take(5)->get();
|
||||
|
||||
return view('dashboard', compact(
|
||||
'totalUsers',
|
||||
|
||||
@ -58,7 +58,7 @@ class ProfileController extends Controller
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
$user->forceDelete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
@ -22,7 +22,7 @@ class UserController extends Controller
|
||||
$direction = $request->query('direction') === 'desc' ? 'desc' : 'asc';
|
||||
$search = trim($request->query('search'));
|
||||
|
||||
$query = User::with('roles');
|
||||
$query = User::with('roles')->whereNull('deleted_at');
|
||||
|
||||
// Wyszukiwanie w imieniu, nazwisku i adresie email
|
||||
if ($search) {
|
||||
@ -46,6 +46,35 @@ class UserController extends Controller
|
||||
return view('users.index', compact('users'));
|
||||
}
|
||||
|
||||
public function deleted(Request $request)
|
||||
{
|
||||
$allowedSorts = ['name', 'email'];
|
||||
|
||||
$sort = in_array($request->query('sort'), $allowedSorts) ? $request->query('sort') : 'name';
|
||||
$direction = $request->query('direction') === 'desc' ? 'desc' : 'asc';
|
||||
$search = trim($request->query('search'));
|
||||
|
||||
$query = User::with('roles')->onlyTrashed();
|
||||
|
||||
if ($search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('surname', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
if ($sort === 'name') {
|
||||
$query->orderBy('name', $direction)->orderBy('surname', $direction);
|
||||
} else {
|
||||
$query->orderBy($sort, $direction);
|
||||
}
|
||||
|
||||
$users = $query->paginate(10)->withQueryString();
|
||||
|
||||
return view('users.deleted', compact('users'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formularz dodawania nowego użytkownika.
|
||||
* Wykorzystuje gotowy widok z Breeze (auth.register).
|
||||
@ -129,6 +158,14 @@ class UserController extends Controller
|
||||
|
||||
$user->delete();
|
||||
|
||||
return redirect()->route('users.index')->with('status', 'Użytkownik został usunięty.');
|
||||
return redirect()->route('users.index')->with('status', 'Użytkownik został oznaczony jako usunięty.');
|
||||
}
|
||||
|
||||
public function restore($userId)
|
||||
{
|
||||
$user = User::withTrashed()->findOrFail($userId);
|
||||
$user->restore();
|
||||
|
||||
return redirect()->route('users.deleted')->with('status', 'Użytkownik został przywrócony.');
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ namespace App\Models;
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Spatie\Permission\Traits\HasRoles; // 1. Import traitu Spatie
|
||||
@ -12,7 +13,7 @@ use Spatie\Permission\Traits\HasRoles; // 1. Import traitu Spatie
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable, HasRoles; // 2. Dodanie HasRoles
|
||||
use HasFactory, Notifiable, HasRoles, SoftDeletes; // 2. Dodanie HasRoles i soft delete
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
};
|
||||
105
resources/views/users/deleted.blade.php
Normal file
105
resources/views/users/deleted.blade.php
Normal file
@ -0,0 +1,105 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-white leading-tight">
|
||||
{{ __('Użytkownicy usunięci') }}
|
||||
</h2>
|
||||
|
||||
<a href="{{ route('users.index') }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-md shadow-sm transition ease-in-out duration-150 gap-2">
|
||||
<span>{{ __('Powrót do użytkowników') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg border border-gray-200 dark:border-gray-700/50">
|
||||
<div class="p-6 text-gray-900 dark:text-white">
|
||||
<div class="mb-6 flex items-center justify-between gap-4">
|
||||
<form method="GET" action="{{ route('users.deleted') }}" class="flex items-center gap-2 w-full max-w-xs">
|
||||
@if(request('sort'))
|
||||
<input type="hidden" name="sort" value="{{ request('sort') }}">
|
||||
@endif
|
||||
@if(request('direction'))
|
||||
<input type="hidden" name="direction" value="{{ request('direction') }}">
|
||||
@endif
|
||||
|
||||
<div class="w-full">
|
||||
<x-text-input
|
||||
type="text"
|
||||
name="search"
|
||||
:value="request('search')"
|
||||
placeholder="Szukaj usuniętego użytkownika..."
|
||||
class="block w-full px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<x-primary-button type="submit">
|
||||
Szukaj
|
||||
</x-primary-button>
|
||||
|
||||
@if(request('search'))
|
||||
<a href="{{ route('users.deleted', request()->only(['sort', 'direction'])) }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition ease-in-out duration-150 whitespace-nowrap">
|
||||
Wyczyść
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full border-collapse" style="table-layout: fixed;">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-700 text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-300">
|
||||
<th scope="col" class="py-3.5 px-4" style="text-align: center !important; width: 25%;">Imię i nazwisko</th>
|
||||
<th scope="col" class="py-3.5 px-4" style="text-align: center !important; width: 35%;">Adres e-mail</th>
|
||||
<th scope="col" class="py-3.5 px-4" style="text-align: center !important; width: 20%;">Usunięty</th>
|
||||
<th scope="col" class="py-3.5 px-4" style="text-align: center !important; width: 20%;">Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700/60 text-sm">
|
||||
@forelse($users as $user)
|
||||
<tr class="transition-colors duration-150">
|
||||
<td class="py-4 px-4 font-medium text-gray-900 dark:text-white align-middle" style="text-align: center !important;">
|
||||
{{ $user->name }} {{ $user->surname }}
|
||||
</td>
|
||||
|
||||
<td class="py-4 px-4 text-gray-600 dark:text-gray-300 align-middle" style="text-align: center !important;">
|
||||
{{ $user->email }}
|
||||
</td>
|
||||
|
||||
<td class="py-4 px-4 text-gray-600 dark:text-gray-300 align-middle" style="text-align: center !important;">
|
||||
{{ $user->deleted_at?->format('d.m.Y H:i') ?? '-' }}
|
||||
</td>
|
||||
|
||||
<td class="py-4 px-4 align-middle" style="text-align: center !important;">
|
||||
<form method="POST" action="{{ route('users.restore', ['user' => $user->id]) }}" onsubmit="return confirm('Czy na pewno chcesz przywrócić tego użytkownika?');">
|
||||
@csrf
|
||||
<button type="submit" class="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 font-medium text-sm transition">
|
||||
Przywróć
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="py-8 text-gray-500 dark:text-gray-400" style="text-align: center !important;">
|
||||
Brak usuniętych użytkowników.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($users->hasPages())
|
||||
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
{{ $users->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@ -60,12 +60,24 @@
|
||||
</div>
|
||||
|
||||
<!-- Przyciski -->
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Zapisz') }}</x-primary-button>
|
||||
<a href="{{ route('users.index') }}" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition">
|
||||
{{ __('Anuluj') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if(auth()->id() !== $user->id)
|
||||
<form method="POST" action="{{ route('users.destroy', $user) }}" onsubmit="return confirm('Czy na pewno chcesz usunąć tego użytkownika?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-500 text-white text-sm font-medium rounded-md shadow-sm transition">
|
||||
{{ __('Usuń użytkownika') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -6,6 +6,12 @@
|
||||
</h2>
|
||||
|
||||
@hasanyrole('admin|kierownik')
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ route('users.deleted') }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-white text-sm font-medium rounded-md shadow-sm transition ease-in-out duration-150 gap-2">
|
||||
<span>{{ __('Użytkownicy usunięci') }}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('users.create') }}"
|
||||
class="inline-flex items-center px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-medium rounded-md shadow-sm transition ease-in-out duration-150 gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@ -13,6 +19,7 @@
|
||||
</svg>
|
||||
<span>{{ __('Dodaj użytkownika') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
@endhasanyrole
|
||||
</div>
|
||||
</x-slot>
|
||||
@ -141,9 +148,21 @@
|
||||
</td>
|
||||
|
||||
<td class="py-4 px-4 align-middle" style="text-align: center !important;">
|
||||
<div class="flex items-center justify-center gap-3">
|
||||
<a href="{{ route('users.edit', $user) }}" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300 font-medium text-sm transition">
|
||||
Edytuj
|
||||
</a>
|
||||
|
||||
@if(auth()->id() !== $user->id)
|
||||
<form method="POST" action="{{ route('users.destroy', $user) }}" onsubmit="return confirm('Czy na pewno chcesz usunąć tego użytkownika?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300 font-medium text-sm transition">
|
||||
Usuń
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
@ -25,11 +25,13 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
||||
// Zarządzanie użytkownikami (tylko dla posiadaczy uprawnienia users_access)
|
||||
Route::middleware(['can:users_access'])->group(function () {
|
||||
Route::get('/users', [UserController::class, 'index'])->name('users.index');
|
||||
Route::get('/users/deleted', [UserController::class, 'deleted'])->name('users.deleted');
|
||||
Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
|
||||
Route::post('/users', [UserController::class, 'store'])->name('users.store');
|
||||
Route::get('/users/{user}/edit', [UserController::class, 'edit'])->name('users.edit');
|
||||
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
|
||||
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
|
||||
Route::post('/users/{user}/restore', [UserController::class, 'restore'])->name('users.restore');
|
||||
});
|
||||
|
||||
// Moduł Raporty
|
||||
|
||||
44
tests/Feature/UserSoftDeleteTest.php
Normal file
44
tests/Feature/UserSoftDeleteTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
test('user can be soft deleted and hidden from the list', function () {
|
||||
Permission::firstOrCreate(['name' => 'users_access']);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$adminRole = Role::firstOrCreate(['name' => 'admin']);
|
||||
$admin->assignRole($adminRole);
|
||||
$admin->givePermissionTo('users_access');
|
||||
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this
|
||||
->actingAs($admin)
|
||||
->delete('/users/' . $user->id);
|
||||
|
||||
$response->assertRedirect(route('users.index'));
|
||||
$this->assertSoftDeleted($user);
|
||||
$this->assertNotNull($user->fresh()->deleted_at);
|
||||
});
|
||||
|
||||
test('deleted users can be listed and restored', function () {
|
||||
Permission::firstOrCreate(['name' => 'users_access']);
|
||||
|
||||
$admin = User::factory()->create();
|
||||
$adminRole = Role::firstOrCreate(['name' => 'admin']);
|
||||
$admin->assignRole($adminRole);
|
||||
$admin->givePermissionTo('users_access');
|
||||
|
||||
$user = User::factory()->create();
|
||||
$user->delete();
|
||||
|
||||
$indexResponse = $this->actingAs($admin)->get('/users/deleted');
|
||||
$indexResponse->assertOk();
|
||||
$indexResponse->assertSee($user->email);
|
||||
|
||||
$restoreResponse = $this->actingAs($admin)->post('/users/' . $user->id . '/restore');
|
||||
$restoreResponse->assertRedirect(route('users.deleted'));
|
||||
$this->assertNull($user->fresh()->deleted_at);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user