fixe duploads and attachments now with colors tab and enhancements

This commit is contained in:
Mark Gregory
2026-06-16 19:01:26 +01:00
parent c19497efc2
commit f8ae45d810
8 changed files with 289 additions and 108 deletions
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Modules\Helpdesk\Http\Controllers;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Modules\Helpdesk\Models\Ticket;
use Symfony\Component\HttpFoundation\StreamedResponse;
final class AttachmentDownloadController extends Controller
{
/**
* Download a ticket attachment through Laravel storage instead of a public URL.
*
* This avoids broken public storage links and keeps ticket authorization enforced.
*
* @throws FileNotFoundException
*/
public function __invoke(int $attachment): StreamedResponse|RedirectResponse
{
abort_unless(Schema::hasTable('helpdesk_attachments'), 404);
$row = DB::table('helpdesk_attachments')->where('id', $attachment)->first();
abort_if($row === null, 404);
$ticketId = $row->ticket_id ?? null;
if ($ticketId !== null) {
$ticket = Ticket::query()->findOrFail((int) $ticketId);
Gate::authorize('view', $ticket);
}
$disk = (string) ($row->disk ?? 'public');
$path = (string) ($row->path ?? $row->file_path ?? $row->stored_path ?? '');
$name = (string) ($row->original_name
?? $row->original_filename
?? $row->file_name
?? $row->filename
?? basename($path));
abort_if($path === '', 404);
abort_unless(Storage::disk($disk)->exists($path), 404);
return Storage::disk($disk)->download($path, $name);
}
}
+25 -22
View File
@@ -209,37 +209,40 @@ final class TicketEdit extends Component
}
/**
* Return assignable users. Falls back to all tenant users when helpdesk flags/roles are absent.
* Return assignable users for this ticket.
*
* The dropdown must never be blank just because optional helpdesk_agent/helpdesk_admin
* flags are missing or false. Prefer tenant/company users, then fall back to all users.
*
* @return EloquentCollection<int, User>
*/
private function agentOptions(): EloquentCollection
{
return User::query()
->select(['id', 'name', 'email', 'company_id'])
->when($this->company_id !== null && Schema::hasColumn('users', 'company_id'), function ($query): void {
$query->where(function ($query): void {
$columns = ['id', 'name', 'email'];
if (Schema::hasColumn('users', 'company_id')) {
$columns[] = 'company_id';
}
$query = User::query()->select($columns)->orderBy('name');
if ($this->company_id !== null && Schema::hasColumn('users', 'company_id')) {
$tenantUsers = (clone $query)
->where(function ($query): void {
$query->where('company_id', $this->company_id)
->orWhereNull('company_id');
});
})
->when(Schema::hasColumn('users', 'helpdesk_agent') || Schema::hasColumn('users', 'helpdesk_admin'), function ($query): void {
$query->where(function ($query): void {
if (Schema::hasColumn('users', 'helpdesk_agent')) {
$query->orWhere('helpdesk_agent', true);
}
})
->when($this->agent_id !== null, function ($query): void {
$query->orWhereKey($this->agent_id);
})
->get();
if (Schema::hasColumn('users', 'helpdesk_admin')) {
$query->orWhere('helpdesk_admin', true);
}
if ($tenantUsers->isNotEmpty()) {
return $tenantUsers;
}
}
if ($this->agent_id !== null) {
$query->orWhereKey($this->agent_id);
}
});
})
->orderBy('name')
->get();
return $query->get();
}
private function freshTicket(Ticket $ticket): Ticket
@@ -63,6 +63,7 @@ final class TicketIndex extends Component
return Ticket::query()
->with(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team'])
->withCount('attachments')
->visibleTo($user)
->when($this->scope === 'open', static fn (Builder $query): Builder => $query->whereNull('completed_at'))
->when($this->scope === 'closed', static fn (Builder $query): Builder => $query->whereNotNull('completed_at'))
+124 -44
View File
@@ -9,6 +9,8 @@ use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Schema;
use Livewire\Attributes\Validate;
use Livewire\Component;
@@ -135,55 +137,124 @@ final class TicketShow extends Component
$this->ticket = $this->freshTicket($this->ticket);
}
/**
* Persist the selected ticket status using a direct table update so Livewire UI wrappers cannot silently fail.
*/
public function updateStatus(WorkflowNotificationService $workflow, SlaService $sla): void
{
$this->authorize('update', $this->ticket);
$this->validate(['status_id' => ['required', 'integer', 'exists:helpdesk_statuses,id']]);
$status = Status::query()->findOrFail($this->status_id);
$this->ticket->forceFill([
$statusId = $this->normaliseNullableInt($this->status_id);
validator(
['status_id' => $statusId],
['status_id' => ['required', 'integer', Rule::exists('helpdesk_statuses', 'id')]],
)->validate();
$status = Status::query()->findOrFail($statusId);
$updates = [
'status_id' => $status->id,
'completed_at' => $status->is_closed ? now() : null,
'last_activity_at' => now(),
])->save();
];
if (Schema::hasColumn('helpdesk_tickets', 'updated_at')) {
$updates['updated_at'] = now();
}
DB::table('helpdesk_tickets')
->where('id', $this->ticket->id)
->update($updates);
$this->ticket = $this->freshTicket($this->ticket);
$this->status_id = (int) $this->ticket->status_id;
if ($status->is_closed) {
$sla->recordClosure($this->ticket);
}
$this->ticket = $this->freshTicket($this->ticket);
$workflow->dispatch($this->ticket, $status->is_closed ? 'ticket.closed' : 'ticket.updated', Auth::user());
$this->dispatch('helpdesk-status-updated');
session()->flash('ticket-action-updated', 'Status updated to '.$this->ticket->status?->name.'.');
$this->dispatch('helpdesk-status-updated', statusId: $this->status_id);
}
/**
* Persist the selected ticket priority using a direct table update.
*/
public function updatePriority(WorkflowNotificationService $workflow): void
{
$this->authorize('update', $this->ticket);
$this->validate(['priority_id' => ['required', 'integer', 'exists:helpdesk_priorities,id']]);
$this->ticket->forceFill([
'priority_id' => $this->priority_id,
$priorityId = $this->normaliseNullableInt($this->priority_id);
validator(
['priority_id' => $priorityId],
['priority_id' => ['required', 'integer', Rule::exists('helpdesk_priorities', 'id')]],
)->validate();
$updates = [
'priority_id' => $priorityId,
'last_activity_at' => now(),
])->save();
];
if (Schema::hasColumn('helpdesk_tickets', 'updated_at')) {
$updates['updated_at'] = now();
}
DB::table('helpdesk_tickets')
->where('id', $this->ticket->id)
->update($updates);
$this->ticket = $this->freshTicket($this->ticket);
$this->priority_id = (int) $this->ticket->priority_id;
$workflow->dispatch($this->ticket, 'ticket.updated', Auth::user());
$this->dispatch('helpdesk-priority-updated');
session()->flash('ticket-action-updated', 'Priority updated to '.$this->ticket->priority?->name.'.');
$this->dispatch('helpdesk-priority-updated', priorityId: $this->priority_id);
}
/**
* Persist the selected ticket assignee.
*/
public function updateAgent(WorkflowNotificationService $workflow): void
{
$this->authorize('update', $this->ticket);
$this->validate(['agent_id' => ['nullable', 'integer', 'exists:users,id']]);
$this->ticket->forceFill([
'agent_id' => $this->agent_id,
$agentId = $this->normaliseNullableInt($this->agent_id);
validator(
['agent_id' => $agentId],
['agent_id' => ['nullable', 'integer', Rule::exists('users', 'id')]],
)->validate();
abort_unless(Schema::hasColumn('helpdesk_tickets', 'agent_id'), 500, 'The helpdesk_tickets.agent_id column is missing.');
$updates = [
'agent_id' => $agentId,
'last_activity_at' => now(),
])->save();
];
if (Schema::hasColumn('helpdesk_tickets', 'updated_at')) {
$updates['updated_at'] = now();
}
DB::table('helpdesk_tickets')
->where('id', $this->ticket->id)
->update($updates);
$this->ticket = $this->freshTicket($this->ticket);
$this->agent_id = $this->ticket->agent_id === null ? null : (int) $this->ticket->agent_id;
$workflow->dispatch($this->ticket, 'ticket.updated', Auth::user());
$this->dispatch('helpdesk-agent-updated');
session()->flash('ticket-action-updated', $this->agent_id === null
? 'Ticket is now unassigned.'
: 'Ticket assigned to '.$this->ticket->agent?->name.'.');
$this->dispatch('helpdesk-agent-updated', agentId: $this->agent_id);
}
public function render(): View
@@ -196,6 +267,18 @@ final class TicketShow extends Component
])->layout('layouts.app');
}
/**
* Convert Livewire select values into a nullable integer.
*/
private function normaliseNullableInt(mixed $value): ?int
{
if ($value === null || $value === '' || $value === '0' || $value === 0) {
return null;
}
return (int) $value;
}
private function freshTicket(Ticket $ticket): Ticket
{
$ticket = $ticket->fresh([
@@ -228,45 +311,42 @@ final class TicketShow extends Component
}
/**
* Return assignable users. Falls back to all tenant users when helpdesk flags/roles are absent.
* Return assignable users for this ticket.
*
* The dropdown must never be blank just because optional helpdesk_agent/helpdesk_admin
* flags are missing or false. Prefer tenant/company users, then fall back to all users.
*
* @return EloquentCollection<int, User>
*/
private function agentOptions(): EloquentCollection
{
return User::query()
->select(['id', 'name', 'email', 'company_id'])
->when($this->ticket->company_id !== null && Schema::hasColumn('users', 'company_id'), function ($query): void {
$query->where(function ($query): void {
$columns = ['id', 'name', 'email'];
if (Schema::hasColumn('users', 'company_id')) {
$columns[] = 'company_id';
}
$query = User::query()->select($columns)->orderBy('name');
if ($this->ticket->company_id !== null && Schema::hasColumn('users', 'company_id')) {
$tenantUsers = (clone $query)
->where(function ($query): void {
$query->where('company_id', $this->ticket->company_id)
->orWhereNull('company_id');
});
})
->when(Schema::hasColumn('users', 'helpdesk_agent') || Schema::hasColumn('users', 'helpdesk_admin'), function ($query): void {
$query->where(function ($query): void {
if (Schema::hasColumn('users', 'helpdesk_agent')) {
$query->orWhere('helpdesk_agent', true);
}
})
->when($this->agent_id !== null, function ($query): void {
$query->orWhereKey($this->agent_id);
})
->get();
if (Schema::hasColumn('users', 'helpdesk_admin')) {
$query->orWhere('helpdesk_admin', true);
}
if ($tenantUsers->isNotEmpty()) {
return $tenantUsers;
}
}
if ($this->agent_id !== null) {
$query->orWhereKey($this->agent_id);
}
});
})
->orderBy('name')
->get();
return $query->get();
}
/**
* Build an unlimited nested comment tree from a flat eager-loaded collection.
*
* @param Collection<int|string, Collection<int, mixed>> $grouped
* @return Collection<int, mixed>
*/
private function buildCommentTree(Collection $grouped, ?int $parentId = null): Collection
{
return $grouped->get($parentId, collect())->map(function ($comment) use ($grouped) {
@@ -381,6 +381,7 @@
Uploading attachments...
</div>
@if ($ticket->attachments->isNotEmpty())
<div class="mt-4 space-y-3">
<div class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-emerald-500/30 bg-emerald-500/10 px-4 py-3">
<h3 class="inline-flex items-center gap-2 text-sm font-extrabold text-emerald-200">
@@ -396,10 +397,10 @@
</span>
</div>
@forelse ($ticket->attachments as $attachment)
@foreach ($ticket->attachments as $attachment)
<div class="flex flex-col gap-3 rounded-xl border border-zinc-700 bg-zinc-900 p-3 text-sm sm:flex-row sm:items-center sm:justify-between">
<a
href="{{ method_exists($attachment, 'url') ? $attachment->url() : Storage::disk($attachment->disk ?? 'public')->url($attachment->path ?? $attachment->file_path) }}"
href="{{ route('helpdesk.attachments.download', $attachment->id) }}"
target="_blank"
class="min-w-0 font-medium text-blue-300 hover:text-blue-200"
>
@@ -416,12 +417,9 @@
Remove
</button>
</div>
@empty
<div class="rounded-xl border border-dashed border-zinc-700 bg-zinc-900 px-4 py-3 text-sm text-zinc-400">
No ticket-level attachments are currently saved.
</div>
@endforelse
@endforeach
</div>
@endif
@if ($attachments !== [])
<div class="mt-4 rounded-xl border border-blue-500/30 bg-blue-500/10 px-4 py-3 text-sm text-blue-100">
@@ -198,6 +198,7 @@
<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>
</tr>
</thead>
@@ -216,11 +217,25 @@
<td class="px-4 py-3"><flux:badge color="{{ $ticket->status->color }}">{{ $ticket->status->name }}</flux:badge></td>
<td class="px-4 py-3"><flux:badge color="{{ $ticket->priority->color }}">{{ $ticket->priority->name }}</flux: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>
</tr>
@empty
<tr>
<td colspan="6" class="px-4 py-12 text-center text-zinc-500">No tickets matched your filters.</td>
<td colspan="7" class="px-4 py-12 text-center text-zinc-500">No tickets matched your filters.</td>
</tr>
@endforelse
</tbody>
@@ -37,6 +37,7 @@
</div>
</article>
@if ($ticket->attachments->isNotEmpty())
<div class="overflow-hidden rounded-2xl border border-blue-200 bg-white shadow-sm dark:border-blue-900/60 dark:bg-zinc-900">
<div class="bg-gradient-to-r from-blue-700 via-sky-600 to-emerald-600 px-6 py-4 text-white">
<div class="flex items-center justify-between gap-4">
@@ -59,18 +60,15 @@
</div>
<div class="grid gap-3 p-6 sm:grid-cols-2">
@forelse ($ticket->attachments as $attachment)
<a href="{{ method_exists($attachment, 'url') ? $attachment->url() : Storage::disk($attachment->disk ?? 'public')->url($attachment->path ?? $attachment->file_path) }}" target="_blank" class="rounded-xl border border-zinc-200 p-3 text-sm hover:bg-zinc-50 dark:border-zinc-700 dark:hover:bg-zinc-800">
@foreach ($ticket->attachments as $attachment)
<a href="{{ route('helpdesk.attachments.download', $attachment->id) }}" target="_blank" class="rounded-xl border border-zinc-200 p-3 text-sm hover:bg-zinc-50 dark:border-zinc-700 dark:hover:bg-zinc-800">
<div class="font-medium text-zinc-900 dark:text-zinc-100">{{ $attachment->original_name ?? $attachment->original_filename ?? $attachment->filename ?? $attachment->file_name ?? basename($attachment->path ?? $attachment->file_path ?? '') }}</div>
<div class="text-xs text-zinc-500">{{ Number::fileSize($attachment->size ?? $attachment->size_bytes ?? 0) }}</div>
</a>
@empty
<div class="rounded-xl border border-dashed border-zinc-300 p-4 text-sm text-zinc-500 dark:border-zinc-700">
No ticket-level attachments are currently saved.
</div>
@endforelse
@endforeach
</div>
</div>
@endif
<section class="space-y-4">
<h2 class="text-lg font-semibold text-zinc-900 dark:text-white">Conversation</h2>
@@ -204,37 +202,65 @@
@can('update', $ticket)
<div class="rounded-2xl border border-zinc-200 bg-white p-5 shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
<h2 class="mb-4 text-base font-semibold text-zinc-900 dark:text-white">Agent actions</h2>
<label class="mb-2 block text-sm font-medium text-zinc-700 dark:text-zinc-200" for="show_agent_id">Assigned agent</label>
<select
id="show_agent_id"
wire:model.live="agent_id"
class="block w-full rounded-lg border border-zinc-300 bg-white p-2 text-sm dark:border-zinc-700 dark:bg-zinc-950"
data-dusk="ticket-show-agent"
>
<option value="">Unassigned</option>
@forelse ($agents as $agent)
<option value="{{ $agent->id }}">{{ $agent->name }} · {{ $agent->email }}</option>
@empty
<option value="" disabled>No users available for assignment</option>
@endforelse
</select>
<flux:button dusk="update-agent" wire:click="updateAgent" class="mt-4 mb-5 w-full !bg-emerald-600 !text-white hover:!bg-emerald-700 focus:!ring-emerald-400/60" variant="primary" icon="user">Update assigned agent</flux:button>
<div class="mb-5 rounded-2xl bg-gradient-to-r from-blue-700 via-indigo-700 to-violet-700 px-4 py-4 text-white shadow-sm">
<p class="text-xs font-black uppercase tracking-[0.24em] text-blue-100">Ticket controls</p>
<h2 class="mt-1 text-lg font-black tracking-tight">Assignment, status and priority</h2>
<p class="mt-1 text-sm font-semibold text-blue-100/90">Use the explicit update buttons below to save each change.</p>
</div>
<flux:select wire:model="status_id" label="Status">
@foreach ($statuses as $status)
<option value="{{ $status->id }}">{{ $status->name }}</option>
@endforeach
</flux:select>
<flux:button dusk="update-status" wire:click="updateStatus" class="mt-4 w-full !bg-blue-600 !text-white hover:!bg-blue-700 focus:!ring-blue-400/60" variant="primary" icon="check">Update status</flux:button>
<div class="mt-5">
<flux:select wire:model="priority_id" label="Priority">
@if (session('ticket-action-updated'))
<div class="mb-5 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-sm font-bold text-emerald-800 dark:border-emerald-800 dark:bg-emerald-950 dark:text-emerald-100">
{{ session('ticket-action-updated') }}
</div>
@endif
<form wire:submit.prevent="updateAgent" class="mb-6 space-y-3">
<label class="block text-sm font-bold text-zinc-700 dark:text-zinc-200" for="show_agent_id">Assigned agent</label>
<select id="show_agent_id" wire:model.live="agent_id" class="hd-select block w-full rounded-xl border border-zinc-300 bg-white p-3 text-sm font-bold text-zinc-900 shadow-sm focus:border-emerald-500 focus:ring-emerald-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100" data-dusk="ticket-show-agent">
<option value="">Unassigned</option>
@forelse ($agents as $agent)
<option value="{{ $agent->id }}">{{ $agent->name }} · {{ $agent->email }}</option>
@empty
<option value="" disabled>No users available for assignment</option>
@endforelse
</select>
@error('agent_id') <p class="text-sm font-bold text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
<button type="submit" dusk="update-agent" class="inline-flex w-full items-center justify-center rounded-xl bg-emerald-600 px-4 py-2.5 text-sm font-black text-white shadow-sm transition hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-emerald-400/70 disabled:cursor-not-allowed disabled:opacity-70" wire:loading.attr="disabled" wire:target="updateAgent">
<i class="fa-solid fa-user-check mr-2" aria-hidden="true"></i>
<span wire:loading.remove wire:target="updateAgent">Assign agent</span>
<span wire:loading wire:target="updateAgent">Assigning...</span>
</button>
</form>
<form wire:submit.prevent="updateStatus" class="mb-6 space-y-3">
<label class="block text-sm font-bold text-zinc-700 dark:text-zinc-200" for="show_status_id">Status</label>
<select id="show_status_id" wire:model.live="status_id" class="hd-select block w-full rounded-xl border border-zinc-300 bg-white p-3 text-sm font-bold text-zinc-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100" data-dusk="ticket-show-status">
@foreach ($statuses as $status)
<option value="{{ $status->id }}">{{ $status->name }}</option>
@endforeach
</select>
@error('status_id') <p class="text-sm font-bold text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
<button type="submit" dusk="update-status" class="inline-flex w-full items-center justify-center rounded-xl bg-blue-600 px-4 py-2.5 text-sm font-black text-white shadow-sm transition hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400/70 disabled:cursor-not-allowed disabled:opacity-70" wire:loading.attr="disabled" wire:target="updateStatus">
<i class="fa-solid fa-circle-check mr-2" aria-hidden="true"></i>
<span wire:loading.remove wire:target="updateStatus">Update status</span>
<span wire:loading wire:target="updateStatus">Updating...</span>
</button>
</form>
<form wire:submit.prevent="updatePriority" class="space-y-3">
<label class="block text-sm font-bold text-zinc-700 dark:text-zinc-200" for="show_priority_id">Priority</label>
<select id="show_priority_id" wire:model.live="priority_id" class="hd-select block w-full rounded-xl border border-zinc-300 bg-white p-3 text-sm font-bold text-zinc-900 shadow-sm focus:border-amber-500 focus:ring-amber-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100" data-dusk="ticket-show-priority">
@foreach ($priorities as $priority)
<option value="{{ $priority->id }}">{{ $priority->name }}</option>
@endforeach
</flux:select>
<flux:button dusk="update-priority" wire:click="updatePriority" class="mt-4 w-full !bg-amber-500 !text-white hover:!bg-amber-600 focus:!ring-amber-300/60" variant="ghost" icon="flag">Update priority</flux:button>
</div>
</select>
@error('priority_id') <p class="text-sm font-bold text-red-600 dark:text-red-400">{{ $message }}</p> @enderror
<button type="submit" dusk="update-priority" class="inline-flex w-full items-center justify-center rounded-xl bg-amber-500 px-4 py-2.5 text-sm font-black text-white shadow-sm transition hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-300/70 disabled:cursor-not-allowed disabled:opacity-70" wire:loading.attr="disabled" wire:target="updatePriority">
<i class="fa-solid fa-flag mr-2" aria-hidden="true"></i>
<span wire:loading.remove wire:target="updatePriority">Update priority</span>
<span wire:loading wire:target="updatePriority">Updating...</span>
</button>
</form>
</div>
@endcan
</aside>
+4
View File
@@ -3,6 +3,7 @@
declare(strict_types=1);
use Illuminate\Support\Facades\Route;
use Modules\Helpdesk\Http\Controllers\AttachmentDownloadController;
use Modules\Helpdesk\Http\Controllers\HelpdeskController;
use Modules\Helpdesk\Http\Controllers\TicketStoreController;
use Modules\Helpdesk\Http\Controllers\WorkflowOpenController;
@@ -25,3 +26,6 @@ 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');
Route::get('/tickets/{ticket}', TicketShow::class)->name('tickets.show');
Route::get('/attachments/{attachment}/download', AttachmentDownloadController::class)
->whereNumber('attachment')
->name('attachments.download');