invitationService = $invitationService; } /** * Display all invitations statistics and management page */ public function index(Request $request): void { $this->setAdminPrefs(); $meta_title = $title = 'Invitation Management'; // Get filter parameters $status = $request->get('status', ''); $invited_by = $request->get('invited_by', ''); $email = $request->get('email', ''); $orderBy = $request->get('ob', 'created_at_desc'); $page = $request->get('page', 1); // Build query $query = Invitation::with(['invitedBy', 'usedBy']); // Apply filters if ($status) { switch ($status) { case 'pending': $query->valid(); // Active, not expired, not used break; case 'used': $query->used(); // Has used_at timestamp break; case 'expired': $query->expired(); // expires_at is in the past break; case 'cancelled': $query->where('is_active', false) ->whereNull('used_at'); // Inactive but not used = cancelled break; } } if ($invited_by) { $user = User::where('username', 'like', '%'.$invited_by.'%')->first(); if ($user) { $query->where('invited_by', $user->id); } } if ($email) { $query->where('email', 'like', '%'.$email.'%'); } // Apply ordering switch ($orderBy) { case 'created_at_asc': $query->orderBy('created_at', 'asc'); break; case 'created_at_desc': $query->orderBy('created_at', 'desc'); break; case 'expires_at_asc': $query->orderBy('expires_at', 'asc'); break; case 'expires_at_desc': $query->orderBy('expires_at', 'desc'); break; case 'email_asc': $query->orderBy('email', 'asc'); break; case 'email_desc': $query->orderBy('email', 'desc'); break; default: $query->orderBy('created_at', 'desc'); } // Paginate results $invitations = $query->paginate(config('nntmux.items_per_page', 25)); // Get overall statistics $stats = $this->getOverallStats(); // Get user statistics (top inviters) $topInviters = $this->getTopInviters(); $this->smarty->assign([ 'invitations' => $invitations, 'stats' => $stats, 'topInviters' => $topInviters, 'status' => $status, 'invited_by' => $invited_by, 'email' => $email, 'orderBy' => $orderBy, 'statusOptions' => [ '' => 'All', 'pending' => 'Pending', 'used' => 'Used', 'expired' => 'Expired', 'cancelled' => 'Cancelled', ], ]); $content = $this->smarty->fetch('admin-invitation-list.tpl'); $this->smarty->assign(compact('title', 'meta_title', 'content')); $this->adminrender(); } /** * Get overall invitation statistics */ private function getOverallStats(): array { return [ 'total' => Invitation::count(), 'pending' => Invitation::valid()->count(), 'used' => Invitation::used()->count(), 'expired' => Invitation::expired()->count(), 'cancelled' => Invitation::where('is_active', false)->whereNull('used_at')->count(), 'today' => Invitation::whereDate('created_at', today())->count(), 'this_week' => Invitation::whereBetween('created_at', [ now()->startOfWeek(), now()->endOfWeek(), ])->count(), 'this_month' => Invitation::whereMonth('created_at', now()->month) ->whereYear('created_at', now()->year) ->count(), ]; } /** * Get top inviters statistics */ private function getTopInviters(int $limit = 10): array { return User::select('users.*') ->selectRaw('COUNT(invitations.id) as total_invitations') ->selectRaw('COUNT(CASE WHEN invitations.used_at IS NOT NULL THEN 1 END) as successful_invitations') ->leftJoin('invitations', 'users.id', '=', 'invitations.invited_by') ->groupBy('users.id') ->having('total_invitations', '>', 0) ->orderBy('total_invitations', 'desc') ->limit($limit) ->get() ->toArray(); } /** * Cancel an invitation */ public function cancel(Request $request): RedirectResponse { try { $id = $request->route('id'); $this->invitationService->cancelInvitation($id); return redirect()->back()->with('success', 'Invitation cancelled successfully'); } catch (\Exception $e) { return redirect()->back()->with('error', 'Failed to cancel invitation: '.$e->getMessage()); } } /** * Resend an invitation */ public function resend(Request $request): RedirectResponse { try { $id = $request->route('id'); $this->invitationService->resendInvitation($id); return redirect()->back()->with('success', 'Invitation resent successfully'); } catch (\Exception $e) { return redirect()->back()->with('error', 'Failed to resend invitation: '.$e->getMessage()); } } /** * Cleanup expired invitations */ public function cleanup(Request $request): RedirectResponse { try { $cleanedCount = $this->invitationService->cleanupExpiredInvitations(); return redirect()->back()->with('success', "Cleaned up {$cleanedCount} expired invitations"); } catch (\Exception $e) { return redirect()->back()->with('error', 'Failed to cleanup invitations: '.$e->getMessage()); } } /** * View detailed invitation */ public function show(Request $request): void { $this->setAdminPrefs(); $id = $request->route('id'); $invitation = Invitation::with(['invitedBy', 'usedBy'])->findOrFail($id); $meta_title = $title = 'Invitation Details'; $this->smarty->assign([ 'invitation' => $invitation, ]); $content = $this->smarty->fetch('admin-invitation-show.tpl'); $this->smarty->assign(compact('title', 'meta_title', 'content')); $this->adminrender(); } /** * Bulk actions on invitations */ public function bulkAction(Request $request): RedirectResponse { $action = $request->get('bulk_action'); $invitationIds = $request->get('invitation_ids', []); if (empty($invitationIds)) { return redirect()->back()->with('error', 'No invitations selected'); } try { $count = 0; switch ($action) { case 'cancel': foreach ($invitationIds as $id) { $this->invitationService->cancelInvitation($id); $count++; } return redirect()->back()->with('success', "Cancelled {$count} invitations"); case 'resend': foreach ($invitationIds as $id) { $this->invitationService->resendInvitation($id); $count++; } return redirect()->back()->with('success', "Resent {$count} invitations"); case 'delete': Invitation::whereIn('id', $invitationIds)->delete(); $count = count($invitationIds); return redirect()->back()->with('success', "Deleted {$count} invitations"); default: return redirect()->back()->with('error', 'Invalid bulk action'); } } catch (\Exception $e) { return redirect()->back()->with('error', 'Bulk action failed: '.$e->getMessage()); } } }