Files
2026-09-21 13:33:05 +01:00

221 lines
13 KiB
PHP
Executable File

<div class="hd-helpdesk-surface space-y-6" dusk="helpdesk-ticket-index">
<div
class="overflow-hidden rounded-3xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
<div class="bg-gradient-to-br from-slate-950 via-slate-900 to-blue-950 px-6 py-7 text-white">
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.28em] text-blue-200">Ticket management</p>
<h1 class="mt-2 text-3xl font-semibold tracking-tight">Active tickets</h1>
<p class="mt-2 max-w-3xl text-sm text-blue-100/90">Search, triage and manage support requests across
companies, projects, teams, priorities and statuses.</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ route('helpdesk.index') }}" wire:navigate class="hd-btn hd-btn-blue">
<i class="fa-solid fa-chart-bar" aria-hidden="true"></i>
<span>Dashboard</span>
</a>
<a href="{{ route('helpdesk.tickets.create') }}" wire:navigate class="hd-btn hd-btn-green">
<i class="fa-solid fa-plus" aria-hidden="true"></i>
<span>New Ticket</span>
</a>
</div>
</div>
</div>
</div>
<div class="rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
<div class="grid gap-4 md:grid-cols-4">
<flux:input wire:model.live.debounce.350ms="search" icon="magnifying-glass"
placeholder="Search reference, subject or body" />
<flux:select wire:model.live="scope">
<option value="open">Open tickets</option>
<option value="closed">Closed tickets</option>
<option value="all">All tickets</option>
</flux:select>
<flux:select wire:model.live="status">
<option value="">All statuses</option>
@foreach ($statuses as $status)
<option value="{{ $status->id }}">{{ $status->name }}</option>
@endforeach
</flux:select>
<flux:select wire:model.live="priority">
<option value="">All priorities</option>
@foreach ($priorities as $priority)
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
@endforeach
</flux:select>
</div>
</div>
<div
class="overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
<div wire:loading.flex
class="items-center gap-2 border-b border-zinc-200 bg-blue-50 px-4 py-3 text-sm text-blue-800 dark:border-zinc-700 dark:bg-blue-950 dark:text-blue-200">
<flux:icon.arrow-path class="size-4 animate-spin" /> Refreshing tickets...
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-zinc-200 text-sm dark:divide-zinc-700">
<caption class="sr-only">Data table</caption>
<thead
class="bg-zinc-50 text-left text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:bg-zinc-800 dark:text-zinc-300">
<tr>
<th class="px-4 py-3">Reference</th>
<th class="px-4 py-3">Subject</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Priority</th>
<th class="px-4 py-3">Agent</th>
<th class="px-4 py-3 text-center">Files</th>
<th class="px-4 py-3">Last activity</th>
<th class="px-4 py-3 text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-zinc-100 dark:divide-zinc-800">
@forelse ($tickets as $ticket)
<tr class="hover:bg-zinc-50 dark:hover:bg-zinc-800/70">
<td class="whitespace-nowrap px-4 py-3 font-mono text-xs">
<a href="{{ route('helpdesk.tickets.show', $ticket) }}" wire:navigate
class="font-semibold text-blue-700 hover:underline dark:text-blue-300">
{{ $ticket->reference }}
</a>
</td>
<td class="px-4 py-3">
<div class="font-medium text-zinc-900 dark:text-zinc-100">{{ $ticket->subject }}</div>
<div class="text-xs text-zinc-500">
{{ $ticket->category?->name ?? 'No category' }}
@if ($ticket->project)
· {{ $ticket->project->title }}
@endif
@if ($ticket->team)
· {{ $ticket->team->name }}
@endif
</div>
</td>
<td class="px-4 py-3">
<x-helpdesk::colour-badge :color="$ticket->status?->color ?? 'zinc'">
{{ $ticket->status?->name ?? 'No status' }}
</x-helpdesk::colour-badge>
</td>
<td class="px-4 py-3">
<x-helpdesk::colour-badge :color="$ticket->priority?->color ?? 'zinc'">
{{ $ticket->priority?->name ?? 'No priority' }}
</x-helpdesk::colour-badge>
</td>
<td class="px-4 py-3 text-zinc-600 dark:text-zinc-300">
{{ $ticket->agent_name ?? 'Unassigned' }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-center">
@if (($ticket->attachments_count ?? 0) > 0)
<span
class="inline-flex items-center justify-center gap-1 rounded-full border border-blue-200 bg-blue-50 px-2.5 py-1 text-xs font-black text-blue-700 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200"
title="{{ $ticket->attachments_count }} attachment{{ $ticket->attachments_count === 1 ? '' : 's' }}">
<i class="fa-solid fa-paperclip" aria-hidden="true"></i>
<span>{{ $ticket->attachments_count }}</span>
<span
class="sr-only">attachment{{ $ticket->attachments_count === 1 ? '' : 's' }}</span>
</span>
@else
<span
class="inline-flex items-center justify-center rounded-full border border-zinc-200 bg-zinc-50 px-2.5 py-1 text-xs font-bold text-zinc-400 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-500"
title="No attachments">
<i class="fa-regular fa-circle" aria-hidden="true"></i>
<span class="sr-only">No attachments</span>
</span>
@endif
</td>
<td class="whitespace-nowrap px-4 py-3 text-zinc-500">
{{ $ticket->last_activity_at?->diffForHumans() ?? $ticket->created_at->diffForHumans() }}
</td>
<td class="whitespace-nowrap px-4 py-3 text-right">
@can('delete', $ticket)
<button type="button" wire:click="askToDeleteTicket({{ $ticket->id }})"
wire:loading.attr="disabled" wire:target="askToDeleteTicket({{ $ticket->id }})"
class="hd-btn hd-btn-sm hd-btn-red disabled:cursor-not-allowed disabled:opacity-60"
title="Delete {{ $ticket->reference }}" dusk="delete-ticket-{{ $ticket->id }}">
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span>Delete</span>
</button>
@endcan
</td>
</tr>
@empty
<tr>
<td colspan="8" class="px-4 py-12 text-center text-zinc-500">No tickets matched your
filters.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="border-t border-zinc-200 p-4 dark:border-zinc-700">
{{ $tickets->links() }}
</div>
</div>
@if ($deleteModalOpen)
<dialog open class="fixed inset-0 z-[9998] flex items-center justify-center bg-black/75 px-4 py-6 backdrop-blur-sm"
aria-modal="true" aria-labelledby="delete-ticket-modal-title"
wire:key="ticket-delete-modal-{{ $pendingDeleteTicketId ?? 'none' }}"
dusk="delete-ticket-confirmation-modal">
<div
class="w-full max-w-lg overflow-hidden rounded-3xl border border-red-500/30 bg-zinc-950 text-zinc-100 shadow-2xl">
<div class="border-b border-red-500/20 bg-red-950/40 px-6 py-5">
<div class="flex items-start gap-4">
<span
class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-red-600 text-white shadow-lg shadow-red-950/40">
<i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i>
</span>
<div class="min-w-0">
<h2 id="delete-ticket-modal-title" class="text-xl font-black tracking-tight text-white">
Delete ticket?
</h2>
<p class="mt-1 text-sm leading-6 text-red-100/90">
This will delete <span
class="font-mono font-bold">{{ $pendingDeleteTicketReference }}</span>. This
action cannot be undone.
</p>
</div>
</div>
</div>
<div class="space-y-3 px-6 py-5">
@if ($pendingDeleteTicketSubject)
<div class="rounded-2xl border border-zinc-800 bg-zinc-900 px-4 py-3">
<p class="text-xs font-bold uppercase tracking-[0.22em] text-zinc-500">Ticket subject</p>
<p class="mt-1 text-sm font-semibold text-zinc-100">{{ $pendingDeleteTicketSubject }}</p>
</div>
@endif
<p class="text-sm leading-6 text-zinc-300">
Confirm only if this ticket was created in error or is no longer required.
</p>
</div>
<div
class="flex flex-col-reverse gap-3 border-t border-zinc-800 bg-zinc-900/80 px-6 py-4 sm:flex-row sm:justify-end">
<button type="button" wire:click="cancelTicketDelete" class="hd-btn hd-btn-sm hd-btn-blue"
dusk="cancel-delete-ticket">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
<span>Cancel</span>
</button>
<button type="button" wire:click="confirmTicketDelete" wire:loading.attr="disabled"
wire:target="confirmTicketDelete"
class="hd-btn hd-btn-sm hd-btn-red disabled:cursor-not-allowed disabled:opacity-60"
dusk="confirm-delete-ticket">
<i class="fa-solid fa-trash" aria-hidden="true"></i>
<span wire:loading.remove wire:target="confirmTicketDelete">Delete ticket</span>
<span wire:loading wire:target="confirmTicketDelete">Deleting...</span>
</button>
</div>
</div>
</dialog>
@endif
</div>