resetPage(); } public function updatedStatusFilter(): void { $this->resetPage(); } public function boot(TicketService $ticketService, UserAppAccessService $appAccessService): void { $this->ticketService = $ticketService; $this->appAccessService = $appAccessService; } public function mount(): void { // Pre-populate dynamic choice lists $this->searchApps(); $this->searchRoles(); $this->searchUsers(); $this->searchAssignableUsers(); // Select first role by default in notified roles array if empty $defaultRole = Role::where('name', 'Support')->first(); if ($defaultRole && empty($this->form->notifiedRoleIds)) { $this->form->notifiedRoleIds = [$defaultRole->id]; } // Auto-open drawer if requested from URL if ($this->raise) { $this->drawerOpen = true; if ($this->appId) { $this->form->connectedAppId = $this->appId; } } // Auto-open ticket if requested from URL if ($this->ticketParam && (int) $this->ticketParam > 0) { $this->showTicket((int) $this->ticketParam); } } /** * Re-hydrate searchable arrays before every render to prevent losing selections. */ public function rendering(): void { if (empty($this->appsSearchable)) { $this->searchApps(); } if (empty($this->rolesSearchable)) { $this->searchRoles(); } if (empty($this->usersSearchable)) { $this->searchUsers(); } if (empty($this->assignableUsersSearchable)) { $this->searchAssignableUsers($this->assignableUsersSearchTerm); } } /** * Compute visible tickets based on permissions. */ #[Computed] public function tickets(): PaginatedDataCollection { $user = auth()->user(); if (!$user) { return TicketListData::collect(collect(), PaginatedDataCollection::class); } $search = trim($this->search); $search = empty($search) ? null : $search; if ($user->hasRole('Admin') || $user->hasRole('admin') || $user->can(TicketPermissionEnum::Read->value)) { return $this->ticketService->getList($search, $this->statusFilter, 10); } return $this->ticketService->getListForUser($user->id, $search, $this->statusFilter, 10); } /** * Compute expiring apps for the current user. */ #[Computed] public function eligibleApps(): Collection { $user = auth()->user(); if (!$user) { return collect(); } return $this->appAccessService->getActiveServices($user) ->filter(fn($app) => $app->isWarning); } /** * Compute available ticket statuses for filtering. */ #[Computed] public function statuses(): Collection { return TicketStatus::query()->get(); } /** * Search connected apps for mary-choices select dropdown. */ public function searchApps(string $value = ''): void { $selectedId = $this->form->connectedAppId; $apps = $this->eligibleApps(); if ($selectedId && !$apps->contains('id', $selectedId)) { $selectedApp = ConnectedApp::find($selectedId); if ($selectedApp) { $apps->push($selectedApp); } } $this->appsSearchable = $apps ->when('' !== $value, fn($col) => $col->filter(fn($app) => str_contains(strtolower($app->name), strtolower($value)))) ->map(fn($app) => [ 'id' => $app->id, 'name' => $app->name.(isset($app->days_remaining) ? ' ('.$app->days_remaining.' days left)' : ''), ]) ->toArray(); } /** * Search roles for mary-choices select dropdown. */ public function searchRoles(string $value = ''): void { $selectedIds = $this->form->notifiedRoleIds; $this->rolesSearchable = Role::query() ->where(function ($query) use ($value, $selectedIds): void { if ('' !== $value) { $query->where('name', 'like', "%{$value}%"); } if (!empty($selectedIds)) { $query->orWhereIn('id', $selectedIds); } }) ->select('id', 'name') ->get() ->toArray(); } /** * Search users for mary-choices select dropdown. */ public function searchUsers(string $value = ''): void { $selectedIds = $this->form->notifiedUserIds; $this->usersSearchable = User::query() ->where('id', '!=', auth()->id()) ->where(function ($query) use ($value, $selectedIds): void { if ('' !== $value) { $query->where('name', 'like', "%{$value}%") ->orWhere('email', 'like', "%{$value}%"); } if (!empty($selectedIds)) { $query->orWhereIn('id', $selectedIds); } }) ->select('id', 'name') ->get() ->toArray(); } /** * Search assignable users dynamically with incremental pagination capability. */ public function searchAssignableUsers(string $value = ''): void { // Reset page limits if search keyword shifts if ($this->assignableUsersSearchTerm !== $value) { $this->assignableUsersSearchTerm = $value; $this->assignableUsersPerPage = 10; } $query = User::query(); $currentAssigneeId = $this->assignedUserId; if (is_array($currentAssigneeId)) { $currentAssigneeId = !empty($currentAssigneeId) ? $currentAssigneeId[0] : null; } if ('' !== $value) { $query->where(function ($q) use ($value) { $q->where('name', 'like', "%{$value}%") ->orWhere('email', 'like', "%{$value}%"); }); if ($currentAssigneeId && (int) $currentAssigneeId > 0) { $query->orWhere('id', (int) $currentAssigneeId); } } // Track total count matching query criteria to determine if "Load More" exists $totalMatches = $query->count(); $this->assignableUsersHasMore = $totalMatches > $this->assignableUsersPerPage; $users = $query ->select('id', 'name') ->limit($this->assignableUsersPerPage) ->get(); // If there is no search term and we have an assigned user, ensure they are in the list if ('' === $value && $currentAssigneeId && (int) $currentAssigneeId > 0) { if (!$users->contains('id', (int) $currentAssigneeId)) { $assignedUser = User::find($currentAssigneeId); if ($assignedUser) { $users->push($assignedUser); } } } $this->assignableUsersSearchable = $users ->map(fn($u) => ['id' => $u->id, 'name' => $u->name]) ->toArray(); } /** * Increments the page window for assignable users dropdown. */ public function loadMoreAssignableUsers(): void { $this->assignableUsersPerPage += 10; $this->searchAssignableUsers($this->assignableUsersSearchTerm); } /** * Select all matching assignable users based on current search context. */ public function selectAllAssignableUsers(): void { // Pluck IDs matching current search context criteria $matchedIds = User::query() ->when('' !== $this->assignableUsersSearchTerm, function ($q) { $q->where('name', 'like', "%{$this->assignableUsersSearchTerm}%") ->orWhere('email', 'like', "%{$this->assignableUsersSearchTerm}%"); }) ->pluck('id') ->toArray(); // Assign the first matched record or pass the array depending on single/multiple context. // Since updateAssignment expects a single nullable ID, we take the primary match or convert accordingly. $this->assignedUserId = !empty($matchedIds) ? (int) $matchedIds[0] : null; $this->updateAssignment(); } /** * Clear assignment selection. */ public function clearAssignableUsers(): void { $this->assignedUserId = null; $this->updateAssignment(); } public function selectAllRoles(): void { $this->form->notifiedRoleIds = Role::query()->pluck('id')->toArray(); $this->searchRoles(); } public function clearRoles(): void { $this->form->notifiedRoleIds = []; } public function selectAllUsers(): void { $this->form->notifiedUserIds = User::query() ->where('id', '!=', auth()->id()) ->pluck('id') ->toArray(); $this->searchUsers(); } public function clearUsers(): void { $this->form->notifiedUserIds = []; } public function saveTicket(): void { $this->form->validate(); $dto = new CreateTicketRequest( userId: (int) auth()->id(), connectedAppId: $this->form->connectedAppId ? (int) $this->form->connectedAppId : null, issueCategory: $this->form->issueCategory, description: $this->form->description ? trim($this->form->description) : null, notifiedRoleIds: $this->form->routingType === 'role' ? array_map('intval', $this->form->notifiedRoleIds) : [], notifiedUserIds: $this->form->routingType === 'user' ? array_map('intval', $this->form->notifiedUserIds) : [], attachment: $this->form->attachment, ); $this->attempt( action: function () use ($dto): void { $this->ticketService->create($dto); $this->form->reset(); $this->drawerOpen = false; $this->raise = false; $this->appId = null; $this->searchApps(); $this->searchRoles(); $this->searchUsers(); }, successMessage: 'Support Ticket has been raised successfully!' ); } public function showTicket(int $id): void { $this->attempt( action: function () use ($id): void { $this->selectedTicketId = $id; $this->selectedTicket = $this->ticketService->getTicket($id); $this->assignedUserId = $this->selectedTicket->assignedUserId; $this->replyMessage = ''; // Keep assignment lookup synchronized upon details injection $this->searchAssignableUsers(); $this->detailsModalOpen = true; }, showSuccess: false ); } public function changeStatus(int $ticketId, string $statusSlug): void { $this->attempt( action: function () use ($ticketId, $statusSlug): void { $this->ticketService->updateStatus($ticketId, $statusSlug); if ($this->selectedTicketId === $ticketId) { $this->selectedTicket = $this->ticketService->getTicket($ticketId); } }, successMessage: 'Ticket status updated successfully!' ); } public function saveReply(): void { $this->validate(['replyMessage' => 'required|string|min:1']); $this->attempt( action: function (): void { $dto = new PostReplyRequest( ticketId: $this->selectedTicketId, userId: (int) auth()->id(), message: $this->replyMessage, ); $this->ticketService->postReply($dto); $this->replyMessage = ''; $this->selectedTicket = $this->ticketService->getTicket($this->selectedTicketId); }, successMessage: 'Comment posted successfully!' ); } public function updateAssignment(): void { $this->attempt( action: function (): void { $assignedId = $this->assignedUserId; if (is_array($assignedId)) { $assignedId = !empty($assignedId) ? $assignedId[0] : null; } $finalAssignedUserId = ($assignedId && (int) $assignedId > 0) ? (int) $assignedId : null; $this->ticketService->assignUser( $this->selectedTicketId, $finalAssignedUserId ); $this->selectedTicket = $this->ticketService->getTicket($this->selectedTicketId); // Refresh list options with correct paginated users $this->searchAssignableUsers($this->assignableUsersSearchTerm); }, successMessage: 'Ticket assignment updated successfully!' ); } public function updatedAssignedUserId($value): void { $this->updateAssignment(); } #[Computed] public function canComment(): bool { if (!$this->selectedTicket) { return false; } $user = auth()->user(); if (!$user) { return false; } if ($user->hasRole(DefaultUserRole::Admin)) { return true; } if ($this->selectedTicket->userId === $user->id) { return true; } if ($this->selectedTicket->assignedUserId === $user->id) { return true; } $ticket = Ticket::find($this->selectedTicketId); if ($ticket) { if ($ticket->notifiedUsers()->where('users.id', $user->id)->exists()) { return true; } if ($ticket->notifiedRoles()->whereIn('roles.id', $user->roles->pluck('id')->toArray())->exists()) { return true; } } return false; } public function toggleDrawer(): void { $this->drawerOpen = !$this->drawerOpen; if (!$this->drawerOpen) { $this->reset(['raise', 'appId']); $this->form->reset(); } } }; ?>
We couldn't find any support tickets matching your search query or filter criteria. Try adjusting your search term or status filter.
@elseYou have not raised any support tickets yet. Click "Raise Ticket" to submit your renewal queries.
@endif{{ $selectedTicket->userName }}
{{ $selectedTicket->appName ?? 'General Inquiry' }}
{{ $selectedTicket->issueCategory }}
None specified
@endif{{ $shortDescription }}
@if($isLong){{ $description }}
@endif{{ $attachment['file_name'] }}
{{ round($attachment['file_size'] / 1024, 1) }} KB
Conversation Access Restricted
Only raised/tagged participants (ticket owner, assigned support, directly notified users, or notified-role members) have access to the discussion feed.