From 97aaa3f665683f6b94d95a9ef7c5562cf97b1ba6 Mon Sep 17 00:00:00 2001 From: DariusIII Date: Fri, 22 May 2026 15:12:17 +0200 Subject: [PATCH] Add payment widget to dashboard --- .../Controllers/Admin/AdminPageController.php | 2 + app/Http/Controllers/BtcPaymentController.php | 4 ++ .../AdminDashboardSnapshotService.php | 45 +++++++++++++++++++ .../js/alpine/components/admin/dashboard.js | 37 +++++++++++++++ resources/views/admin/dashboard.blade.php | 35 +++++++++++++++ 5 files changed, 123 insertions(+) diff --git a/app/Http/Controllers/Admin/AdminPageController.php b/app/Http/Controllers/Admin/AdminPageController.php index abc01c9af..0d1d50fb6 100644 --- a/app/Http/Controllers/Admin/AdminPageController.php +++ b/app/Http/Controllers/Admin/AdminPageController.php @@ -56,6 +56,7 @@ class AdminPageController extends BasePageController 'stats' => $payload['stats'], 'registrationStatus' => $payload['registrationStatus'], 'nextRegistrationPeriod' => $payload['nextRegistrationPeriod'], + 'hasRecentPayments' => ! empty($payload['recent_payments']), ])); } @@ -133,6 +134,7 @@ class AdminPageController extends BasePageController 'userStats' => $payload['userStats'], 'systemMetrics' => $payload['systemMetrics'], 'recent_activity' => $recentActivity, + 'recent_payments' => $payload['recent_payments'] ?? [], 'serviceStatuses' => $serviceStatuses, 'activeIncidents' => $activeIncidents, ]); diff --git a/app/Http/Controllers/BtcPaymentController.php b/app/Http/Controllers/BtcPaymentController.php index 24bbe94f6..584ead5a0 100644 --- a/app/Http/Controllers/BtcPaymentController.php +++ b/app/Http/Controllers/BtcPaymentController.php @@ -6,8 +6,10 @@ namespace App\Http\Controllers; use App\Models\Payment; use App\Models\User; +use App\Services\AdminDashboardSnapshotService; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; class BtcPaymentController extends BasePageController @@ -49,6 +51,8 @@ class BtcPaymentController extends BasePageController 'invoice_id' => $payload['invoiceId'], ]); + Cache::forget(AdminDashboardSnapshotService::CACHE_KEY); + return response('OK', 200); } diff --git a/app/Services/AdminDashboardSnapshotService.php b/app/Services/AdminDashboardSnapshotService.php index 94601fb4e..22b61f0b9 100644 --- a/app/Services/AdminDashboardSnapshotService.php +++ b/app/Services/AdminDashboardSnapshotService.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Services; +use App\Models\Payment; use App\Models\Release; use App\Models\ReleaseReport; use App\Models\UsenetGroup; @@ -83,6 +84,7 @@ class AdminDashboardSnapshotService 'userStats' => $this->buildUserStats(), 'systemMetrics' => $this->buildSystemMetrics(), 'recent_activity' => $this->buildRecentActivity(), + 'recent_payments' => $this->buildRecentPayments(), 'serviceStatuses' => $this->siteStatusService->getEnabledServices(), 'activeIncidents' => $this->siteStatusService->getActiveIncidents(), 'registrationStatus' => $registrationStatus, @@ -204,4 +206,47 @@ class AdminDashboardSnapshotService ]; })->all(); } + + /** + * Build the "Last 5 payments" widget payload (most recent records by id desc). + * + * @return array> + */ + private function buildRecentPayments(): array + { + return Payment::query() + ->orderByDesc('id') + ->limit(5) + ->get([ + 'id', + 'created_at', + 'username', + 'email', + 'item_description', + 'invoice_amount', + 'payment_method', + 'payment_status', + 'invoice_status', + 'order_id', + ]) + ->map(static function (Payment $payment): array { + $createdAt = $payment->created_at; + + return [ + 'id' => (int) $payment->id, + 'username' => (string) $payment->username, + 'email' => (string) $payment->email, + 'item_description' => (string) $payment->item_description, + 'invoice_amount' => (string) $payment->invoice_amount, + 'payment_method' => (string) $payment->payment_method, + 'payment_status' => (string) $payment->payment_status, + 'invoice_status' => (string) ($payment->invoice_status ?? Payment::INVOICE_STATUS_PENDING), + 'order_id' => (string) $payment->order_id, + 'created_at' => $createdAt?->toIso8601String(), + 'created_at_human' => $createdAt?->diffForHumans(), + 'created_at_formatted' => $createdAt?->timezone(config('app.timezone'))->format('Y-m-d H:i'), + ]; + }) + ->all(); + } } diff --git a/resources/js/alpine/components/admin/dashboard.js b/resources/js/alpine/components/admin/dashboard.js index 334bb4b2e..3351039af 100644 --- a/resources/js/alpine/components/admin/dashboard.js +++ b/resources/js/alpine/components/admin/dashboard.js @@ -83,6 +83,7 @@ Alpine.data('adminDashboard', () => ({ this._renderUserStats(payload.userStats); this._renderSystemMetrics(payload.systemMetrics); this._renderRecentActivity(payload.recent_activity); + this._renderRecentPayments(payload.recent_payments); this._renderSiteStatus(payload.serviceStatuses, payload.activeIncidents); this._renderSystemServices(payload.serviceStatuses); this._lastRefreshAt = Date.now(); @@ -279,6 +280,42 @@ Alpine.data('adminDashboard', () => ({ `; }).join(''); }, + _renderRecentPayments(payments) { + const hasPayments = Array.isArray(payments) && payments.length > 0; + const existing = this.$el.querySelector('[data-widget="recent-payments"]'); + if (!hasPayments) { + if (existing) existing.classList.add('hidden'); + return; + } + if (existing) existing.classList.remove('hidden'); + const widget = this._swapWidget('recent-payments'); + if (!widget) return; + const tbody = widget.querySelector('[data-payments-tbody]'); + if (!tbody) return; + const settledClasses = 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200'; + const pendingClasses = 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200'; + const otherClasses = 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200'; + tbody.innerHTML = payments.map(p => { + const status = String(p.payment_status ?? ''); + const isSettled = status.toLowerCase() === 'settled'; + const isPending = status === '' || status.toLowerCase() === 'pending'; + const badge = isSettled ? settledClasses : (isPending ? pendingClasses : otherClasses); + const statusLabel = status || 'Pending'; + const dateText = p.created_at_formatted ?? p.created_at_human ?? ''; + const item = p.item_description ?? ''; + const itemTrunc = item.length > 40 ? item.slice(0, 40) + '…' : item; + return ` + + ${escapeHtml(dateText)} + ${escapeHtml(p.username ?? '')} + ${escapeHtml(itemTrunc)} + ${escapeHtml(p.invoice_amount ?? '')} + + ${escapeHtml(statusLabel)} + + `; + }).join(''); + }, _renderSiteStatus(services, incidents) { const widget = this._swapWidget('site-status'); if (!widget) return; diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 627823d5f..a6e85dd4f 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -474,6 +474,41 @@ + + @if(! empty($hasRecentPayments)) +
+

+ + + Last 5 Payments + + View all → +

+
+ @for($i = 0; $i < 5; $i++) +
+ @endfor +
+ +
+ @endif +

Quick Links