Usuwanie uzytkownika
This commit is contained in:
parent
a680be3fd3
commit
97adda56d3
@ -12,11 +12,11 @@ class DashboardController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index(): View
|
public function index(): View
|
||||||
{
|
{
|
||||||
$totalUsers = User::count();
|
$totalUsers = User::whereNull('deleted_at')->count();
|
||||||
$adminCount = User::role('admin')->count();
|
$adminCount = User::role('admin')->whereNull('deleted_at')->count();
|
||||||
$managerCount = User::role('kierownik')->count();
|
$managerCount = User::role('kierownik')->whereNull('deleted_at')->count();
|
||||||
$workerCount = User::role('pracownik')->count();
|
$workerCount = User::role('pracownik')->whereNull('deleted_at')->count();
|
||||||
$recentUsers = User::latest()->take(5)->get();
|
$recentUsers = User::whereNull('deleted_at')->latest()->take(5)->get();
|
||||||
|
|
||||||
return view('dashboard', compact(
|
return view('dashboard', compact(
|
||||||
'totalUsers',
|
'totalUsers',
|
||||||
|
|||||||
@ -58,7 +58,7 @@ class ProfileController extends Controller
|
|||||||
|
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
|
|
||||||
$user->delete();
|
$user->forceDelete();
|
||||||
|
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|||||||
@ -22,7 +22,7 @@ class UserController extends Controller
|
|||||||
$direction = $request->query('direction') === 'desc' ? 'desc' : 'asc';
|
$direction = $request->query('direction') === 'desc' ? 'desc' : 'asc';
|
||||||
$search = trim($request->query('search'));
|
$search = trim($request->query('search'));
|
||||||
|
|
||||||
$query = User::with('roles');
|
$query = User::with('roles')->whereNull('deleted_at');
|
||||||
|
|
||||||
// Wyszukiwanie w imieniu, nazwisku i adresie email
|
// Wyszukiwanie w imieniu, nazwisku i adresie email
|
||||||
if ($search) {
|
if ($search) {
|
||||||
@ -46,6 +46,35 @@ class UserController extends Controller
|
|||||||
return view('users.index', compact('users'));
|
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.
|
* Formularz dodawania nowego użytkownika.
|
||||||
* Wykorzystuje gotowy widok z Breeze (auth.register).
|
* Wykorzystuje gotowy widok z Breeze (auth.register).
|
||||||
@ -129,6 +158,14 @@ class UserController extends Controller
|
|||||||
|
|
||||||
$user->delete();
|
$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 Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Database\Factories\UserFactory;
|
use Database\Factories\UserFactory;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Spatie\Permission\Traits\HasRoles; // 1. Import traitu Spatie
|
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
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<UserFactory> */
|
/** @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.
|
* 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,11 +60,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Przyciski -->
|
<!-- Przyciski -->
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<x-primary-button>{{ __('Zapisz') }}</x-primary-button>
|
<div class="flex items-center gap-4">
|
||||||
<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">
|
<x-primary-button>{{ __('Zapisz') }}</x-primary-button>
|
||||||
{{ __('Anuluj') }}
|
<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">
|
||||||
</a>
|
{{ __('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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -6,13 +6,20 @@
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@hasanyrole('admin|kierownik')
|
@hasanyrole('admin|kierownik')
|
||||||
<a href="{{ route('users.create') }}"
|
<div class="flex items-center gap-3">
|
||||||
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">
|
<a href="{{ route('users.deleted') }}"
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
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">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
<span>{{ __('Użytkownicy usunięci') }}</span>
|
||||||
</svg>
|
</a>
|
||||||
<span>{{ __('Dodaj użytkownika') }}</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">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
|
</svg>
|
||||||
|
<span>{{ __('Dodaj użytkownika') }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
@endhasanyrole
|
@endhasanyrole
|
||||||
</div>
|
</div>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
@ -141,9 +148,21 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="py-4 px-4 align-middle" style="text-align: center !important;">
|
<td class="py-4 px-4 align-middle" style="text-align: center !important;">
|
||||||
<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">
|
<div class="flex items-center justify-center gap-3">
|
||||||
Edytuj
|
<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">
|
||||||
</a>
|
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>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -25,11 +25,13 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
// Zarządzanie użytkownikami (tylko dla posiadaczy uprawnienia users_access)
|
// Zarządzanie użytkownikami (tylko dla posiadaczy uprawnienia users_access)
|
||||||
Route::middleware(['can:users_access'])->group(function () {
|
Route::middleware(['can:users_access'])->group(function () {
|
||||||
Route::get('/users', [UserController::class, 'index'])->name('users.index');
|
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::get('/users/create', [UserController::class, 'create'])->name('users.create');
|
||||||
Route::post('/users', [UserController::class, 'store'])->name('users.store');
|
Route::post('/users', [UserController::class, 'store'])->name('users.store');
|
||||||
Route::get('/users/{user}/edit', [UserController::class, 'edit'])->name('users.edit');
|
Route::get('/users/{user}/edit', [UserController::class, 'edit'])->name('users.edit');
|
||||||
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
|
Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
|
||||||
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
|
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
|
||||||
|
Route::post('/users/{user}/restore', [UserController::class, 'restore'])->name('users.restore');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Moduł Raporty
|
// 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