From edbdb6410204cb1eef9f3e937ceb4afb96634b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Pocrnji=C4=8D?= Date: Tue, 18 Nov 2025 21:46:22 +0100 Subject: [PATCH] Updated client contract table and notification table, multiselect --- .../ActivityNotificationController.php | 40 ++- .../Controllers/Admin/UserRoleController.php | 14 +- app/Http/Controllers/ClientController.php | 11 +- app/Http/Controllers/ContractController.php | 69 ++++ app/Http/Middleware/EnsureUserIsActive.php | 42 +++ app/Models/User.php | 2 + app/Providers/FortifyServiceProvider.php | 19 ++ bootstrap/app.php | 1 + ...83124_add_active_column_to_users_table.php | 28 ++ .../Components/DataTable/DataTableServer.vue | 36 +- resources/js/Pages/Admin/Users/Index.vue | 27 +- resources/js/Pages/Client/Contracts.vue | 323 +++++++++++++++--- resources/js/Pages/Notifications/Unread.vue | 162 +++++++-- routes/web.php | 9 +- 14 files changed, 672 insertions(+), 111 deletions(-) create mode 100644 app/Http/Middleware/EnsureUserIsActive.php create mode 100644 database/migrations/2025_11_10_183124_add_active_column_to_users_table.php diff --git a/app/Http/Controllers/ActivityNotificationController.php b/app/Http/Controllers/ActivityNotificationController.php index b53ce24..d1b457e 100644 --- a/app/Http/Controllers/ActivityNotificationController.php +++ b/app/Http/Controllers/ActivityNotificationController.php @@ -13,8 +13,10 @@ class ActivityNotificationController extends Controller */ public function __invoke(Request $request) { - $request->validate([ - 'activity_id' => ['required', 'integer', 'exists:activities,id'], + $data = $request->validate([ + 'activity_id' => ['sometimes', 'integer', 'exists:activities,id'], + 'activity_ids' => ['sometimes', 'array', 'min:1'], + 'activity_ids.*' => ['integer', 'exists:activities,id'], ]); $userId = optional($request->user())->id; @@ -22,19 +24,29 @@ public function __invoke(Request $request) abort(403); } - $activity = Activity::query()->select(['id', 'due_date'])->findOrFail($request->integer('activity_id')); - $due = optional($activity->due_date) ? date('Y-m-d', strtotime($activity->due_date)) : now()->toDateString(); + $ids = []; + if (!empty($data['activity_id'])) { + $ids[] = $data['activity_id']; + } + if (!empty($data['activity_ids'])) { + $ids = array_merge($ids, $data['activity_ids']); + } + $ids = array_unique($ids); - ActivityNotificationRead::query()->updateOrCreate( - [ - 'user_id' => $userId, - 'activity_id' => $activity->id, - 'due_date' => $due, - ], - [ - 'read_at' => now(), - ] - ); + $activities = Activity::query()->select(['id', 'due_date'])->whereIn('id', $ids)->get(); + foreach ($activities as $activity) { + $due = optional($activity->due_date) ? date('Y-m-d', strtotime($activity->due_date)) : now()->toDateString(); + ActivityNotificationRead::query()->updateOrCreate( + [ + 'user_id' => $userId, + 'activity_id' => $activity->id, + 'due_date' => $due, + ], + [ + 'read_at' => now(), + ] + ); + } return back(); } diff --git a/app/Http/Controllers/Admin/UserRoleController.php b/app/Http/Controllers/Admin/UserRoleController.php index 64b2214..45c1fb0 100644 --- a/app/Http/Controllers/Admin/UserRoleController.php +++ b/app/Http/Controllers/Admin/UserRoleController.php @@ -20,7 +20,7 @@ public function index(Request $request): Response { Gate::authorize('manage-settings'); - $users = User::with('roles:id,slug,name')->orderBy('name')->get(['id', 'name', 'email']); + $users = User::with('roles:id,slug,name')->orderBy('name')->get(['id', 'name', 'email', 'active']); $roles = Role::with('permissions:id,slug,name')->orderBy('name')->get(['id', 'name', 'slug']); $permissions = Permission::orderBy('slug')->get(['id', 'name', 'slug']); @@ -61,4 +61,16 @@ public function update(Request $request, User $user): RedirectResponse return back()->with('success', 'Roles updated'); } + + public function toggleActive(User $user): RedirectResponse + { + Gate::authorize('manage-settings'); + + $user->active = ! $user->active; + $user->save(); + + $status = $user->active ? 'aktiviran' : 'deaktiviran'; + + return back()->with('success', "Uporabnik {$status}"); + } } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 0db4353..2e5a528 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -118,7 +118,8 @@ public function contracts(Client $client, Request $request) $from = $request->input('from'); $to = $request->input('to'); $search = $request->input('search'); - $segmentId = $request->input('segment'); + $segmentsParam = $request->input('segments'); + $segmentIds = $segmentsParam ? array_filter(explode(',', $segmentsParam)) : []; $contractsQuery = \App\Models\Contract::query() ->whereHas('clientCase', function ($q) use ($client) { @@ -150,9 +151,9 @@ public function contracts(Client $client, Request $request) }); }); }) - ->when($segmentId, function ($q) use ($segmentId) { - $q->whereHas('segments', function ($s) use ($segmentId) { - $s->where('segments.id', $segmentId) + ->when($segmentIds, function ($q) use ($segmentIds) { + $q->whereHas('segments', function ($s) use ($segmentIds) { + $s->whereIn('segments.id', $segmentIds) ->where('contract_segment.active', true); }); }) @@ -168,7 +169,7 @@ public function contracts(Client $client, Request $request) return Inertia::render('Client/Contracts', [ 'client' => $data, 'contracts' => $contractsQuery->paginate($request->integer('perPage', 20))->withQueryString(), - 'filters' => $request->only(['from', 'to', 'search', 'segment']), + 'filters' => $request->only(['from', 'to', 'search', 'segments']), 'segments' => $segments, 'types' => $types, ]); diff --git a/app/Http/Controllers/ContractController.php b/app/Http/Controllers/ContractController.php index 44b933a..160b03d 100644 --- a/app/Http/Controllers/ContractController.php +++ b/app/Http/Controllers/ContractController.php @@ -4,6 +4,8 @@ use App\Models\Contract; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Illuminate\Validation\Rule; use Inertia\Inertia; class ContractController extends Controller @@ -58,4 +60,71 @@ public function update(Contract $contract, Request $request) ]); } + + public function segment(Request $request) + { + $data = $request->validate([ + 'segment_id' => ['required', 'integer', Rule::exists('segments', 'id')->where('active', true)], + 'contracts' => ['required', 'array', 'min:1'], + 'contracts.*' => ['string', Rule::exists('contracts', 'uuid')], + ]); + + $segmentId = (int) $data['segment_id']; + $uuids = array_values($data['contracts']); + + $contracts = Contract::query() + ->whereIn('uuid', $uuids) + ->get(['id', 'client_case_id']); + + DB::transaction(function () use ($contracts, $segmentId) { + foreach ($contracts as $contract) { + // Ensure the segment is attached to the client case and active + $attached = DB::table('client_case_segment') + ->where('client_case_id', $contract->client_case_id) + ->where('segment_id', $segmentId) + ->first(); + + if (! $attached) { + DB::table('client_case_segment')->insert([ + 'client_case_id' => $contract->client_case_id, + 'segment_id' => $segmentId, + 'active' => true, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } elseif (! $attached->active) { + DB::table('client_case_segment') + ->where('id', $attached->id) + ->update(['active' => true, 'updated_at' => now()]); + } + + // Deactivate all current contract segments + DB::table('contract_segment') + ->where('contract_id', $contract->id) + ->update(['active' => false, 'updated_at' => now()]); + + // Activate or attach the target segment + $pivot = DB::table('contract_segment') + ->where('contract_id', $contract->id) + ->where('segment_id', $segmentId) + ->first(); + + if ($pivot) { + DB::table('contract_segment') + ->where('id', $pivot->id) + ->update(['active' => true, 'updated_at' => now()]); + } else { + DB::table('contract_segment')->insert([ + 'contract_id' => $contract->id, + 'segment_id' => $segmentId, + 'active' => true, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + }); + + return back()->with('success', __('Pogodbe so bile preusmerjene v izbrani segment.')); + } } diff --git a/app/Http/Middleware/EnsureUserIsActive.php b/app/Http/Middleware/EnsureUserIsActive.php new file mode 100644 index 0000000..a63c6e0 --- /dev/null +++ b/app/Http/Middleware/EnsureUserIsActive.php @@ -0,0 +1,42 @@ +active) { + // Revoke all tokens for Sanctum + if (method_exists($user, 'tokens')) { + $user->tokens()->delete(); + } + + // Logout from web guard + Auth::guard('web')->logout(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + if ($request->expectsJson()) { + return response()->json(['message' => 'Vaš račun je bil onemogočen.'], 403); + } + + return redirect()->route('login')->with('error', 'Vaš račun je bil onemogočen.'); + } + + return $next($request); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index b5c9247..2fda698 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -30,6 +30,7 @@ class User extends Authenticatable 'name', 'email', 'password', + 'active', ]; /** @@ -63,6 +64,7 @@ protected function casts(): array return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'active' => 'boolean', ]; } diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 2d741e3..e742949 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -6,11 +6,14 @@ use App\Actions\Fortify\ResetUserPassword; use App\Actions\Fortify\UpdateUserPassword; use App\Actions\Fortify\UpdateUserProfileInformation; +use App\Models\User; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; +use Illuminate\Validation\ValidationException; use Laravel\Fortify\Fortify; class FortifyServiceProvider extends ServiceProvider @@ -33,6 +36,22 @@ public function boot(): void Fortify::updateUserPasswordsUsing(UpdateUserPassword::class); Fortify::resetUserPasswordsUsing(ResetUserPassword::class); + Fortify::authenticateUsing(function (Request $request) { + $user = User::where('email', $request->email)->first(); + + if ($user && Hash::check($request->password, $user->password)) { + if (! $user->active) { + throw ValidationException::withMessages([ + Fortify::username() => ['Uporabnik je onemogočen.'], + ]); + } + + return $user; + } + + return null; + }); + RateLimiter::for('login', function (Request $request) { $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip()); diff --git a/bootstrap/app.php b/bootstrap/app.php index 9e3b6ad..b5fe181 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -15,6 +15,7 @@ $middleware->web(append: [ \App\Http\Middleware\HandleInertiaRequests::class, \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, + \App\Http\Middleware\EnsureUserIsActive::class, ]); $middleware->alias([ diff --git a/database/migrations/2025_11_10_183124_add_active_column_to_users_table.php b/database/migrations/2025_11_10_183124_add_active_column_to_users_table.php new file mode 100644 index 0000000..83bce9e --- /dev/null +++ b/database/migrations/2025_11_10_183124_add_active_column_to_users_table.php @@ -0,0 +1,28 @@ +boolean('active')->default(true)->after('email'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('active'); + }); + } +}; diff --git a/resources/js/Components/DataTable/DataTableServer.vue b/resources/js/Components/DataTable/DataTableServer.vue index b2b3fa4..e9198ad 100644 --- a/resources/js/Components/DataTable/DataTableServer.vue +++ b/resources/js/Components/DataTable/DataTableServer.vue @@ -171,7 +171,7 @@ function goToPageInput() { +   diff --git a/resources/js/Pages/Admin/Users/Index.vue b/resources/js/Pages/Admin/Users/Index.vue index ef4adad..e8da78a 100644 --- a/resources/js/Pages/Admin/Users/Index.vue +++ b/resources/js/Pages/Admin/Users/Index.vue @@ -1,6 +1,6 @@