221 lines
13 KiB
PHP
Executable File
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>
|