From 037c08498b5fac4a808f0d954d847b2a5bd06bd9 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Tue, 25 Aug 2026 22:19:04 +0100 Subject: [PATCH] settings updates --- Modules/Helpdesk/Livewire/KanbanBoard.php | 16 +- Modules/Helpdesk/Livewire/SettingsManager.php | 2 +- Modules/Helpdesk/Livewire/TicketCreate.php | 2 +- Modules/Helpdesk/Livewire/TicketEdit.php | 2 +- Modules/Helpdesk/Livewire/TicketIndex.php | 2 +- Modules/Helpdesk/Livewire/TicketShow.php | 2 +- Modules/Helpdesk/Models/Ticket.php | 6 +- Modules/Helpdesk/Policies/TicketPolicy.php | 61 +- build:helpdesk | 1 + package-lock.json | 10 + package.json | 10 +- resources/themes/backend/assets/css/app.css | 11 +- .../themes/backend/assets/css/helpdesk.css | 33 + .../helpdesk/colour-badge.blade.php | 16 + .../helpdesk/colour-select.blade.php | 74 + .../components/helpdesk/date-picker.blade.php | 21 + .../components/helpdesk/html-editor.blade.php | 143 ++ .../components/helpdesk/layouts/app.blade.php | 9 + .../helpdesk/lookup-table.blade.php | 47 + .../helpdesk/project-table.blade.php | 47 + .../helpdesk/repository-table.blade.php | 36 + .../helpdesk/settings-section.blade.php | 33 + .../components/helpdesk/team-table.blade.php | 45 + .../modules/helpdesk/dashboard/card.blade.php | 1 + .../livewire/dashboard-widget.blade.php | 282 ++- .../livewire/settings-manager.blade.php | 1002 +++++++++- .../helpdesk/livewire/ticket-create.blade.php | 459 ++++- .../helpdesk/livewire/ticket-edit.blade.php | 471 ++++- .../helpdesk/livewire/ticket-index.blade.php | 333 ++-- .../helpdesk/livewire/ticket-show.blade.php | 1708 ++++++++++++++++- .../helpdesk/livewire/ticket-timer.blade.php | 161 +- .../partials/comment-thread.blade.php | 327 ++++ .../helpdesk/partials/page-header.blade.php | 27 + .../partials/ticket-controls.blade.php | 275 +++ .../ticket-time-log-history-query.blade.php | 16 + .../partials/ticket-time-timer.blade.php | 3 + .../helpdesk/partials/time-log-form.blade.php | 32 + 37 files changed, 5141 insertions(+), 585 deletions(-) create mode 100644 build:helpdesk create mode 100644 resources/themes/backend/views/components/helpdesk/colour-badge.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/colour-select.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/date-picker.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/html-editor.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/layouts/app.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/lookup-table.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/project-table.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/repository-table.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/settings-section.blade.php create mode 100644 resources/themes/backend/views/components/helpdesk/team-table.blade.php create mode 100644 resources/themes/backend/views/modules/helpdesk/dashboard/card.blade.php create mode 100644 resources/themes/backend/views/modules/helpdesk/partials/comment-thread.blade.php create mode 100644 resources/themes/backend/views/modules/helpdesk/partials/page-header.blade.php create mode 100644 resources/themes/backend/views/modules/helpdesk/partials/ticket-controls.blade.php create mode 100644 resources/themes/backend/views/modules/helpdesk/partials/ticket-time-log-history-query.blade.php create mode 100644 resources/themes/backend/views/modules/helpdesk/partials/ticket-time-timer.blade.php create mode 100644 resources/themes/backend/views/modules/helpdesk/partials/time-log-form.blade.php diff --git a/Modules/Helpdesk/Livewire/KanbanBoard.php b/Modules/Helpdesk/Livewire/KanbanBoard.php index 202148a..008425f 100644 --- a/Modules/Helpdesk/Livewire/KanbanBoard.php +++ b/Modules/Helpdesk/Livewire/KanbanBoard.php @@ -73,7 +73,7 @@ final class KanbanBoard extends Component 'last_activity_at' => now(), ])->save(); - $freshTicket = $ticket->fresh(['status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) ?? $ticket; + $freshTicket = $ticket->fresh(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) ?? $ticket; $workflow->dispatch( $freshTicket, @@ -81,12 +81,12 @@ final class KanbanBoard extends Component Auth::user(), ); - $message = 'Ticket ' . $freshTicket->reference . ' moved from ' . $oldStatusName . ' to ' . $status->name . '.'; + $message = 'Ticket '.$freshTicket->reference.' moved from '.$oldStatusName.' to '.$status->name.'.'; session()->flash('success', $message); $this->refreshBoard(); $this->dispatch('helpdesk-kanban-moved', ticketId: (int) $freshTicket->id, statusId: (int) $freshTicket->status_id); - $this->dispatch('helpdesk:toast', type: 'success', title: 'Ticket moved', message: $message); + $this->dispatch('thredary:toast', type: 'success', title: 'Ticket moved', message: $message); } finally { $this->isMovingTicket = false; } @@ -121,15 +121,15 @@ final class KanbanBoard extends Component $this->board_id = $board->id; - $fresh = $ticket->fresh(['status', 'priority', 'category', 'project', 'team', 'board']) ?? $ticket; + $fresh = $ticket->fresh(['user:id,name,email', 'owner:id,name,email', 'agent:id,name,email', 'status', 'priority', 'category', 'project', 'team', 'board']) ?? $ticket; $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); - $message = 'Ticket ' . $fresh->reference . ' moved from ' . $oldBoardName . ' to ' . $board->name . '.'; + $message = 'Ticket '.$fresh->reference.' moved from '.$oldBoardName.' to '.$board->name.'.'; session()->flash('success', $message); $this->refreshBoard(); $this->dispatch('helpdesk-kanban-moved', ticketId: (int) $fresh->id, statusId: (int) $fresh->status_id); - $this->dispatch('helpdesk:toast', type: 'success', title: 'Board changed', message: $message); + $this->dispatch('thredary:toast', type: 'success', title: 'Board changed', message: $message); } /** @@ -191,7 +191,7 @@ final class KanbanBoard extends Component 'statuses' => $board?->statuses ?? collect(), 'ticketsByStatus' => $this->ticketsByStatus($board), 'refreshNonce' => $this->refreshNonce, - ])->layout('layouts.app', ['title' => __('Helpdesk Kanban')]); + ])->layout('layouts.app'); } /** @@ -234,7 +234,7 @@ final class KanbanBoard extends Component /** * Resolve the selected board from an already-loaded board collection. * - * @param Collection $boards + * @param Collection $boards */ private function resolveSelectedBoard(Collection $boards): ?KanbanBoardModel { diff --git a/Modules/Helpdesk/Livewire/SettingsManager.php b/Modules/Helpdesk/Livewire/SettingsManager.php index 0b21945..dd51da8 100644 --- a/Modules/Helpdesk/Livewire/SettingsManager.php +++ b/Modules/Helpdesk/Livewire/SettingsManager.php @@ -718,7 +718,7 @@ final class SettingsManager extends Component 'tags' => Tag::query()->orderBy('name')->get(), 'checklistTemplates' => ChecklistTemplate::query()->orderBy('name')->get(), 'colourOptions' => Color::options(), - ])->layout('layouts.app', ['title' => __('Helpdesk Settings')]); + ])->layout('layouts.app'); } private function orderedStatuses(): mixed diff --git a/Modules/Helpdesk/Livewire/TicketCreate.php b/Modules/Helpdesk/Livewire/TicketCreate.php index f80648c..e307a5f 100644 --- a/Modules/Helpdesk/Livewire/TicketCreate.php +++ b/Modules/Helpdesk/Livewire/TicketCreate.php @@ -146,7 +146,7 @@ final class TicketCreate extends Component 'projects' => $this->projectOptions(), 'products' => Product::query()->where('is_active', true)->orderBy('name')->get(), 'tags' => Tag::query()->orderBy('name')->get(), - ])->layout('layouts.app', ['title' => __('Create Helpdesk Ticket')]); + ])->layout('layouts.app'); } /** diff --git a/Modules/Helpdesk/Livewire/TicketEdit.php b/Modules/Helpdesk/Livewire/TicketEdit.php index 69551f6..b260faf 100644 --- a/Modules/Helpdesk/Livewire/TicketEdit.php +++ b/Modules/Helpdesk/Livewire/TicketEdit.php @@ -198,7 +198,7 @@ final class TicketEdit extends Component 'products' => Product::query()->where('is_active', true)->orderBy('name')->get(), 'tags' => Tag::query()->orderBy('name')->get(), 'agents' => $this->agentOptions(), - ])->layout('layouts.app', ['title' => __('Edit Helpdesk Ticket')]); + ])->layout('layouts.app'); } /** diff --git a/Modules/Helpdesk/Livewire/TicketIndex.php b/Modules/Helpdesk/Livewire/TicketIndex.php index 6da2a10..33b1835 100644 --- a/Modules/Helpdesk/Livewire/TicketIndex.php +++ b/Modules/Helpdesk/Livewire/TicketIndex.php @@ -105,7 +105,7 @@ final class TicketIndex extends Component 'tickets' => $this->tickets(), 'statuses' => Status::query()->orderBy('sort_order')->get(), 'priorities' => Priority::query()->orderBy('sort_order')->get(), - ])->layout('layouts.app', ['title' => __('Helpdesk Tickets')]); + ])->layout('layouts.app'); } private function resetTicketDeletionState(): void diff --git a/Modules/Helpdesk/Livewire/TicketShow.php b/Modules/Helpdesk/Livewire/TicketShow.php index 4f50039..7f6ff99 100644 --- a/Modules/Helpdesk/Livewire/TicketShow.php +++ b/Modules/Helpdesk/Livewire/TicketShow.php @@ -325,7 +325,7 @@ final class TicketShow extends Component 'priorities' => Priority::query()->orderBy('sort_order')->get(), 'agents' => $this->agentOptions(), 'cannedResponses' => CannedResponse::query()->where('is_active', true)->orderBy('sort_order')->orderBy('name')->get(), - ])->layout('layouts.app', ['title' => __('Helpdesk Ticket')]); + ])->layout('layouts.app'); } private function normaliseNullableInt(mixed $value): ?int diff --git a/Modules/Helpdesk/Models/Ticket.php b/Modules/Helpdesk/Models/Ticket.php index acfd504..b243547 100644 --- a/Modules/Helpdesk/Models/Ticket.php +++ b/Modules/Helpdesk/Models/Ticket.php @@ -27,7 +27,7 @@ final class Ticket extends Model protected $table = 'helpdesk_tickets'; - protected $with = ['user']; + protected $with = ['user', 'owner', 'agent']; /** @var list */ protected $fillable = [ @@ -194,11 +194,11 @@ final class Ticket extends Model /** @param Builder $query @return Builder */ public function scopeVisibleTo(Builder $query, User $user): Builder { - if ($user->isHelpdeskAdmin()) { + if ($user->isAdmin() || (bool) ($user->helpdesk_admin ?? false)) { return $query; } - if ($user->isHelpdeskAgent()) { + if ((method_exists($user, 'isHelpdeskAgent') && $user->isHelpdeskAgent()) || (bool) ($user->helpdesk_agent ?? false)) { return $query->where(static function (Builder $subQuery) use ($user): void { $subQuery->where('agent_id', $user->id)->orWhere('user_id', $user->id); }); diff --git a/Modules/Helpdesk/Policies/TicketPolicy.php b/Modules/Helpdesk/Policies/TicketPolicy.php index 73cd9ab..85a46dc 100644 --- a/Modules/Helpdesk/Policies/TicketPolicy.php +++ b/Modules/Helpdesk/Policies/TicketPolicy.php @@ -16,15 +16,10 @@ final class TicketPolicy public function view(User $user, Ticket $ticket): bool { - if ($this->isHelpdeskAdmin($user)) { + if ($this->isAdmin($user) || $this->isAgent($user)) { return true; } - if ($this->isHelpdeskAgent($user)) { - return (int) $ticket->agent_id === (int) $user->id - || (int) $ticket->user_id === (int) $user->id; - } - return (int) $ticket->user_id === (int) $user->id; } @@ -33,29 +28,13 @@ final class TicketPolicy return true; } - /** - * Helpdesk workflow updates. - * - * Admins can update every Helpdesk ticket. - * - * Agents can update any ticket that is visible to them under the Helpdesk - * visibility rules. This is important for Kanban drag/drop: a card must - * never be displayed as movable and then fail authorization on drop. - * - * Normal requesters cannot change workflow/status/assignment data. - */ public function update(User $user, Ticket $ticket): bool { - if ($this->isHelpdeskAdmin($user)) { + if ($this->isAdmin($user) || $this->isAgent($user)) { return true; } - if (! $this->isHelpdeskAgent($user)) { - return false; - } - - return (int) $ticket->agent_id === (int) $user->id - || (int) $ticket->user_id === (int) $user->id; + return (int) $ticket->user_id === (int) $user->id; } public function comment(User $user, Ticket $ticket): bool @@ -65,36 +44,20 @@ final class TicketPolicy public function delete(User $user, Ticket $ticket): bool { - return $this->isHelpdeskAdmin($user); + return $this->isAdmin($user); } - private function isHelpdeskAdmin(User $user): bool + private function isAdmin(User $user): bool { - if (method_exists($user, 'isHelpdeskAdmin')) { - return $user->isHelpdeskAdmin(); - } - - if ((bool) ($user->helpdesk_admin ?? false)) { - return true; - } - - if (method_exists($user, 'isAdmin')) { - return $user->isAdmin(); - } - - if (method_exists($user, 'isMpbaPlatformAdmin')) { - return $user->isMpbaPlatformAdmin(); - } - - return false; + return (method_exists($user, 'isHelpdeskAdmin') && $user->isHelpdeskAdmin()) + || (bool) ($user->helpdesk_admin ?? false) + || (method_exists($user, 'isAdmin') && $user->isAdmin()); } - private function isHelpdeskAgent(User $user): bool + private function isAgent(User $user): bool { - if (method_exists($user, 'isHelpdeskAgent')) { - return $user->isHelpdeskAgent(); - } - - return (bool) ($user->helpdesk_agent ?? false); + return (method_exists($user, 'isHelpdeskAgent') && $user->isHelpdeskAgent()) + || (bool) ($user->helpdesk_agent ?? false) + || (method_exists($user, 'isAgent') && $user->isAgent()); } } diff --git a/build:helpdesk b/build:helpdesk new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/build:helpdesk @@ -0,0 +1 @@ + diff --git a/package-lock.json b/package-lock.json index f47f994..75f9362 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@fortawesome/fontawesome-free": "^7.0.0", "@laravel/passkeys": "^0.2.0", "@tailwindcss/vite": "^4.1.11", "concurrently": "^10.0.3", @@ -33,6 +34,15 @@ "node": ">=18" } }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.3.1.tgz", + "integrity": "sha512-wmglKKPDIkgV3aWlZzWECCPoGIkYCulzBwxG9+w7rc5BGapZ6cPMpoPOT8k36J0Ni7PPX6c/rsoMWfS4d1MUMg==", + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "engines": { + "node": ">=6" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", diff --git a/package.json b/package.json index 4847df2..7395dd5 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,9 @@ "private": true, "type": "module", "scripts": { - "build": "npm run build:backend && npm run build:helpdesk", - "build:backend": "vite build --config ./vite.config.js", - "build:helpdesk": "npm --prefix Modules/Helpdesk ci && npm --prefix Modules/Helpdesk run build", - "dev": "vite --config ./vite.config.js" + "build": "vite build", + "dev": "vite", + "build:backend": "vite build --config ./vite.config.js" }, "dependencies": { "@laravel/passkeys": "^0.2.0", @@ -14,7 +13,8 @@ "concurrently": "^10.0.3", "laravel-vite-plugin": "^3.1", "tailwindcss": "^4.0.7", - "vite": "^8.0.0" + "vite": "^8.0.0", + "@fortawesome/fontawesome-free": "^7.0.0" }, "optionalDependencies": { "@laravel/multiplex": "^0.4.1", diff --git a/resources/themes/backend/assets/css/app.css b/resources/themes/backend/assets/css/app.css index 3239a57..e4cd6a9 100644 --- a/resources/themes/backend/assets/css/app.css +++ b/resources/themes/backend/assets/css/app.css @@ -1,4 +1,5 @@ @import 'tailwindcss'; +@import '@fortawesome/fontawesome-free/css/all.min.css'; @import '../../../../../vendor/livewire/flux/dist/flux.css'; @import './helpdesk.css'; @@ -14,13 +15,3 @@ --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; } - -@layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentColor); - } -} diff --git a/resources/themes/backend/assets/css/helpdesk.css b/resources/themes/backend/assets/css/helpdesk.css index 16f7afa..dc1f936 100644 --- a/resources/themes/backend/assets/css/helpdesk.css +++ b/resources/themes/backend/assets/css/helpdesk.css @@ -972,3 +972,36 @@ span.flatpickr-weekday { box-shadow: 0 0 0 1000px #09090b inset !important; transition: background-color 9999s ease-in-out 0s; } + + +/* Backend theme integration: keep Core Helpdesk surfaces full-width and dark. */ +.hd-helpdesk-surface { + width: 100%; +} + +.dark .hd-core-settings input:not([type="checkbox"]):not([type="radio"]):not([type="color"]), +.dark .hd-core-settings textarea, +.dark .hd-core-settings select, +.dark .hd-core-settings [data-flux-control] { + background: #2b2b2b !important; + border-color: #4b4b4b !important; + color: #f4f4f5 !important; +} + +.dark .hd-core-settings input::placeholder, +.dark .hd-core-settings textarea::placeholder { + color: #9ca3af !important; +} + +.dark .hd-core-settings [data-flux-field] label, +.dark .hd-core-settings [data-flux-label] { + color: #f4f4f5 !important; +} + +/* Coloured Settings cards remain cards at all common desktop widths. */ +.hd-core-settings > .grid.md\:grid-cols-4.xl\:grid-cols-7 > a { + min-height: 4.6rem; + display: flex; + flex-direction: column; + justify-content: center; +} diff --git a/resources/themes/backend/views/components/helpdesk/colour-badge.blade.php b/resources/themes/backend/views/components/helpdesk/colour-badge.blade.php new file mode 100644 index 0000000..c65e065 --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/colour-badge.blade.php @@ -0,0 +1,16 @@ +@props([ + 'color' => 'gray', + 'label' => null, +]) + +@php + $background = \Modules\Helpdesk\Support\Color::css($color); + $foreground = \Modules\Helpdesk\Support\Color::text($color); +@endphp + +class('inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold shadow-sm ring-1 ring-zinc-900/10') }} + style="background-color: {{ $background }}; color: {{ $foreground }};" +> + {{ $label ?? $slot }} + diff --git a/resources/themes/backend/views/components/helpdesk/colour-select.blade.php b/resources/themes/backend/views/components/helpdesk/colour-select.blade.php new file mode 100644 index 0000000..1385c92 --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/colour-select.blade.php @@ -0,0 +1,74 @@ +@props([ + 'label' => 'Badge colour', + 'model', + 'options' => [], +]) + +@php + $currentValue = data_get($this, $model, 'gray'); + $currentHex = \Modules\Helpdesk\Support\Color::css($currentValue); + $currentText = \Modules\Helpdesk\Support\Color::text($currentValue); + $currentLabel = \Modules\Helpdesk\Support\Color::label($currentValue); +@endphp + +
class('rounded-xl border border-zinc-200 bg-white p-3 shadow-sm dark:border-zinc-700 dark:bg-zinc-950/50') }}> +
+
+ + +
+ + + + {{ $currentLabel }} + +
+ +
+ @foreach ($options as $key => $option) + + @endforeach +
+
+ +
+ + + +
+
+
diff --git a/resources/themes/backend/views/components/helpdesk/date-picker.blade.php b/resources/themes/backend/views/components/helpdesk/date-picker.blade.php new file mode 100644 index 0000000..523076a --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/date-picker.blade.php @@ -0,0 +1,21 @@ +@props(['id', 'model', 'label' => null, 'required' => false, 'placeholder' => 'Select date']) + +
+ @if ($label) + + @endif + +
+ except('value')->merge([ + 'class' => + 'block w-full rounded-xl border border-zinc-700 bg-zinc-950 px-4 py-3 text-sm text-zinc-100 shadow-sm outline-none transition-colors duration-150 placeholder:text-zinc-500 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40', + ]) }} /> +
+
diff --git a/resources/themes/backend/views/components/helpdesk/html-editor.blade.php b/resources/themes/backend/views/components/helpdesk/html-editor.blade.php new file mode 100644 index 0000000..d1d6f76 --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/html-editor.blade.php @@ -0,0 +1,143 @@ +@props([ + 'model' => 'body', + 'label' => 'Details', + 'placeholder' => 'Describe the ticket, include steps to reproduce, expected result, actual result and any useful technical details.', + 'rows' => 10, + 'editorDusk' => null, +]) + +
+ + +
+
+ Spellcheck enabled + + + + + + + + +
+ +
+ + +
+ +

+ Browser spellcheck stays active in the native editable area. Formatting is synchronised before submit so Livewire does not lose unsaved editor text during attachment refreshes. +

+
diff --git a/resources/themes/backend/views/components/helpdesk/layouts/app.blade.php b/resources/themes/backend/views/components/helpdesk/layouts/app.blade.php new file mode 100644 index 0000000..59ce0d3 --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/layouts/app.blade.php @@ -0,0 +1,9 @@ +@props(['title' => null]) + +{{-- + Helpdesk compatibility component. + + Do not call from here: when Laravel resolves this component path first, + that creates an infinite component loop. Delegate directly to the central WMS layout instead. +--}} +@include('layouts.app.sidebar') diff --git a/resources/themes/backend/views/components/helpdesk/lookup-table.blade.php b/resources/themes/backend/views/components/helpdesk/lookup-table.blade.php new file mode 100644 index 0000000..7fbce6d --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/lookup-table.blade.php @@ -0,0 +1,47 @@ +
+ + + + + + + + + + + + + @forelse ($items as $item) + + + + + + + + + @empty + + + + @endforelse + +
NameSlugColourDefaultTicketsActions
{{ $item->name }}{{ $item->slug }} + + + {{ \Illuminate\Support\Str::of(\Modules\Helpdesk\Support\Color::normalise($item->color))->headline() }} + + + + {{ $item->is_default ?? false ? 'Default' : 'No' }} + + {{ $item->tickets_count ?? 0 }} +
+ + +
+
No records yet.
+
diff --git a/resources/themes/backend/views/components/helpdesk/project-table.blade.php b/resources/themes/backend/views/components/helpdesk/project-table.blade.php new file mode 100644 index 0000000..c7af1cb --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/project-table.blade.php @@ -0,0 +1,47 @@ +
+ + + + + + + + + + + + + + @forelse ($items as $item) + + + + + + + + + + @empty + + + + @endforelse + +
ProjectCompanyTeamColourDefaultTicketsActions
{{ $item->title }}{{ $item->company?->name ?? 'Global' }}{{ $item->team?->name ?? 'No team' }} + + + {{ \Illuminate\Support\Str::of(\Modules\Helpdesk\Support\Color::normalise($item->color))->headline() }} + + + + {{ $item->is_default ?? false ? 'Default' : 'No' }} + {{ $item->tickets_count ?? 0 }} +
+
No projects yet.
+
diff --git a/resources/themes/backend/views/components/helpdesk/repository-table.blade.php b/resources/themes/backend/views/components/helpdesk/repository-table.blade.php new file mode 100644 index 0000000..8c590c4 --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/repository-table.blade.php @@ -0,0 +1,36 @@ +
+ + + + + + + + + + + + @forelse ($items as $item) + + + + + + + + @empty + + + + @endforelse + +
NameProviderProjectCommitsActions
{{ $item->name }}{{ strtoupper($item->provider) }}{{ $item->project?->title ?? 'No project' }}{{ $item->commits_count ?? 0 }} +
+
No repositories yet.
+
diff --git a/resources/themes/backend/views/components/helpdesk/settings-section.blade.php b/resources/themes/backend/views/components/helpdesk/settings-section.blade.php new file mode 100644 index 0000000..3074835 --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/settings-section.blade.php @@ -0,0 +1,33 @@ +@props([ + 'title' => 'Settings', + 'description' => '', +]) + +
merge([ + 'class' => 'overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm ring-1 ring-zinc-900/5 dark:border-zinc-700 dark:bg-zinc-900 dark:ring-white/10', + ]) }}> +
+
+
+

Configuration

+

{{ $title }}

+

{{ $description }}

+
+ + + + +
+
+ +
+
+ {{ $form }} +
+
+ +
+ {{ $slot }} +
+
diff --git a/resources/themes/backend/views/components/helpdesk/team-table.blade.php b/resources/themes/backend/views/components/helpdesk/team-table.blade.php new file mode 100644 index 0000000..90a3233 --- /dev/null +++ b/resources/themes/backend/views/components/helpdesk/team-table.blade.php @@ -0,0 +1,45 @@ +
+ + + + + + + + + + + + + @forelse ($items as $item) + + + + + + + + + @empty + + + + @endforelse + +
TeamCompanyColourDefaultTicketsActions
{{ $item->name }}{{ $item->company?->name ?? 'Global' }} + + + {{ \Illuminate\Support\Str::of(\Modules\Helpdesk\Support\Color::normalise($item->color))->headline() }} + + + + {{ $item->is_default ?? false ? 'Default' : 'No' }} + {{ $item->tickets_count ?? 0 }} +
+
No teams yet.
+
diff --git a/resources/themes/backend/views/modules/helpdesk/dashboard/card.blade.php b/resources/themes/backend/views/modules/helpdesk/dashboard/card.blade.php new file mode 100644 index 0000000..3764821 --- /dev/null +++ b/resources/themes/backend/views/modules/helpdesk/dashboard/card.blade.php @@ -0,0 +1 @@ + diff --git a/resources/themes/backend/views/modules/helpdesk/livewire/dashboard-widget.blade.php b/resources/themes/backend/views/modules/helpdesk/livewire/dashboard-widget.blade.php index 068d2c9..3e13ab5 100644 --- a/resources/themes/backend/views/modules/helpdesk/livewire/dashboard-widget.blade.php +++ b/resources/themes/backend/views/modules/helpdesk/livewire/dashboard-widget.blade.php @@ -1,124 +1,196 @@ -
-
-
- {{ __('Helpdesk') }} - {{ __('Support operations, workload and recent activity.') }} -
- -
- - {{ __('Tickets') }} - - - {{ __('Kanban') }} - - - {{ __('New ticket') }} - -
-
- -
-
- {{ __('Total tickets') }} -
{{ $totalCount }}
- {{ __('All tickets visible to you') }} -
- -
- {{ __('Open') }} -
{{ $openCount }}
- {{ $openPercent }}% {{ __('of total') }} -
- -
- {{ __('Closed') }} -
{{ $closedCount }}
- {{ $closedPercent }}% {{ __('resolved') }} -
- -
- {{ __('High priority') }} -
{{ $criticalCount }}
- {{ __('Critical, urgent or high') }} -
-
- -
-
-
+
+ Total tickets Open tickets Closed tickets +
+
+
- {{ __('Recent tickets') }} - {{ __('Latest support activity') }} +
Total tickets
+
{{ $totalCount }}
+
+
+
- - {{ __('View all') }} -
+
+
+
+
-
+
+
+
+
Open tickets
+
{{ $openCount }}
+
+
+ +
+
+
+
+
+
+ +
+
+
+
Closed tickets
+
{{ $closedCount }}
+
+
+ +
+
+
+
+
+
+ +
+
+
+
High priority
+
{{ $criticalCount }}
+
+
+ +
+
+
Open urgent/high/critical workload.
+
+
+ +
+ {{-- Recent Tickets End--}} +
-
-
- {{ __('Recent Git activity') }} - {{ __('Commits linked to Helpdesk') }} -
+
+
+
+
+

SLA overview

+

Closure rate and open workload.

+
+ {{ $closedPercent }}% closed +
-
- @forelse ($recentCommits as $commit) -
-
- {{ $commit->repository?->name ?? __('Repository') }} -
-
- {{ $commit->message ?? __('Commit') }} -
-
- @if ($commit->sha) - {{ substr($commit->sha, 0, 8) }} - @endif - @if ($commit->committed_at) - {{ $commit->committed_at->diffForHumans() }} - @endif +
+
+
+
+
{{ $closedPercent }}%
+
closed
- @empty -
- {{ __('No Git activity yet.') }} +
+
+
Closed{{ $closedPercent }}%
+
+
+
+
Open{{ $openPercent }}%
+
+
- @endforelse +
-
+ +
+ +
+ + + + +
+

Recent commits

+

Latest linked Git activity

+
+
+ + + + +
+ + + +
+ @forelse ($recentCommits as $commit) +
+
+ {{ $commit->short_hash }} · {{ $commit->repository?->name }} +
+
+ {{ $commit->message }} +
+
+ @empty +
+ No commits tracked yet. +
+ @endforelse +
+
+
diff --git a/resources/themes/backend/views/modules/helpdesk/livewire/settings-manager.blade.php b/resources/themes/backend/views/modules/helpdesk/livewire/settings-manager.blade.php index f5f7560..f013f84 100644 --- a/resources/themes/backend/views/modules/helpdesk/livewire/settings-manager.blade.php +++ b/resources/themes/backend/views/modules/helpdesk/livewire/settings-manager.blade.php @@ -1,68 +1,952 @@ -
-
-
- {{ __('Helpdesk Settings') }} - {{ __('Manage statuses, priorities, categories, teams, projects and Helpdesk configuration.') }} +@php + /** + * Keep the settings view safe during rolling module upgrades or Livewire navigate + * hydration where the compiled Blade view may be newer than the loaded component. + */ + $statuses = collect($statuses ?? []); + $priorities = collect($priorities ?? []); + $categories = collect($categories ?? []); + $teams = collect($teams ?? []); + $projects = collect($projects ?? []); + $boards = collect($boards ?? []); + $repositories = collect($repositories ?? []); + $workflowTemplates = collect($workflowTemplates ?? []); + $cannedResponses = collect($cannedResponses ?? []); + $slaPolicies = collect($slaPolicies ?? []); + $automationRules = collect($automationRules ?? []); + $checklistTemplates = collect($checklistTemplates ?? []); + $tags = collect($tags ?? []); + $products = collect($products ?? []); +@endphp + +
+
+
+
+
+

Helpdesk administration +

+

Support configuration

+

Manage lookup data, workflow messages, canned + responses, SLA policies, automation rules, checklists, tags and product/service linkage.

+
+ + +
- {{ __('Back to Helpdesk') }}
+
+ @foreach ([ + 'statuses' => ['label' => 'Statuses', 'count' => $statuses->count()], + 'priorities' => ['label' => 'Priorities', 'count' => $priorities->count()], + 'categories' => ['label' => 'Categories', 'count' => $categories->count()], + 'teams' => ['label' => 'Teams', 'count' => $teams->count()], + 'projects' => ['label' => 'Projects', 'count' => $projects->count()], + 'boards' => ['label' => 'Boards', 'count' => $boards->count()], + 'git' => ['label' => 'Git', 'count' => $repositories->count()], + 'workflow' => ['label' => 'Workflow', 'count' => $workflowTemplates->count()], + 'canned' => ['label' => 'Canned', 'count' => $cannedResponses->count()], + 'sla' => ['label' => 'SLA', 'count' => $slaPolicies->count()], + 'automation' => ['label' => 'Rules', 'count' => $automationRules->count()], + 'checklists' => ['label' => 'Checks', 'count' => $checklistTemplates->count()], + 'tags' => ['label' => 'Tags', 'count' => $tags->count()], + 'products' => ['label' => 'Products', 'count' => $products->count()], + ] as $key => $item) + @php + $sectionPalette = match ($key) { + 'statuses' => ['border' => '#38bdf8', 'bg' => 'rgba(14, 165, 233, .22)', 'active' => '#0284c7'], + 'priorities' => ['border' => '#f87171', 'bg' => 'rgba(239, 68, 68, .22)', 'active' => '#dc2626'], + 'categories' => ['border' => '#818cf8', 'bg' => 'rgba(99, 102, 241, .22)', 'active' => '#4f46e5'], + 'teams' => ['border' => '#60a5fa', 'bg' => 'rgba(37, 99, 235, .22)', 'active' => '#2563eb'], + 'projects' => ['border' => '#22d3ee', 'bg' => 'rgba(8, 145, 178, .22)', 'active' => '#0891b2'], + 'boards' => ['border' => '#fbbf24', 'bg' => 'rgba(245, 158, 11, .24)', 'active' => '#f59e0b'], + 'git' => ['border' => '#94a3b8', 'bg' => 'rgba(71, 85, 105, .28)', 'active' => '#475569'], + 'workflow' => ['border' => '#34d399', 'bg' => 'rgba(5, 150, 105, .22)', 'active' => '#059669'], + 'canned' => ['border' => '#2dd4bf', 'bg' => 'rgba(13, 148, 136, .22)', 'active' => '#0d9488'], + 'sla' => ['border' => '#fb923c', 'bg' => 'rgba(234, 88, 12, .22)', 'active' => '#ea580c'], + 'automation' => ['border' => '#c084fc', 'bg' => 'rgba(147, 51, 234, .22)', 'active' => '#9333ea'], + 'checklists' => ['border' => '#a3e635', 'bg' => 'rgba(101, 163, 13, .22)', 'active' => '#65a30d'], + 'tags' => ['border' => '#f472b6', 'bg' => 'rgba(219, 39, 119, .22)', 'active' => '#db2777'], + 'products' => ['border' => '#a78bfa', 'bg' => 'rgba(124, 58, 237, .22)', 'active' => '#7c3aed'], + default => ['border' => '#71717a', 'bg' => 'rgba(63, 63, 70, .22)', 'active' => '#3f3f46'], + }; - @php - $sections = [ - 'statuses' => 'Statuses', - 'priorities' => 'Priorities', - 'categories' => 'Categories', - 'teams' => 'Teams', - 'projects' => 'Projects', - 'boards' => 'Boards', - 'repositories' => 'Repositories', - 'workflows' => 'Workflows', - 'canned' => 'Canned responses', - 'sla' => 'SLA policies', - 'automation' => 'Automation', - 'products' => 'Products', - 'tags' => 'Tags', - 'checklists' => 'Checklists', - ]; - @endphp - -
- @foreach ($sections as $key => $label) - + $sectionStyle = + $section === $key + ? 'border-color: ' . + $sectionPalette['border'] . + '; background-color: ' . + $sectionPalette['active'] . + '; color: #ffffff; box-shadow: 0 16px 36px rgba(0,0,0,.32);' + : 'border-color: ' . + $sectionPalette['border'] . + '; background-color: ' . + $sectionPalette['bg'] . + '; color: #ffffff; box-shadow: 0 10px 24px rgba(0,0,0,.20);'; + @endphp + +
{{ $item['label'] }}
+
{{ $item['count'] }}
+
@endforeach
@if ($section === 'statuses') - - @elseif ($section === 'priorities') - - @elseif ($section === 'categories') - - @elseif ($section === 'teams') - - @elseif ($section === 'projects') - - @elseif ($section === 'boards') - - @elseif ($section === 'repositories') - - @elseif ($section === 'workflows') - - @elseif ($section === 'canned') - - @elseif ($section === 'sla') - - @elseif ($section === 'automation') - - @elseif ($section === 'products') - - @elseif ($section === 'tags') - - @elseif ($section === 'checklists') - + + +
+
+ +
+
+ +
+ +
+ +
+ + +
+ Save status + + Reset + +
+ +
+ +
@endif + + @if ($section === 'priorities') + + +
+
+ +
+
+ +
+ +
+ +
+ +
+ Save priority + + Reset + +
+ +
+ +
+ @endif + + @if ($section === 'categories') + + +
+
+ + + @foreach ($companies as $company) + + @endforeach + +
+
+ +
+
+ +
+ +
+ +
+ +
+ Save + category + Reset + +
+ +
+ +
+ @endif + + @if ($section === 'teams') + + +
+
+ + + @foreach ($companies as $company) + + @endforeach + +
+
+ +
+
+ +
+ +
+ +
+ +
+ Save team + + Reset + +
+ +
+ +
+ @endif + + @if ($section === 'projects') + + +
+
+ + + @foreach ($companies as $company) + + @endforeach + +
+
+ + + @foreach ($teams as $team) + + @endforeach + +
+
+ +
+
+ +
+ +
+ +
+ +
+ Save project + + Reset + +
+ +
+ +
+ @endif + + + @if ($section === 'boards') + + +
+ + + + + + @foreach ($boardOwners as $owner) + + @endforeach + + +
+ +
+
+ +
+ @foreach ($statuses as $status) + + @endforeach +
+
+
+ Save board + + Reset + +
+ +
+ +
+ + + + + + + + + + + + + @forelse ($boards as $boardItem) + + + + + + + + + @empty + + + + @endforelse + +
BoardOwnerColumnsTicketsDefaultActions
+
{{ $boardItem->name }} +
+
{{ $boardItem->slug }}
+
+ {{ $boardItem->owner?->name ?? 'Unassigned' }} +
+ @foreach ($boardItem->statuses as $status) + {{ $status->name }} + @endforeach +
+
{{ $boardItem->tickets_count ?? 0 }} + + {{ $boardItem->is_default ? 'Default' : 'No' }} + + + Edit + Delete + +
No Kanban boards yet. +
+
+
+ @endif + + @if ($section === 'git') + + +
+ + + @foreach ($companies as $company) + + @endforeach + + + + @foreach ($projects as $project) + + @endforeach + + + + + +
+ +
+
+ Save + repository + Reset + +
+ +
+ +
+ @endif + + + @if ($section === 'workflow') + + +
+ + + + + + + + + + + + + + + + +
+ +
+
+ +
+
+ +
+
+ Save + workflow message + Reset + +
+ +
+ +
+ + + + + + + + + + + + @forelse ($workflowTemplates as $template) + + + + + + + + @empty + + + + @endforelse + +
NameEventRecipientActiveActions
+ {{ $template->name }}{{ $template->event }}{{ $template->to_role }} + + {{ $template->is_active ? 'Active' : 'Inactive' }} + + + Edit + Delete + +
No workflow messages + yet.
+
+
+ @endif + + + @if ($section === 'canned') + + +
+ + + + + + @foreach ($categories as $category) + + @endforeach + + + + @foreach ($teams as $team) + + @endforeach + + +
+ +
+
+ +
+
+ Save canned + response + Reset + +
+ +
+
+ @forelse ($cannedResponses as $response) +
+
+
+

{{ $response->name }}

+

{{ $response->category?->name ?? 'Any category' }} · + {{ $response->team?->name ?? 'Any team' }}

+
+ + {{ $response->is_active ? 'Active' : 'Inactive' }} +
+

+ {{ strip_tags($response->body) }}

+
+ Edit + + Delete + +
+
+ @empty +
+ No canned responses yet.
+ @endforelse +
+
+ @endif + + @if ($section === 'sla') + + +
+ + + + + @foreach ($priorities as $priority) + + @endforeach + + + + @foreach ($categories as $category) + + @endforeach + + + + + + +
+ Save SLA + policy + Reset + +
+ +
+
+ + + + + + + + + + + @forelse ($slaPolicies as $policy) + + + + + + @empty + + + @endforelse + +
PolicyTargetsApplies toActions
{{ $policy->name }} @if ($policy->is_default) + Default + @endif + {{ $policy->first_response_minutes }}m response · + {{ $policy->resolution_minutes }}m close{{ $policy->priority?->name ?? 'Any priority' }} · + {{ $policy->category?->name ?? 'Any category' }} + + Edit + Delete + +
No SLA policies yet. +
+
+
+ @endif + + @if ($section === 'automation') + + +
+ + + + + + + + + @foreach ($priorities as $priority) + + @endforeach + + + + @foreach ($categories as $category) + + @endforeach + + + + @foreach ($teams as $team) + + @endforeach + + + + @foreach ($projects as $project) + + @endforeach + + + + @foreach ($statuses as $status) + + @endforeach + + +
+ Save + automation rule + Reset + +
+ +
+
+ @forelse ($automationRules as $rule) +
+

{{ $rule->name }}

+

Sender: {{ $rule->match_sender ?: 'Any' }} · Subject: + {{ $rule->match_subject ?: 'Any' }}

+
+ Edit + + Delete + +
+
@empty
+ No automation rules yet.
+ @endforelse +
+
+ @endif + + @if ($section === 'checklists') + + +
+ + + + + @foreach ($categories as $category) + + @endforeach + + + + @foreach ($projects as $project) + + @endforeach + + +
+ +
+
+ Save + checklist + Reset + +
+ +
+
+ @forelse ($checklistTemplates as $template) +
+

{{ $template->name }}

+

{{ count($template->items ?? []) }} items

+
+ Edit + + Delete + +
+
@empty
+ No checklist templates yet.
+ @endforelse +
+
+ @endif + + @if ($section === 'tags') + + +
+
+ +
+
+ +
+ +
+ Save tag + + Reset + +
+ +
+
+ @forelse ($tags as $tag) +
+ {{ $tag->name }} +
+ Edit + + Delete + +
+
@empty
+ No tags yet.
+ @endforelse +
+
+ @endif + + @if ($section === 'products') + + +
+ + + + + +
+ +
+
+ Save + product/service + Reset + +
+ +
+
+ @forelse ($products as $product) +
+

{{ $product->name }}

+

{{ ucfirst($product->type) }} · + {{ $product->version ?: 'No version' }} · {{ $product->tickets_count }} tickets

+
+ Edit + + Delete + +
+
@empty
+ No products or services yet.
+ @endforelse +
+
+ @endif +
diff --git a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-create.blade.php b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-create.blade.php index cf0d17d..5b7efcd 100644 --- a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-create.blade.php +++ b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-create.blade.php @@ -1,76 +1,405 @@ -
-
-
- {{ __('Create ticket') }} - {{ __('Create a new Helpdesk request.') }} -
- {{ __('Back to tickets') }} -
+@php + $helpdeskAttachmentAccept = collect(config('helpdesk.allowed_extensions', config('helpdesk.allowed_mimes', []))) + ->filter(fn ($extension) => is_string($extension) && trim($extension) !== '') + ->map(fn ($extension) => '.' . strtolower(ltrim(trim($extension), '.'))) + ->unique() + ->implode(','); +@endphp +
+ @include('modules.helpdesk.partials.page-header', [ + 'eyebrow' => 'Helpdesk', + 'title' => 'Create ticket', + 'subtitle' => + 'Raise a new support request with priority, product/service, project, category, team and full details.', + ]) -
-
-
- - @error('subject')

{{ $message }}

@enderror + +
+
+

Ticket details

+

+ Mandatory fields are marked with *. + Use the searchable lists to keep the ticket correctly routed. +

-
- - @error('body')

{{ $message }}

@enderror +
+
+ + + + + @error('subject') +

{{ $message }}

+ @enderror +
+ + @if ($companies->isNotEmpty()) +
+ + +
+ +
+ + @error('company_id') +

{{ $message }}

+ @enderror +
+ @endif + +
+ + +
+ +
+ + @error('board_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('priority_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('category_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('project_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('team_id') +

{{ $message }}

+ @enderror +
+ +
+
+
+ + Tags + + + + Optional · select all that apply + +
+ +
+ @forelse ($tags as $tag) + + @empty +
+ No tags are available. +
+ @endforelse +
+ + @error('tag_ids') +

{{ $message }}

+ @enderror + + @error('tag_ids.*') +

{{ $message }}

+ @enderror +
+
+ +
+ + + @error('due_at') +

{{ $message }}

+ @enderror +
+ +
+ + +

+ + Rich formatting is available. Browser spelling suggestions appear by right-clicking or Ctrl-clicking red-underlined words. +

+
+ +
+
+
+
+ + +

+ Files are uploaded to Livewire temporary storage first. They are attached permanently only after the ticket is created. +

+
+ + +
+ + +
+ + + + +
+ @if (count($attachments) > 0) +
+ @foreach ($attachments as $attachment) +
+ + {{ $attachment->getClientOriginalName() }} + {{ number_format($attachment->getSize() / 1024, 1) }} KB +
+ @endforeach +
+ @endif + +
+
+ Uploading attachments... + +
+
+
+
+
+ + @error('attachments') +

{{ $message }}

+ @enderror + + @error('attachments.*') +

{{ $message }}

+ @enderror +
+
- - - @foreach ($boards as $item)@endforeach - +
+ - - - @foreach ($priorities as $item)@endforeach - + + + Cancel + - - - @foreach ($categories as $item)@endforeach - - - - - @foreach ($teams as $item)@endforeach - - - - - @foreach ($projects as $item)@endforeach - - - - - @foreach ($products as $item)@endforeach - - - - -
- - +
- -
- - - @error('attachments.*')

{{ $message }}

@enderror -
-
- -
- {{ __('Clear') }} - - {{ __('Create ticket') }} - {{ __('Saving…') }} -
diff --git a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-edit.blade.php b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-edit.blade.php index 71b9f58..ac50f9c 100644 --- a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-edit.blade.php +++ b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-edit.blade.php @@ -1,59 +1,428 @@ +@php + $helpdeskAttachmentAccept = collect(config('helpdesk.allowed_extensions', config('helpdesk.allowed_mimes', []))) + ->filter(fn ($extension) => is_string($extension) && trim($extension) !== '') + ->map(fn ($extension) => '.' . strtolower(ltrim(trim($extension), '.'))) + ->unique() + ->implode(','); +@endphp
-
-
- {{ __('Edit :reference', ['reference' => $ticket->reference]) }} - {{ __('Update ticket details and assignment.') }} -
- {{ __('Cancel') }} -
+ @include('modules.helpdesk.partials.page-header', [ + 'eyebrow' => 'Helpdesk', + 'title' => 'Edit Ticket', + 'subtitle' => + 'Update ownership, assignment, workflow status, product/service, project, team, category, priority and ticket details.', + ]) -
-
-
-
+ +
+
+
+
+
+

+ {{ $ticket->reference }} · Ticket controls +

- @foreach ($statuses as $item)@endforeach - @foreach ($priorities as $item)@endforeach - - @foreach ($boards as $item)@endforeach - @foreach ($agents as $item)@endforeach - - @foreach ($categories as $item)@endforeach - @foreach ($teams as $item)@endforeach - - @foreach ($projects as $item)@endforeach - @foreach ($products as $item)@endforeach - - - -
- - -
- -
- - -
- - @if ($ticket->attachments->isNotEmpty()) -
- {{ __('Existing attachments') }} - @foreach ($ticket->attachments as $attachment) -
- {{ $attachment->original_name ?? $attachment->filename ?? __('Attachment') }} - {{ __('Remove') }} + + + Owner: + {{ $ticket->owner?->name ?? 'Unknown' }} +
- @endforeach -
- @endif -
-
- {{ __('Clear') }} - {{ __('Save changes') }} +

+ Mandatory fields are marked with *. + Assignment and status updates trigger configured workflow messages. +

+
+ + + Back to ticket + +
+
+ +
+
+ + + + + @error('subject') +

{{ $message }}

+ @enderror +
+ + @if ($companies->isNotEmpty()) +
+ + +
+ +
+ + @error('company_id') +

{{ $message }}

+ @enderror +
+ @endif + +
+ + +
+ +
+ + @error('board_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('status_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('priority_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('agent_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('category_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('project_id') +

{{ $message }}

+ @enderror +
+ +
+ + +
+ +
+ + @error('team_id') +

{{ $message }}

+ @enderror +
+ +
+
+
+ + Tags + + + + Optional · select all that apply + +
+ +
+ @forelse ($tags as $tag) + + @empty +
+ No tags are available. +
+ @endforelse +
+ + @error('tag_ids') +

{{ $message }}

+ @enderror + + @error('tag_ids.*') +

{{ $message }}

+ @enderror +
+
+ +
+ + + @error('due_at') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('body') +

{{ $message }}

+ @enderror +
+ +
+
+
+
+
+ + +

+ Add screenshots, documents, logs or supporting files to help resolve the ticket. +

+
+ + +
+
+ +
+ + + +
+ + Uploading attachments... +
+ + @if ($ticket->attachments->isNotEmpty()) +
+
+

+ + + + Existing attachments +

+ + + + {{ $ticket->attachments->count() }} file(s) + +
+ + @foreach ($ticket->attachments as $attachment) + + @endforeach +
+ @endif + + @if ($attachments !== []) +
+ {{ count($attachments) }} new attachment(s) selected. Save the ticket to attach + them. +
+ @endif + + @error('attachments') +

{{ $message }}

+ @enderror + + @error('attachments.*') +

{{ $message }}

+ @enderror +
+
+
+
+ +
+ + + + Cancel + + + +
diff --git a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php index b6656d8..f8acef6 100644 --- a/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php +++ b/resources/themes/backend/views/modules/helpdesk/livewire/ticket-index.blade.php @@ -1,202 +1,217 @@ -
-
-
- {{ __('Tickets') }} - {{ __('Manage Helpdesk tickets, assignments and status.') }} -
- -
- - {{ __('Kanban') }} - - - - {{ __('New ticket') }} - +
+
+
+
+
+

Ticket management

+

Active tickets

+

Search, triage and manage support requests across + companies, projects, teams, priorities and statuses.

+
+ +
-
- +
+
+ - - - @foreach ($statuses as $statusOption) - - @endforeach - + + + + + - - - @foreach ($priorities as $priorityOption) - - @endforeach - + + + @foreach ($statuses as $status) + + @endforeach + - - - - - + + + @foreach ($priorities as $priority) + + @endforeach + +
-
+
+
+ Refreshing tickets... +
+
- - +
+ - - - - - - + + + + + + + + - @forelse ($tickets as $ticket) - - + - - - - - - - + + - @empty - + @endforelse
- {{ __('Reference') }} - - {{ __('Subject') }} - - {{ __('Status') }} - - {{ __('Priority') }} - - {{ __('Agent') }} - - {{ __('Actions') }} - ReferenceSubjectStatusPriorityAgentFilesLast activityActions
- +
+ {{ $ticket->reference }} -
- - {{ $ticket->subject }} - - -
- @if ($ticket->category) - {{ $ticket->category->name }} - @endif - - @if ($ticket->project) - {{ $ticket->project->title }} - @endif - - @if ($ticket->attachments_count) - {{ trans_choice(':count attachment|:count attachments', $ticket->attachments_count, ['count' => $ticket->attachments_count]) }} - @endif -
+
{{ $ticket->subject }}
+
+ {{ $ticket->category?->name ?? 'No category' }} + @if ($ticket->project) + · {{ $ticket->project->title }} + @endif + @if ($ticket->team) + · {{ $ticket->team->name }} + @endif
- {{ $ticket->status?->name ?? '—' }} + + + {{ $ticket->status?->name ?? 'No status' }} + - {{ $ticket->priority?->name ?? '—' }} + + + {{ $ticket->priority?->name ?? 'No priority' }} + - {{ $ticket->agent_name ?? '—' }} + + {{ $ticket->agent_name ?? 'Unassigned' }} + + @if (($ticket->attachments_count ?? 0) > 0) + + + {{ $ticket->attachments_count }} + attachment{{ $ticket->attachments_count === 1 ? '' : 's' }} + + @else + + + No attachments + + @endif + + {{ $ticket->last_activity_at?->diffForHumans() ?? $ticket->created_at->diffForHumans() }} -
- - {{ __('View') }} - - - @can('update', $ticket) - - {{ __('Edit') }} - - @endcan - - @can('delete', $ticket) - - {{ __('Delete') }} - - @endcan -
+ @can('delete', $ticket) + + @endcan
- {{ __('No tickets found') }} - - {{ __('Try changing the filters or create a new ticket.') }} - - No tickets matched your + filters.
- @if ($tickets->hasPages()) -
- {{ $tickets->links() }} -
- @endif +
+ {{ $tickets->links() }} +
@if ($deleteModalOpen) -
-
- {{ __('Delete ticket?') }} +