Files

253 lines
15 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div class="hd-helpdesk-surface hd-mobile-ready 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="hd-page-hero 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
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>
<a href="{{ route('helpdesk.tickets.archived') }}" wire:navigate class="hd-btn hd-btn-amber">
<i class="fa-solid fa-box-archive" aria-hidden="true"></i>
<span>Archived</span>
</a>
</div>
</div>
</div>
</div>
<div class="hd-filter-panel 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">
<div class="hd-ticket-search-wrap">
<i class="fa-solid fa-magnifying-glass hd-ticket-search-icon" aria-hidden="true"></i>
<input
type="search"
class="hd-ticket-search"
wire:model.live.debounce.350ms="search"
placeholder="Search reference, subject or body"
aria-label="Search tickets"
>
</div>
<div class="hd-select-wrap">
<select class="hd-select" wire:model.live="scope" aria-label="Ticket scope">
<option value="open">Open tickets</option>
<option value="closed">Closed tickets</option>
<option value="all">All tickets</option>
</select>
</div>
<div class="hd-select-wrap">
<select class="hd-select" wire:model.live="status" aria-label="Ticket status">
<option value="">All statuses</option>
@foreach ($statuses as $status)
<option value="{{ $status->id }}">{{ $status->name }}</option>
@endforeach
</select>
</div>
<div class="hd-select-wrap">
<select class="hd-select" wire:model.live="priority" aria-label="Ticket priority">
<option value="">All priorities</option>
@foreach ($priorities as $priority)
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
@endforeach
</select>
</div>
</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">
<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="askToArchiveTicket({{ $ticket->id }})"
wire:loading.attr="disabled" wire:target="askToArchiveTicket({{ $ticket->id }})"
class="hd-btn hd-btn-sm hd-btn-amber disabled:cursor-not-allowed disabled:opacity-60"
title="Archive {{ $ticket->reference }}" dusk="delete-ticket-{{ $ticket->id }}">
<i class="fa-solid fa-box-archive" aria-hidden="true"></i>
<span>Archive</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="flex flex-col gap-3 border-t border-zinc-200 bg-zinc-50/70 px-4 py-4 dark:border-zinc-700 dark:bg-zinc-950/35 sm:flex-row sm:items-center sm:justify-between">
<div class="text-sm font-medium text-zinc-500 dark:text-zinc-400">
Showing
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $tickets->firstItem() ?? 0 }}</span>
–
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $tickets->lastItem() ?? 0 }}</span>
of
<span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $tickets->total() }}</span> tickets
</div>
<div class="min-w-0">
@if ($tickets->hasPages())
{{ $tickets->onEachSide(1)->links() }}
@else
<span class="inline-flex rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-xs font-semibold text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900">Page 1 of 1</span>
@endif
</div>
</div>
</div>
@if ($archiveModalOpen)
<div class="fixed inset-0 z-[9998] flex items-center justify-center bg-black/75 px-4 py-6 backdrop-blur-sm"
role="dialog" aria-modal="true" aria-labelledby="archive-ticket-modal-title"
wire:key="ticket-archive-modal-{{ $pendingArchiveTicketId ?? 'none' }}"
dusk="archive-ticket-confirmation-modal">
<div
class="w-full max-w-lg overflow-hidden rounded-3xl border border-amber-500/30 bg-zinc-950 text-zinc-100 shadow-2xl">
<div class="border-b border-amber-500/20 bg-amber-950/35 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-amber-500 text-zinc-950 shadow-lg shadow-amber-950/30">
<i class="fa-solid fa-triangle-exclamation" aria-hidden="true"></i>
</span>
<div class="min-w-0">
<h2 id="archive-ticket-modal-title" class="text-xl font-black tracking-tight text-white">
Archive ticket?
</h2>
<p class="mt-1 text-sm leading-6 text-amber-100/90">
This will move <span
class="font-mono font-bold">{{ $pendingArchiveTicketReference }}</span> to the ticket archive.
It can be restored later.
</p>
</div>
</div>
</div>
<div class="space-y-3 px-6 py-5">
@if ($pendingArchiveTicketSubject)
<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">{{ $pendingArchiveTicketSubject }}</p>
</div>
@endif
<p class="text-sm leading-6 text-zinc-300">
Archived tickets are removed from active ticket lists and the Kanban board until restored.
</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="cancelTicketArchive" class="hd-btn hd-btn-sm hd-btn-blue"
dusk="cancel-archive-ticket">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
<span>Cancel</span>
</button>
<button type="button" wire:click="confirmTicketArchive" wire:loading.attr="disabled"
wire:target="confirmTicketArchive"
class="hd-btn hd-btn-sm hd-btn-amber disabled:cursor-not-allowed disabled:opacity-60"
dusk="confirm-archive-ticket">
<i class="fa-solid fa-box-archive" aria-hidden="true"></i>
<span wire:loading.remove wire:target="confirmTicketArchive">Archive ticket</span>
<span wire:loading wire:target="confirmTicketArchive">Archiving...</span>
</button>
</div>
</div>
</div>
@endif
</div>