From 6e50b3db3f2418231f09497bab8fb7c0dc49bef9 Mon Sep 17 00:00:00 2001 From: Marcel Menk Date: Sat, 9 Aug 2025 21:50:37 +0200 Subject: [PATCH 1/3] feat: notification indicators --- app/Helpers/SupportRun.php | 10 ++- .../Controllers/AdminSupportController.php | 8 +- .../Controllers/CustomerSupportController.php | 2 +- app/Http/Kernel.php | 4 + .../Middleware/IdentifyAdminProductLists.php | 2 +- app/Http/Middleware/IdentifyAdminStatus.php | 70 +++++++++++++++++ .../IdentifyCustomerProductLists.php | 2 +- .../Middleware/IdentifyCustomerStatus.php | 70 +++++++++++++++++ composer.lock | 12 +-- ...025_08_09_000001_update_support_tables.php | 75 +++++++++++++++++++ routes/web.php | 4 + 11 files changed, 246 insertions(+), 13 deletions(-) create mode 100644 app/Http/Middleware/IdentifyAdminStatus.php create mode 100644 app/Http/Middleware/IdentifyCustomerStatus.php create mode 100644 database/migrations/2025_08_09_000001_update_support_tables.php diff --git a/app/Helpers/SupportRun.php b/app/Helpers/SupportRun.php index d3a2295..e02cc53 100644 --- a/app/Helpers/SupportRun.php +++ b/app/Helpers/SupportRun.php @@ -35,7 +35,10 @@ public static function nextTicket(?int $category_id): ?SupportTicket ! empty($category = SupportCategory::byId($category_id)) && $category->assignments->where('user_id', '=', Auth::id()) ) { - return SupportTicket::where('category_id', '=', $category_id) + return SupportTicket::where(function (Builder $builder) { + return $builder->where('category_id', '=', 0) + ->orWhereNull('category_id'); + }) ->where('status', '=', 'open') ->whereDoesntHave('history', function (Builder $builder) { return $builder->where('user_id', '=', Auth::id()) @@ -58,7 +61,10 @@ public static function nextTicket(?int $category_id): ?SupportTicket ->orderBy('created_at') ->first(); } elseif ($category_id === 0) { - return SupportTicket::where('category_id', '=', 0) + return SupportTicket::where(function (Builder $builder) { + return $builder->where('category_id', '=', 0) + ->orWhereNull('category_id'); + }) ->where('status', '=', 'open') ->whereDoesntHave('history', function (Builder $builder) { return $builder->where('user_id', '=', Auth::id()) diff --git a/app/Http/Controllers/AdminSupportController.php b/app/Http/Controllers/AdminSupportController.php index 4428885..cae4380 100644 --- a/app/Http/Controllers/AdminSupportController.php +++ b/app/Http/Controllers/AdminSupportController.php @@ -71,7 +71,11 @@ public function support_index_list(Request $request): JsonResponse }) ->orWhere('category_id', '=', 0) ->orWhereNull('category_id'); - })->where('category_id', '=', $request->category); + }); + + if ($request->category) { + $query = $query->where('category_id', '=', $request->category); + } switch ($request->type) { case 'open': @@ -678,7 +682,7 @@ public function support_move(Request $request): RedirectResponse { Validator::make($request->toArray(), [ 'ticket_id' => ['required', 'integer'], - 'category_id' => ['required', 'integer'], + 'category_id' => ['nullable', 'integer'], ])->validate(); if ( diff --git a/app/Http/Controllers/CustomerSupportController.php b/app/Http/Controllers/CustomerSupportController.php index b1b2a6c..c2813a9 100644 --- a/app/Http/Controllers/CustomerSupportController.php +++ b/app/Http/Controllers/CustomerSupportController.php @@ -201,7 +201,7 @@ public function support_details(Request $request) public function support_create(Request $request): RedirectResponse { Validator::make($request->toArray(), [ - 'category_id' => ['required', 'integer'], + 'category_id' => ['nullable', 'integer'], 'subject' => ['required', 'string'], 'priority' => ['required', 'string'], 'message' => ['required', 'string'], diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index bc70acd..d64d643 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -7,7 +7,9 @@ use App\Http\Middleware\Authenticate; use App\Http\Middleware\EncryptCookies; use App\Http\Middleware\IdentifyAdminProductLists; +use App\Http\Middleware\IdentifyAdminStatus; use App\Http\Middleware\IdentifyCustomerProductLists; +use App\Http\Middleware\IdentifyCustomerStatus; use App\Http\Middleware\IdentifyTenant; use App\Http\Middleware\InjectNavigateables; use App\Http\Middleware\InjectShopCategoryOrProduct; @@ -112,5 +114,7 @@ class Kernel extends HttpKernel 'shop.categoryOrProduct' => InjectShopCategoryOrProduct::class, 'products.admin' => IdentifyAdminProductLists::class, 'products.customer' => IdentifyCustomerProductLists::class, + 'status.admin' => IdentifyAdminStatus::class, + 'status.customer' => IdentifyCustomerStatus::class, ]; } diff --git a/app/Http/Middleware/IdentifyAdminProductLists.php b/app/Http/Middleware/IdentifyAdminProductLists.php index 7a6cbec..a39aacd 100644 --- a/app/Http/Middleware/IdentifyAdminProductLists.php +++ b/app/Http/Middleware/IdentifyAdminProductLists.php @@ -29,7 +29,7 @@ public function handle(Request $request, Closure $next) { $results = []; - $products = Products::list()->reject(function ($handler) { + Products::list()->reject(function ($handler) { return !$handler->ui()->admin; })->each(function ($handler) use (&$results) { $results[] = (object) [ diff --git a/app/Http/Middleware/IdentifyAdminStatus.php b/app/Http/Middleware/IdentifyAdminStatus.php new file mode 100644 index 0000000..7fbcefd --- /dev/null +++ b/app/Http/Middleware/IdentifyAdminStatus.php @@ -0,0 +1,70 @@ + + */ +class IdentifyAdminStatus +{ + /** + * Handle an incoming request. + * + * @param Request $request + * @param Closure $next + * + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + $request->attributes->add([ + 'badges' => (object) [ + 'orders' => ShopOrderQueue::where('invalid', '=', false) + ->where('approved', '=', true) + ->where('disapproved', '=', false) + ->where('setup', '=', false) + ->where('deleted', '=', false) + ->where('fails', '<', 3) + ->count(), + 'tickets' => SupportTicket::where('status', '=', 'open') + ->where(function (Builder $builder) { + return $builder->where('category_id', '=', 0) + ->orWhereNull('category_id') + ->orWhereHas('category', function (Builder $builder) { + return $builder->whereHas('assignments', function (Builder $builder) { + return $builder->where('user_id', '=', Auth::id()); + }); + }); + }) + ->get() + ->filter(function (SupportTicket $ticket) { + $lastMessage = $ticket->messages->filter(function (SupportTicketMessage $message) { + return !$message->note; + })->last(); + + return !in_array($lastMessage->user?->role, [ + 'admin', + 'employee', + ]); + }) + ->count(), + ], + ]); + + return $next($request); + } +} diff --git a/app/Http/Middleware/IdentifyCustomerProductLists.php b/app/Http/Middleware/IdentifyCustomerProductLists.php index 4186f06..e7da4fa 100644 --- a/app/Http/Middleware/IdentifyCustomerProductLists.php +++ b/app/Http/Middleware/IdentifyCustomerProductLists.php @@ -29,7 +29,7 @@ public function handle(Request $request, Closure $next) { $results = []; - $products = Products::list()->reject(function ($handler) { + Products::list()->reject(function ($handler) { return !$handler->ui()->customer; })->each(function ($handler) use (&$results) { $results[] = (object) [ diff --git a/app/Http/Middleware/IdentifyCustomerStatus.php b/app/Http/Middleware/IdentifyCustomerStatus.php new file mode 100644 index 0000000..ea2d6e0 --- /dev/null +++ b/app/Http/Middleware/IdentifyCustomerStatus.php @@ -0,0 +1,70 @@ + + */ +class IdentifyCustomerStatus +{ + /** + * Handle an incoming request. + * + * @param Request $request + * @param Closure $next + * + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + $invoices = Invoice::where('user_id', '=', Auth::id()) + ->where('status', '=', 'unpaid') + ->whereDoesntHave('type', function (Builder $builder) { + return $builder->where('type', '=', 'prepaid'); + }) + ->get(); + + $request->attributes->add([ + 'badges' => (object) [ + 'invoices' => (object) [ + 'total' => (clone $invoices)->count(), + 'overdue' => (clone $invoices)->filter(function (Invoice $invoice) { + return $invoice->overdue; + })->count(), + ], + 'tickets' => SupportTicket::whereHas('assignments', function (Builder $builder) { + return $builder->where('user_id', '=', Auth::id()); + }) + ->where('status', '=', 'open') + ->get() + ->filter(function (SupportTicket $ticket) { + $lastMessage = $ticket->messages->filter(function (SupportTicketMessage $message) { + return !$message->note; + })->last(); + + return in_array($lastMessage->user?->role, [ + 'admin', + 'employee', + ]); + }) + ->count(), + ], + ]); + + return $next($request); + } +} diff --git a/composer.lock b/composer.lock index 3d200ca..4905b5a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "11227b59f860e2dd590368b44e4652b8", + "content-hash": "defde7552a959aa4b07010a9b742ee3b", "packages": [ { "name": "awobaz/compoships", @@ -2055,16 +2055,16 @@ }, { "name": "forepath/obms-theme-aurora", - "version": "v1.2.1", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/forepath/obms-theme-aurora.git", - "reference": "f1ff227a81818b7d71c495ce70838e8b03791aa0" + "reference": "0c1685acb5d3b97dbb3604a353c4341c08b1e1d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/forepath/obms-theme-aurora/zipball/f1ff227a81818b7d71c495ce70838e8b03791aa0", - "reference": "f1ff227a81818b7d71c495ce70838e8b03791aa0", + "url": "https://api.github.com/repos/forepath/obms-theme-aurora/zipball/0c1685acb5d3b97dbb3604a353c4341c08b1e1d1", + "reference": "0c1685acb5d3b97dbb3604a353c4341c08b1e1d1", "shasum": "" }, "require": { @@ -2094,7 +2094,7 @@ "issues": "https://github.com/forepath/obms-theme-aurora/issues", "source": "https://github.com/forepath/obms-theme-aurora" }, - "time": "2025-08-09T08:16:15+00:00" + "time": "2025-08-09T20:05:28+00:00" }, { "name": "fruitcake/php-cors", diff --git a/database/migrations/2025_08_09_000001_update_support_tables.php b/database/migrations/2025_08_09_000001_update_support_tables.php new file mode 100644 index 0000000..0977958 --- /dev/null +++ b/database/migrations/2025_08_09_000001_update_support_tables.php @@ -0,0 +1,75 @@ +foreignId('category_id')->nullable()->change(); + }); + + Schema::table('support_ticket_history', function (Blueprint $table) { + $table->enum('type', [ + 'message', + 'priority', + 'assignment', + 'category', + 'escalation', + 'deescalation', + 'lock', + 'unlock', + 'hold', + 'unhold', + 'close', + 'reopen', + 'file', + 'status', + 'run', + ])->change(); + }); + + Schema::table('support_runs', function (Blueprint $table) { + $table->foreignId('category_id')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('support_runs', function (Blueprint $table) { + $table->foreignId('category_id')->nullable(false)->change(); + }); + + Schema::table('support_ticket_history', function (Blueprint $table) { + $table->enum('type', [ + 'message', + 'priority', + 'assignment', + 'category', + 'escalation', + 'deescalation', + 'lock', + 'unlock', + 'hold', + 'unhold', + 'close', + 'reopen', + 'file', + ])->change(); + }); + + Schema::table('tenants', function (Blueprint $table) { + $table->foreignId('category_id')->nullable(false)->change(); + }); + } +}; diff --git a/routes/web.php b/routes/web.php index 7cbcac1..ecf73cf 100644 --- a/routes/web.php +++ b/routes/web.php @@ -67,6 +67,7 @@ 'middleware' => [ 'role.customer', 'products.customer', + 'status.customer', ], ], function () { Route::get('/accept', [App\Http\Controllers\CustomerDashboardController::class, 'accept'])->name('customer.accept'); @@ -179,6 +180,7 @@ 'middleware' => [ 'role.employee', 'products.admin', + 'status.admin', ], ], function () { Route::get('/home', [App\Http\Controllers\AdminDashboardController::class, 'index'])->name('admin.home'); @@ -526,6 +528,7 @@ Route::group([ 'middleware' => [ 'products.customer', + 'status.customer', ], ], function () { Page::query()->each(function (Page $page) { @@ -540,6 +543,7 @@ 'middleware' => [ 'shop.categoryOrProduct', 'products.customer', + 'status.customer', ], ], function () { Route::get('/shop', [App\Http\Controllers\CustomerShopController::class, 'render_category'])->name('public.shop'); From aeb2917dffb37edb0521e4dd35073b38d7f39f2d Mon Sep 17 00:00:00 2001 From: Marcel Menk Date: Sat, 9 Aug 2025 23:02:09 +0200 Subject: [PATCH 2/3] fix: support run next ticket selection --- app/Helpers/SupportRun.php | 54 ++++++++----------- .../Controllers/AdminSupportController.php | 4 +- composer.lock | 10 ++-- 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/app/Helpers/SupportRun.php b/app/Helpers/SupportRun.php index e02cc53..c869f9c 100644 --- a/app/Helpers/SupportRun.php +++ b/app/Helpers/SupportRun.php @@ -6,6 +6,7 @@ use App\Models\Support\Category\SupportCategory; use App\Models\Support\SupportTicket; +use App\Models\Support\SupportTicketMessage; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Auth; @@ -32,13 +33,10 @@ public static function nextTicket(?int $category_id): ?SupportTicket if (! empty($category_id)) { if ( $category_id > 0 && - ! empty($category = SupportCategory::byId($category_id)) && + ! empty($category = SupportCategory::where('id', '=', $category_id)->first()) && $category->assignments->where('user_id', '=', Auth::id()) ) { - return SupportTicket::where(function (Builder $builder) { - return $builder->where('category_id', '=', 0) - ->orWhereNull('category_id'); - }) + $tickets = SupportTicket::where('category_id', '=', $category->id) ->where('status', '=', 'open') ->whereDoesntHave('history', function (Builder $builder) { return $builder->where('user_id', '=', Auth::id()) @@ -49,19 +47,12 @@ public static function nextTicket(?int $category_id): ?SupportTicket ->whereDoesntHave('run', function (Builder $builder) { return $builder->whereNull('ended_at'); }) - ->whereHas('messages', function (Builder $builder) { - return $builder - ->latest() - ->whereHas('user', function (Builder $builder) { - return $builder->where('role', '=', 'customer'); - }); - }) ->orderByDesc('escalated') ->orderBy('hold') ->orderBy('created_at') - ->first(); + ->get(); } elseif ($category_id === 0) { - return SupportTicket::where(function (Builder $builder) { + $tickets = SupportTicket::where(function (Builder $builder) { return $builder->where('category_id', '=', 0) ->orWhereNull('category_id'); }) @@ -75,20 +66,13 @@ public static function nextTicket(?int $category_id): ?SupportTicket ->whereDoesntHave('run', function (Builder $builder) { return $builder->whereNull('ended_at'); }) - ->whereHas('messages', function (Builder $builder) { - return $builder - ->latest() - ->whereHas('user', function (Builder $builder) { - return $builder->where('role', '=', 'customer'); - }); - }) ->orderByDesc('escalated') ->orderBy('hold') ->orderBy('created_at') - ->first(); + ->get(); } } else { - return SupportTicket::where(function (Builder $builder) { + $tickets = SupportTicket::where(function (Builder $builder) { return $builder->where('category_id', '=', 0) ->orWhereNull('category_id') ->orWhereHas('category', function (Builder $builder) { @@ -107,19 +91,25 @@ public static function nextTicket(?int $category_id): ?SupportTicket ->whereDoesntHave('run', function (Builder $builder) { return $builder->whereNull('ended_at'); }) - ->whereHas('messages', function (Builder $builder) { - return $builder - ->latest() - ->whereHas('user', function (Builder $builder) { - return $builder->where('role', '=', 'customer'); - }); - }) ->orderByDesc('escalated') ->orderBy('hold') ->orderBy('created_at') - ->first(); + ->get(); } - return null; + if (empty($tickets)) { + return null; + } + + return $tickets->filter(function (SupportTicket $ticket) { + $lastMessage = $ticket->messages->filter(function (SupportTicketMessage $message) { + return !$message->note; + })->last(); + + return !in_array($lastMessage->user?->role, [ + 'admin', + 'employee', + ]); + })->first(); } } diff --git a/app/Http/Controllers/AdminSupportController.php b/app/Http/Controllers/AdminSupportController.php index cae4380..0b743a9 100644 --- a/app/Http/Controllers/AdminSupportController.php +++ b/app/Http/Controllers/AdminSupportController.php @@ -75,6 +75,8 @@ public function support_index_list(Request $request): JsonResponse if ($request->category) { $query = $query->where('category_id', '=', $request->category); + } else { + $query = $query->whereNull('category_id'); } switch ($request->type) { @@ -895,7 +897,7 @@ public function support_run_start(Request $request): RedirectResponse ->exists() ) { /* @var SupportTicket|null $ticket */ - if (! empty($ticket = SupportRunHelper::nextTicket($request->category ?? null))) { + if (! empty($ticket = SupportRunHelper::nextTicket($request->category ? (int) $request->category : null))) { /* @var SupportRun|null $run */ $run = SupportRun::create([ 'category_id' => $request->category ?? null, diff --git a/composer.lock b/composer.lock index 4905b5a..4bcfc0d 100644 --- a/composer.lock +++ b/composer.lock @@ -2055,16 +2055,16 @@ }, { "name": "forepath/obms-theme-aurora", - "version": "v1.3.2", + "version": "v1.3.3", "source": { "type": "git", "url": "https://github.com/forepath/obms-theme-aurora.git", - "reference": "0c1685acb5d3b97dbb3604a353c4341c08b1e1d1" + "reference": "df5bd322fcc432c159e3761b228a156fe0eda1e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/forepath/obms-theme-aurora/zipball/0c1685acb5d3b97dbb3604a353c4341c08b1e1d1", - "reference": "0c1685acb5d3b97dbb3604a353c4341c08b1e1d1", + "url": "https://api.github.com/repos/forepath/obms-theme-aurora/zipball/df5bd322fcc432c159e3761b228a156fe0eda1e0", + "reference": "df5bd322fcc432c159e3761b228a156fe0eda1e0", "shasum": "" }, "require": { @@ -2094,7 +2094,7 @@ "issues": "https://github.com/forepath/obms-theme-aurora/issues", "source": "https://github.com/forepath/obms-theme-aurora" }, - "time": "2025-08-09T20:05:28+00:00" + "time": "2025-08-09T20:15:56+00:00" }, { "name": "fruitcake/php-cors", From 634ba45604a3a4948a61ab21d712987f58597839 Mon Sep 17 00:00:00 2001 From: Marcel Menk Date: Sat, 9 Aug 2025 23:17:39 +0200 Subject: [PATCH 3/3] fix: table button width --- app/Http/Controllers/AdminSettingsController.php | 2 +- app/Http/Controllers/AdminSupportController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/AdminSettingsController.php b/app/Http/Controllers/AdminSettingsController.php index adeccce..ba5b779 100644 --- a/app/Http/Controllers/AdminSettingsController.php +++ b/app/Http/Controllers/AdminSettingsController.php @@ -82,7 +82,7 @@ public function settings_list(Request $request): JsonResponse ->get() ->transform(function (Setting $setting) { $edit = ' - +