Uprawnienia i moduły

This commit is contained in:
Tomasz Boruc 2026-08-22 23:31:07 +02:00
parent 843d404c18
commit d9f4db73c5
18 changed files with 559 additions and 69 deletions

View File

@ -10,8 +10,8 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Spatie\Permission\Models\Role;
class RegisteredUserController extends Controller
{
@ -20,28 +20,39 @@ class RegisteredUserController extends Controller
*/
public function create(): View
{
return view('auth.register');
$roles = Role::all();
return view('auth.register', compact('roles'));
}
/**
* Handle an incoming registration request.
*
* @throws ValidationException
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
// 1. Walidacja danych z formularza
$request->validate([
'name' => ['required', 'string', 'max:255'],
'surname' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
'role' => ['required', 'exists:roles,name'], // Walidacja wybrane pole musi istnieć w tabeli roles
]);
// 2. Utworzenie nowego użytkownika
$user = User::create([
'name' => $request->name,
'surname' => $request->surname,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
// 3. Przypisanie wybranej roli ze Spatie
$user->assignRole($request->role);
// 4. Wysłanie zdarzenia rejestracji i zalogowanie
event(new Registered($user));
Auth::login($user);

View File

@ -21,6 +21,9 @@ class ProfileController extends Controller
]);
}
/**
* Update the user's profile information.
*/
/**
* Update the user's profile information.
*/
@ -34,6 +37,11 @@ class ProfileController extends Controller
$request->user()->save();
// Jeśli zapisujący użytkownik to Admin i przesłano nową rolę - aktualizujemy ją
if ($request->user()->hasRole('admin') && $request->filled('role')) {
$request->user()->syncRoles($request->role);
}
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}

View File

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Spatie\Permission\Models\Role;
class UserController extends Controller
{
/**
* Wyświetla listę wszystkich użytkowników.
*/
public function index()
{
$users = User::with('roles')->paginate(10);
return view('users.index', compact('users'));
}
/**
* Formularz edycji wybranego użytkownika.
*/
public function edit(User $user)
{
$roles = Role::all();
return view('users.edit', compact('user', 'roles'));
}
/**
* Zapis zmian danych oraz ról użytkownika.
*/
public function update(Request $request, User $user)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'surname' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
'roles' => ['nullable', 'array'],
'roles.*' => ['string', 'exists:roles,name'],
]);
$user->update([
'name' => $validated['name'],
'surname' => $validated['surname'],
'email' => $validated['email'],
]);
// Synchronizuje przekazane role (jeśli nic nie zaznaczono, przekazuje pustą tablicę)
$user->syncRoles($request->input('roles', []));
return redirect()->route('users.index')->with('status', 'Użytkownik został zaktualizowany.');
}
/**
* Usuwanie użytkownika.
*/
public function destroy(User $user)
{
// Blokada usunięcia samego siebie
if (auth()->id() === $user->id) {
return redirect()->back()->with('error', 'Nie możesz usunąć własnego konta!');
}
$user->delete();
return redirect()->route('users.index')->with('status', 'Użytkownik został usunięty.');
}
}

View File

@ -2,19 +2,22 @@
namespace App\Http\Requests;
use App\Enums\UserRole;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'surname' => ['required', 'string', 'max:255'],
'role' => ['required', Rule::enum(UserRole::class)],
'surname' => ['required', 'string', 'max:255'], // Jeśli używasz nazwiska
'email' => [
'required',
'string',
@ -23,6 +26,7 @@ class ProfileUpdateRequest extends FormRequest
'max:255',
Rule::unique(User::class)->ignore($this->user()->id),
],
'role' => ['nullable', 'string', 'exists:roles,name'], // Dopuszczamy role
];
}
}

View File

@ -26,6 +26,7 @@ class UserFactory extends Factory
{
return [
'name' => fake()->name(),
'surname' => fake()->lastName(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),

View File

@ -2,10 +2,10 @@
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\PermissionRegistrar;
class DatabaseSeeder extends Seeder
{
@ -14,36 +14,43 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
// 1. Tworzenie ról (grupa)
$adminRole = Role::create(['name' => 'admin']);
$kierownikRole = Role::create(['name' => 'kierownik']);
$pracownikRole = Role::create(['name' => 'pracownik']);
// Resetujemy pamięć podręczną uprawnień Spatie
app()[PermissionRegistrar::class]->forgetCachedPermissions();
// 2. Tworzenie użytkowników
$admin = User::factory()->create([
'name' => 'Jan',
'surname' => 'Admin',
'email' => 'a@a.com',
'password' => Hash::make('123'),
// 1. Tworzenie uprawnień (Permissions)
$permissions = [
'raporty_access',
'flota_access',
'zaliczki_access',
'users_access',
];
foreach ($permissions as $permission) {
Permission::firstOrCreate(['name' => $permission]);
}
// 2. Tworzenie ról (Roles)
$adminRole = Role::firstOrCreate(['name' => 'admin']);
$kierownikRole = Role::firstOrCreate(['name' => 'kierownik']);
$pracownikRole = Role::firstOrCreate(['name' => 'pracownik']);
// 3. Przypisywanie uprawnień do ról
$adminRole->syncPermissions(Permission::all());
$kierownikRole->syncPermissions([
'raporty_access',
'flota_access',
'zaliczki_access',
]);
$kierownik = User::factory()->create([
'name' => 'Krzysztof',
'surname' => 'Kierownik',
'email' => 'k@k.com',
'password' => Hash::make('123'),
$pracownikRole->syncPermissions([
'raporty_access',
'zaliczki_access',
]);
$pracownik = User::factory()->create([
'name' => 'Piotr',
'surname' => 'Pracownik',
'email' => 'p@p.com',
'password' => Hash::make('123'),
// 4. Wywołanie seedera użytkowników
$this->call([
UserSeeder::class,
]);
// 3. Przypisanie ról
$admin->assignRole($adminRole);
$kierownik->assignRole($kierownikRole);
$pracownik->assignRole($pracownikRole);
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Role;
class UserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Pobieramy istniejące role
$adminRole = Role::where('name', 'admin')->first();
$kierownikRole = Role::where('name', 'kierownik')->first();
$pracownikRole = Role::where('name', 'pracownik')->first();
// 1. Domyślne konta testowe
$admin = User::factory()->create([
'name' => 'Jan',
'surname' => 'Admin',
'email' => 'a@a.com',
'password' => Hash::make('123'),
]);
$admin->assignRole($adminRole);
$kierownik = User::factory()->create([
'name' => 'Krzysztof',
'surname' => 'Kierownik',
'email' => 'k@k.com',
'password' => Hash::make('123'),
]);
$kierownik->assignRole($kierownikRole);
$pracownik = User::factory()->create([
'name' => 'Piotr',
'surname' => 'Pracownik',
'email' => 'p@p.com',
'password' => Hash::make('123'),
]);
$pracownik->assignRole($pracownikRole);
// 2. Generowanie dodatkowych 30 losowych użytkowników z rolami (password = 'password')
User::factory(30)->create()->each(function ($user) use ($pracownikRole, $kierownikRole) {
$role = rand(1, 100) <= 80 ? $pracownikRole : $kierownikRole;
$user->assignRole($role);
});
}
}

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -9,6 +9,13 @@
<x-input-error :messages="$errors->get('name')" class="mt-2" />
</div>
<!-- Surname -->
<div class="mt-4">
<x-input-label for="surname" :value="__('Nazwisko')" />
<x-text-input id="surname" class="block mt-1 w-full" type="text" name="surname" :value="old('surname')" required autocomplete="family-name" />
<x-input-error :messages="$errors->get('surname')" class="mt-2" />
</div>
<!-- Email Address -->
<div class="mt-4">
<x-input-label for="email" :value="__('Email')" />
@ -16,6 +23,22 @@
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<!-- Select Role (Dynamic) -->
<div class="mt-4">
<x-input-label for="role" :value="__('Rola')" />
<select id="role" name="role" class="block mt-1 w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm" required>
<option value="" disabled selected>-- Wybierz rolę --</option>
@foreach($roles as $role)
<option value="{{ $role->name }}" {{ old('role') === $role->name ? 'selected' : '' }}>
{{ ucfirst($role->name) }}
</option>
@endforeach
</select>
<x-input-error :messages="$errors->get('role')" class="mt-2" />
</div>
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />

View File

@ -1,3 +1,14 @@
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
</svg>
<div class="inline-flex flex-col items-end">
<div>
<img src="{{ asset('images/logo.png') }}"
alt="Logo"
style="filter: brightness(0) invert(1);"
{{ $attributes->merge(['class' => 'h-9 w-auto block']) }}>
</div>
<div class="w-full flex justify-end">
<span class="text-[9px] font-extrabold tracking-widest leading-none mt-1 text-white text-right">
OnLine
</span>
</div>
</div>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 470 B

View File

@ -0,0 +1,15 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Flota') }}
</h2>
</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 p-6 text-gray-900 dark:text-gray-100">
Tu będzie Flota
</div>
</div>
</div>
</x-app-layout>

View File

@ -10,11 +10,35 @@
</a>
</div>
<!-- Navigation Links -->
<!-- Navigation Links (Desktop) -->
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
@can('users_access')
<x-nav-link :href="route('users.index')" :active="request()->routeIs('users.*')">
{{ __('Użytkownicy') }}
</x-nav-link>
@endcan
@can('raporty_access')
<x-nav-link :href="route('raporty')" :active="request()->routeIs('raporty')">
{{ __('Raporty') }}
</x-nav-link>
@endcan
@can('flota_access')
<x-nav-link :href="route('flota')" :active="request()->routeIs('flota')">
{{ __('Flota') }}
</x-nav-link>
@endcan
@can('zaliczki_access')
<x-nav-link :href="route('zaliczki')" :active="request()->routeIs('zaliczki')">
{{ __('Zaliczki') }}
</x-nav-link>
@endcan
</div>
</div>
@ -64,12 +88,36 @@
</div>
</div>
<!-- Responsive Navigation Menu -->
<!-- Responsive Navigation Menu (Mobile) -->
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
<div class="pt-2 pb-3 space-y-1">
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
{{ __('Dashboard') }}
</x-responsive-nav-link>
@can('users_access')
<x-responsive-nav-link :href="route('users.index')" :active="request()->routeIs('users.*')">
{{ __('Użytkownicy') }}
</x-responsive-nav-link>
@endcan
@can('raporty_access')
<x-responsive-nav-link :href="route('raporty')" :active="request()->routeIs('raporty')">
{{ __('Raporty') }}
</x-responsive-nav-link>
@endcan
@can('flota_access')
<x-responsive-nav-link :href="route('flota')" :active="request()->routeIs('flota')">
{{ __('Flota') }}
</x-responsive-nav-link>
@endcan
@can('zaliczki_access')
<x-responsive-nav-link :href="route('zaliczki')" :active="request()->routeIs('zaliczki')">
{{ __('Zaliczki') }}
</x-responsive-nav-link>
@endcan
</div>
<!-- Responsive Settings Options -->

View File

@ -5,7 +5,7 @@
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __('Zaktualizuj swoje dane osobowe, rolę oraz adres e-mail.') }}
{{ __('Zaktualizuj swoje dane osobowe oraz adres e-mail.') }}
</p>
</header>
@ -31,7 +31,32 @@
<x-input-error class="mt-2" :messages="$errors->get('surname')" />
</div>
{{-- ROLA --}}
<div>
<x-input-label for="role" :value="__('Rola w systemie')" />
@role('admin')
{{-- Admin może edytować rolę --}}
<select id="role" name="role" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
@foreach(\Spatie\Permission\Models\Role::all() as $role)
<option value="{{ $role->name }}" {{ $user->hasRole($role->name) ? 'selected' : '' }}>
{{ ucfirst($role->name) }}
</option>
@endforeach
</select>
@else
{{-- Pozostali widziana rola tylko do odczytu --}}
<x-text-input
id="role"
type="text"
class="mt-1 block w-full bg-gray-100 dark:bg-gray-800 cursor-not-allowed text-gray-500"
:value="ucfirst($user->getRoleNames()->first() ?? 'Brak roli')"
disabled
/>
@endrole
<x-input-error class="mt-2" :messages="$errors->get('role')" />
</div>
{{-- EMAIL --}}
<div>

View File

@ -0,0 +1,15 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Raporty') }}
</h2>
</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 p-6 text-gray-900 dark:text-gray-100">
Tu będą Raporty
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,76 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Edycja użytkownika') }}: {{ $user->name }} {{ $user->surname }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
<div class="max-w-xl">
<section>
<header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __('Dane użytkownika') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
{{ __('Zaktualizuj dane osobowe oraz rolę przypisaną do konta.') }}
</p>
</header>
<form method="post" action="{{ route('users.update', $user) }}" class="mt-6 space-y-6">
@csrf
@method('put')
<!-- Imię -->
<div>
<x-input-label for="name" :value="__('Imię')" />
<x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus />
<x-input-error class="mt-2" :messages="$errors->get('name')" />
</div>
<!-- Nazwisko -->
<div>
<x-input-label for="surname" :value="__('Nazwisko')" />
<x-text-input id="surname" name="surname" type="text" class="mt-1 block w-full" :value="old('surname', $user->surname)" required />
<x-input-error class="mt-2" :messages="$errors->get('surname')" />
</div>
<!-- Email -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" name="email" type="email" class="mt-1 block w-full" :value="old('email', $user->email)" required />
<x-input-error class="mt-2" :messages="$errors->get('email')" />
</div>
<!-- Rola -->
<div>
<x-input-label for="role" :value="__('Rola')" />
<select id="role" name="roles[]" class="mt-1 block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm">
@foreach($roles as $role)
<option value="{{ $role->name }}" {{ $user->hasRole($role->name) ? 'selected' : '' }}>
{{ strtoupper($role->name) }}
</option>
@endforeach
</select>
<x-input-error class="mt-2" :messages="$errors->get('roles')" />
</div>
<!-- Przyciski -->
<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>
</form>
</section>
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,94 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-white leading-tight">
{{ __('Zarządzanie Użytkownikami') }}
</h2>
</x-slot>
<!-- Styl do wymuszenia powiększonych badge'y i efektu hover niezależnie od Vite/Tailwind -->
<style>
.custom-table-row:hover {
background-color: rgba(255, 255, 255, 0.05) !important;
}
.role-badge {
display: inline-block !important;
padding: 6px 20px !important;
min-width: 120px !important;
text-align: center !important;
border-radius: 6px !important;
}
</style>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg border border-gray-700/50">
<div class="p-6 text-white">
<div class="overflow-x-auto">
<table class="w-full border-collapse" style="table-layout: fixed;">
<thead>
<tr class="border-b border-gray-700 text-xs font-semibold uppercase tracking-wider 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: 25%;">Rola</th>
<th scope="col" class="py-3.5 px-4" style="text-align: center !important; width: 15%;">Akcje</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-700/60 text-sm">
@forelse($users as $user)
<tr class="custom-table-row transition-colors duration-150">
<!-- Imię i Nazwisko -->
<td class="py-4 px-4 font-medium text-white align-middle" style="text-align: center !important;">
{{ $user->name }} {{ $user->surname }}
</td>
<!-- Email (Wymuszone wycentrowanie) -->
<td class="py-4 px-4 text-gray-300 align-middle" style="text-align: center !important;">
{{ $user->email }}
</td>
<!-- Rola (Poszerzony badge z min-width 120px) -->
<td class="py-4 px-4 text-gray-300 align-middle" style="text-align: center !important;">
<div style="display: flex; justify-content: center; align-items: center; gap: 8px;">
@forelse($user->roles as $role)
<span class="role-badge text-xs font-semibold uppercase tracking-wider bg-gray-700 text-gray-100 border border-gray-600 shadow-sm">
{{ $role->name }}
</span>
@empty
<span class="text-gray-500 text-xs">brak</span>
@endforelse
</div>
</td>
<!-- Akcje -->
<td class="py-4 px-4 align-middle" style="text-align: center !important;">
<a href="{{ route('users.edit', $user) }}" class="text-indigo-400 hover:text-indigo-300 font-medium text-sm transition">
Edytuj
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="py-8 text-gray-400" style="text-align: center !important;">
Brak użytkowników do wyświetlenia.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($users->hasPages())
<div class="mt-6 pt-4 border-t border-gray-700">
{{ $users->links() }}
</div>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,15 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Zaliczki') }}
</h2>
</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 p-6 text-gray-900 dark:text-gray-100">
Tu będą Zaliczki
</div>
</div>
</div>
</x-app-layout>

View File

@ -1,35 +1,48 @@
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
// 1. Dostęp tylko dla Admina
Route::get('/one', function () {
return 'Strona dla ADMINA';
})->middleware(['auth', 'role:admin']);
// 2. Dostęp dla Kierownika ORAZ Admina (używamy separatora | dla warunku LUB)
Route::get('/second', function () {
return 'Strona dla KIEROWNIKA i ADMINA';
})->middleware(['auth', 'role:admin|kierownik']);
// 3. Dostęp dla wszystkich zalogowanych użytkowników (admin, kierownik, pracownik)
Route::get('/third', function () {
return 'Strona dla WSZYSTKICH zalogowanych';
})->middleware(['auth']);
Route::get('/', function () {
return view('welcome');
});
// Grupa tras chronionych autoryzacją
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
})->name('dashboard');
Route::middleware('auth')->group(function () {
// Profil użytkownika
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// 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/{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');
});
// Moduł Raporty
Route::middleware(['can:raporty_access'])->group(function () {
Route::view('/raporty', 'raporty')->name('raporty');
});
// Moduł Flota
Route::middleware(['can:flota_access'])->group(function () {
Route::view('/flota', 'flota')->name('flota');
});
// Moduł Zaliczki
Route::middleware(['can:zaliczki_access'])->group(function () {
Route::view('/zaliczki', 'zaliczki')->name('zaliczki');
});
});
require __DIR__.'/auth.php';