diff --git a/app/Http/Controllers/Admin/AdminPageController.php b/app/Http/Controllers/Admin/AdminPageController.php index 087332392..20db128f3 100644 --- a/app/Http/Controllers/Admin/AdminPageController.php +++ b/app/Http/Controllers/Admin/AdminPageController.php @@ -11,6 +11,7 @@ use App\Models\ReleaseReport; use App\Models\UsenetGroup; use App\Models\User; use App\Models\UserActivity; +use App\Services\RegistrationStatusService; use App\Services\SystemMetricsService; use App\Services\UserStatsService; use Illuminate\Http\JsonResponse; @@ -22,11 +23,18 @@ class AdminPageController extends BasePageController protected SystemMetricsService $systemMetricsService; - public function __construct(UserStatsService $userStatsService, SystemMetricsService $systemMetricsService) + protected RegistrationStatusService $registrationStatusService; + + public function __construct( + UserStatsService $userStatsService, + SystemMetricsService $systemMetricsService, + RegistrationStatusService $registrationStatusService + ) { parent::__construct(); $this->userStatsService = $userStatsService; $this->systemMetricsService = $systemMetricsService; + $this->registrationStatusService = $registrationStatusService; } /** @@ -35,6 +43,7 @@ class AdminPageController extends BasePageController public function index(): mixed { $this->setAdminPrefs(); + $now = now(); $userStats = Cache::remember('admin_user_stats', 120, function () { return [ @@ -48,12 +57,19 @@ class AdminPageController extends BasePageController ]; }); + $registrationStatus = $this->registrationStatusService->resolve($now); + $nextRegistrationPeriod = $registrationStatus['active_period'] === null + ? $this->registrationStatusService->getNextUpcomingPeriod($now) + : null; + return view('admin.dashboard', array_merge($this->viewData, [ 'meta_title' => 'Admin Home', 'meta_description' => 'Admin home page', 'userStats' => $userStats, 'stats' => $this->getDefaultStats(), 'systemMetrics' => $this->getSystemMetrics(), + 'registrationStatus' => $registrationStatus, + 'nextRegistrationPeriod' => $nextRegistrationPeriod, 'recent_activity' => $this->getRecentUserActivity(), ])); } diff --git a/app/Services/RegistrationStatusService.php b/app/Services/RegistrationStatusService.php index 554114d21..78642f70a 100644 --- a/app/Services/RegistrationStatusService.php +++ b/app/Services/RegistrationStatusService.php @@ -51,6 +51,17 @@ class RegistrationStatusService ->first(); } + public function getNextUpcomingPeriod(?CarbonInterface $at = null): ?RegistrationPeriod + { + $at ??= now(); + + return RegistrationPeriod::query() + ->enabled() + ->upcoming($at) + ->orderBy('starts_at') + ->first(); + } + /** * @return array{ * manual_status: int, diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index 0c333bfc0..cbacd9e6d 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -3,6 +3,13 @@ @section('content') @php $dashboardLastRefreshedAt = now()->format('H:i:s'); + $dashboardStatusBadgeClasses = static function (int $status): string { + return match ($status) { + \App\Models\Settings::REGISTER_STATUS_OPEN => 'border border-emerald-500/30 bg-emerald-600 text-white shadow-sm dark:border-emerald-300/20 dark:bg-emerald-500 dark:text-slate-950', + \App\Models\Settings::REGISTER_STATUS_INVITE => 'border border-amber-500/30 bg-amber-500 text-slate-950 shadow-sm dark:border-amber-200/20 dark:bg-amber-400 dark:text-slate-950', + default => 'border border-rose-500/30 bg-rose-600 text-white shadow-sm dark:border-rose-200/20 dark:bg-rose-500 dark:text-white', + }; + }; @endphp
+ +
+
+
+

+ + Site Registration Status +

+

+ Live registration availability, manual baseline, and the current schedule summary. +

+
+ + Manage Registrations + +
+ +
+
+

Effective Status

+
+ + {{ $registrationStatus['effective_status_label'] }} + + @if($registrationStatus['scheduled_override_active']) + + Scheduled Override + + @endif +
+

{{ $registrationStatus['message'] }}

+
+ +
+

Manual Baseline

+
+ + {{ $registrationStatus['manual_status_label'] }} + +
+

+ Used whenever no scheduled open period is currently active. +

+
+ +
+ @if($registrationStatus['active_period']) +

Active Open Period

+
+

{{ $registrationStatus['active_period']->name }}

+

+ {{ $registrationStatus['active_period']->starts_at->format('Y-m-d H:i') }} to + {{ $registrationStatus['active_period']->ends_at->format('Y-m-d H:i') }} +

+
+

This window is active on the public registration form right now.

+ @elseif($nextRegistrationPeriod) +

Next Scheduled Open Period

+
+

{{ $nextRegistrationPeriod->name }}

+

+ {{ $nextRegistrationPeriod->starts_at->format('Y-m-d H:i') }} to + {{ $nextRegistrationPeriod->ends_at->format('Y-m-d H:i') }} +

+
+

The next temporary public signup window is already scheduled.

+ @else +

Scheduled Open Periods

+

+ No active or upcoming open-registration periods are currently scheduled. +

+ @endif +
+
+
+ @if(isset($userStats))
diff --git a/tests/Feature/AdminDashboardRegistrationStatusWidgetTest.php b/tests/Feature/AdminDashboardRegistrationStatusWidgetTest.php new file mode 100644 index 000000000..a9d7da339 --- /dev/null +++ b/tests/Feature/AdminDashboardRegistrationStatusWidgetTest.php @@ -0,0 +1,38 @@ +assertFileExists($bladePath); + + $content = file_get_contents($bladePath); + + $this->assertStringContainsString('Site Registration Status', $content); + $this->assertStringContainsString("route('admin.registrations.index')", $content); + $this->assertStringContainsString("registrationStatus['effective_status_label']", $content); + $this->assertStringContainsString("registrationStatus['manual_status_label']", $content); + $this->assertStringContainsString('$nextRegistrationPeriod', $content); + $this->assertStringContainsString('Manage Registrations', $content); + } + + public function test_dashboard_controller_provides_registration_widget_data(): void + { + $controllerPath = app_path('Http/Controllers/Admin/AdminPageController.php'); + + $this->assertFileExists($controllerPath); + + $content = file_get_contents($controllerPath); + + $this->assertStringContainsString('RegistrationStatusService', $content); + $this->assertStringContainsString("'registrationStatus' => \$registrationStatus", $content); + $this->assertStringContainsString("'nextRegistrationPeriod' => \$nextRegistrationPeriod", $content); + $this->assertStringContainsString('getNextUpcomingPeriod', $content); + } +}