dark mode supoport and new design now with delet esupport

This commit is contained in:
Mark Gregory
2026-08-27 17:28:02 +01:00
parent 6de15d1398
commit c8408658c9
8 changed files with 365 additions and 59 deletions
@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace Modules\Helpdesk\Livewire;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Modules\Helpdesk\Models\Ticket;
final class ArchivedTicketIndex extends Component
{
use AuthorizesRequests;
use WithPagination;
#[Url]
public string $search = '';
public bool $deleteModalOpen = false;
public ?int $pendingDeleteTicketId = null;
public ?string $pendingDeleteTicketReference = null;
public ?string $pendingDeleteTicketSubject = null;
public function updatedSearch(): void
{
$this->resetPage();
}
public function restoreTicket(int $ticketId): void
{
$ticket = $this->archivedTicket($ticketId);
$this->authorize('delete', $ticket);
$reference = $ticket->reference;
$ticket->restore();
$ticket->forceFill(['archived_at' => null])->save();
$this->resetPage();
$this->dispatch(
'thredary:toast',
type: 'success',
title: 'Ticket restored',
message: "Ticket {$reference} was restored to the active ticket list.",
);
}
public function askToDeletePermanently(int $ticketId): void
{
$ticket = $this->archivedTicket($ticketId);
$this->authorize('delete', $ticket);
$this->pendingDeleteTicketId = $ticket->id;
$this->pendingDeleteTicketReference = $ticket->reference;
$this->pendingDeleteTicketSubject = $ticket->subject;
$this->deleteModalOpen = true;
}
public function cancelPermanentDelete(): void
{
$this->resetDeleteState();
}
public function deletePermanently(): void
{
if ($this->pendingDeleteTicketId === null) {
$this->resetDeleteState();
return;
}
$ticket = $this->archivedTicket($this->pendingDeleteTicketId);
$this->authorize('delete', $ticket);
$reference = $ticket->reference;
DB::transaction(static function () use ($ticket): void {
$attachments = $ticket->attachments()->get(['id', 'disk', 'path']);
foreach ($attachments as $attachment) {
if (filled($attachment->disk) && filled($attachment->path)) {
Storage::disk($attachment->disk)->delete($attachment->path);
}
}
$ticket->forceDelete();
});
$this->resetDeleteState();
$this->resetPage();
$this->dispatch(
'thredary:toast',
type: 'success',
title: 'Ticket permanently deleted',
message: "Ticket {$reference} and its archived data were permanently deleted.",
);
}
public function render(): View
{
return view('modules.helpdesk.livewire.archived-ticket-index', [
'tickets' => $this->tickets(),
])->layout('layouts.app');
}
private function archivedTicket(int $ticketId): Ticket
{
return Ticket::query()
->onlyTrashed()
->visibleTo(Auth::user())
->findOrFail($ticketId);
}
/** @return LengthAwarePaginator<int, Ticket> */
private function tickets(): LengthAwarePaginator
{
$user = Auth::user();
$search = trim($this->search);
return Ticket::query()
->onlyTrashed()
->without('user')
->select('helpdesk_tickets.*')
->addSelect([
'requester_name' => User::query()
->select('name')
->whereColumn('users.id', 'helpdesk_tickets.user_id')
->limit(1),
'agent_name' => User::query()
->select('name')
->whereColumn('users.id', 'helpdesk_tickets.agent_id')
->limit(1),
])
->with(['status', 'priority', 'category', 'project', 'team'])
->withCount('attachments')
->visibleTo($user)
->when($search !== '', static function (Builder $query) use ($search): Builder {
return $query->where(static function (Builder $subQuery) use ($search): void {
$subQuery->where('reference', 'like', '%'.$search.'%')
->orWhere('subject', 'like', '%'.$search.'%')
->orWhere('body', 'like', '%'.$search.'%');
});
})
->orderByDesc('archived_at')
->orderByDesc('deleted_at')
->paginate(15);
}
private function resetDeleteState(): void
{
$this->deleteModalOpen = false;
$this->pendingDeleteTicketId = null;
$this->pendingDeleteTicketReference = null;
$this->pendingDeleteTicketSubject = null;
}
}
+26 -25
View File
@@ -22,13 +22,13 @@ final class TicketIndex extends Component
use AuthorizesRequests;
use WithPagination;
public bool $deleteModalOpen = false;
public bool $archiveModalOpen = false;
public ?int $pendingDeleteTicketId = null;
public ?int $pendingArchiveTicketId = null;
public ?string $pendingDeleteTicketReference = null;
public ?string $pendingArchiveTicketReference = null;
public ?string $pendingDeleteTicketSubject = null;
public ?string $pendingArchiveTicketSubject = null;
#[Url]
public string $search = '';
@@ -48,54 +48,55 @@ final class TicketIndex extends Component
}
/**
* Open the delete confirmation modal for a visible ticket.
* Open the archive confirmation modal for a visible ticket.
*/
public function askToDeleteTicket(int $ticketId): void
public function askToArchiveTicket(int $ticketId): void
{
$ticket = Ticket::query()
->visibleTo(Auth::user())
->findOrFail($ticketId);
$this->pendingDeleteTicketId = $ticket->id;
$this->pendingDeleteTicketReference = $ticket->reference;
$this->pendingDeleteTicketSubject = $ticket->subject;
$this->deleteModalOpen = true;
$this->pendingArchiveTicketId = $ticket->id;
$this->pendingArchiveTicketReference = $ticket->reference;
$this->pendingArchiveTicketSubject = $ticket->subject;
$this->archiveModalOpen = true;
}
public function cancelTicketDelete(): void
public function cancelTicketArchive(): void
{
$this->resetTicketDeletionState();
$this->resetTicketArchiveState();
}
/**
* Delete the confirmed ticket.
* Archive the confirmed ticket using Laravel soft deletes.
*/
public function confirmTicketDelete(): void
public function confirmTicketArchive(): void
{
if ($this->pendingDeleteTicketId === null) {
$this->resetTicketDeletionState();
if ($this->pendingArchiveTicketId === null) {
$this->resetTicketArchiveState();
return;
}
$ticket = Ticket::query()
->visibleTo(Auth::user())
->findOrFail($this->pendingDeleteTicketId);
->findOrFail($this->pendingArchiveTicketId);
$this->authorize('delete', $ticket);
$reference = $ticket->reference;
$ticket->forceFill(['archived_at' => now()])->save();
$ticket->delete();
$this->resetTicketDeletionState();
$this->resetTicketArchiveState();
$this->resetPage();
$this->dispatch(
'thredary:toast',
type: 'success',
title: 'Ticket deleted',
message: "Ticket {$reference} was deleted successfully.",
title: 'Ticket archived',
message: "Ticket {$reference} was moved to the archive.",
);
}
@@ -108,12 +109,12 @@ final class TicketIndex extends Component
])->layout('layouts.app');
}
private function resetTicketDeletionState(): void
private function resetTicketArchiveState(): void
{
$this->deleteModalOpen = false;
$this->pendingDeleteTicketId = null;
$this->pendingDeleteTicketReference = null;
$this->pendingDeleteTicketSubject = null;
$this->archiveModalOpen = false;
$this->pendingArchiveTicketId = null;
$this->pendingArchiveTicketReference = null;
$this->pendingArchiveTicketSubject = null;
}
/**
+2
View File
@@ -13,6 +13,7 @@ use Modules\Helpdesk\Http\Controllers\TicketControlsController;
use Modules\Helpdesk\Http\Controllers\TicketStoreController;
use Modules\Helpdesk\Http\Controllers\TicketTimeLogController;
use Modules\Helpdesk\Http\Controllers\WorkflowOpenController;
use Modules\Helpdesk\Livewire\ArchivedTicketIndex;
use Modules\Helpdesk\Livewire\KanbanBoard;
use Modules\Helpdesk\Livewire\SettingsManager;
use Modules\Helpdesk\Livewire\TicketCreate;
@@ -28,6 +29,7 @@ Route::get('/settings', SettingsManager::class)->name('settings');
Route::get('/kanban', KanbanBoard::class)->name('kanban');
Route::get('/tickets', TicketIndex::class)->name('tickets.index');
Route::get('/tickets/archived', ArchivedTicketIndex::class)->name('tickets.archived');
Route::post('/tickets', TicketStoreController::class)->name('tickets.store');
Route::get('/tickets/create', TicketCreate::class)->name('tickets.create');
Route::get('/tickets/{ticket}/edit', TicketEdit::class)->name('tickets.edit');