From fc3371ea777c8f94ae9a49f6d715b9730647c988 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Tue, 16 Jun 2026 20:02:18 +0100 Subject: [PATCH] fixe duploads and attachments now with colors tab and enhancements --- .../Controllers/TicketControlsController.php | 161 +++++++++++ Modules/Helpdesk/Livewire/TicketEdit.php | 6 +- Modules/Helpdesk/Livewire/TicketShow.php | 50 +--- .../views/livewire/ticket-show.blade.php | 50 ++-- Modules/Helpdesk/Routes/web.php | 4 + Modules/Helpdesk/changelog.yaml | 14 + Modules/Helpdesk/module.json | 2 +- .../Controllers/TicketControlsController.php | 161 +++++++++++ .../views/livewire/ticket-show.blade.php | 268 ++++++++++++++++++ Modules/Routes/web.php | 35 +++ Modules/changelog.yaml | 192 +++++++++++++ Modules/module.json | 26 ++ 12 files changed, 900 insertions(+), 69 deletions(-) create mode 100644 Modules/Helpdesk/Http/Controllers/TicketControlsController.php create mode 100644 Modules/Http/Controllers/TicketControlsController.php create mode 100644 Modules/Resources/views/livewire/ticket-show.blade.php create mode 100644 Modules/Routes/web.php create mode 100644 Modules/changelog.yaml create mode 100644 Modules/module.json diff --git a/Modules/Helpdesk/Http/Controllers/TicketControlsController.php b/Modules/Helpdesk/Http/Controllers/TicketControlsController.php new file mode 100644 index 00000000..00becdfd --- /dev/null +++ b/Modules/Helpdesk/Http/Controllers/TicketControlsController.php @@ -0,0 +1,161 @@ +validate([ + 'agent_id' => [ + 'nullable', + 'integer', + Rule::exists('users', 'id')->where(function ($query) use ($ticket): void { + if ($ticket->company_id !== null && Schema::hasColumn('users', 'company_id')) { + $query->where(function ($query) use ($ticket): void { + $query->where('company_id', $ticket->company_id) + ->orWhereNull('company_id'); + }); + } + }), + ], + ]); + + $agentId = $this->nullableInt($validated['agent_id'] ?? null); + + $this->updateTicket($ticket, [ + 'agent_id' => $agentId, + 'last_activity_at' => now(), + ]); + + $fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); + + return redirect() + ->route('helpdesk.tickets.show', $fresh) + ->with('ticket-action-updated', $agentId === null + ? 'Ticket is now unassigned.' + : 'Ticket assigned to '.($fresh->agent?->name ?? 'selected agent').'.'); + } + + /** + * Persist the selected ticket status from the ticket detail controls. + */ + public function updateStatus(Request $request, Ticket $ticket, WorkflowNotificationService $workflow, SlaService $sla): RedirectResponse + { + Gate::authorize('update', $ticket); + + $validated = $request->validate([ + 'status_id' => ['required', 'integer', Rule::exists('helpdesk_statuses', 'id')], + ]); + + $status = Status::query()->findOrFail((int) $validated['status_id']); + + $this->updateTicket($ticket, [ + 'status_id' => $status->id, + 'completed_at' => $status->is_closed ? now() : null, + 'last_activity_at' => now(), + ]); + + $this->ensureBoardContainsStatus($ticket, (int) $status->id); + + $fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + + if ($status->is_closed) { + $sla->recordClosure($fresh); + } + + $workflow->dispatch($fresh, $status->is_closed ? 'ticket.closed' : 'ticket.updated', Auth::user()); + + return redirect() + ->route('helpdesk.tickets.show', $fresh) + ->with('ticket-action-updated', 'Status updated to '.($fresh->status?->name ?? 'selected status').'.'); + } + + /** + * Persist the selected ticket priority from the ticket detail controls. + */ + public function updatePriority(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse + { + Gate::authorize('update', $ticket); + + $validated = $request->validate([ + 'priority_id' => ['required', 'integer', Rule::exists('helpdesk_priorities', 'id')], + ]); + + $this->updateTicket($ticket, [ + 'priority_id' => (int) $validated['priority_id'], + 'last_activity_at' => now(), + ]); + + $fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); + + return redirect() + ->route('helpdesk.tickets.show', $fresh) + ->with('ticket-action-updated', 'Priority updated to '.($fresh->priority?->name ?? 'selected priority').'.'); + } + + /** + * Apply a direct guarded ticket update without relying on Livewire hydration. + * + * @param array $updates + */ + private function updateTicket(Ticket $ticket, array $updates): void + { + if (Schema::hasColumn('helpdesk_tickets', 'updated_at')) { + $updates['updated_at'] = now(); + } + + DB::table('helpdesk_tickets') + ->where('id', $ticket->id) + ->update($updates); + } + + /** + * Keep the active Kanban board usable when a ticket is moved to a status not yet assigned to the board. + */ + private function ensureBoardContainsStatus(Ticket $ticket, int $statusId): void + { + if ($ticket->board_id === null || ! Schema::hasTable('helpdesk_kanban_board_statuses')) { + return; + } + + DB::table('helpdesk_kanban_board_statuses')->updateOrInsert( + ['board_id' => $ticket->board_id, 'status_id' => $statusId], + ['updated_at' => now(), 'created_at' => now()], + ); + } + + /** + * Convert form select values to nullable integers. + */ + private function nullableInt(mixed $value): ?int + { + if ($value === null || $value === '' || $value === '0' || $value === 0) { + return null; + } + + return (int) $value; + } +} diff --git a/Modules/Helpdesk/Livewire/TicketEdit.php b/Modules/Helpdesk/Livewire/TicketEdit.php index 67c84b17..e7391ec6 100644 --- a/Modules/Helpdesk/Livewire/TicketEdit.php +++ b/Modules/Helpdesk/Livewire/TicketEdit.php @@ -233,7 +233,7 @@ final class TicketEdit extends Component ->orWhereNull('company_id'); }) ->when($this->agent_id !== null, function ($query): void { - $query->orWhereKey($this->agent_id); + $query->orWhere('id', $this->agent_id); }) ->get(); @@ -248,8 +248,8 @@ final class TicketEdit extends Component private function freshTicket(Ticket $ticket): Ticket { return $ticket->fresh([ - 'owner', - 'agent', + 'owner.company', + 'agent.company', 'status', 'priority', 'category', diff --git a/Modules/Helpdesk/Livewire/TicketShow.php b/Modules/Helpdesk/Livewire/TicketShow.php index 9df52792..28907e8e 100644 --- a/Modules/Helpdesk/Livewire/TicketShow.php +++ b/Modules/Helpdesk/Livewire/TicketShow.php @@ -41,11 +41,11 @@ final class TicketShow extends Component /** @var array */ public array $attachments = []; - public int|string|null $status_id = null; + public ?int $status_id = null; - public int|string|null $priority_id = null; + public ?int $priority_id = null; - public int|string|null $agent_id = null; + public ?int $agent_id = null; public ?int $reply_parent_id = null; @@ -225,7 +225,10 @@ final class TicketShow extends Component $agentId = $this->normaliseNullableInt($this->agent_id); - $this->validateAssignableAgent($agentId); + 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.'); @@ -279,8 +282,8 @@ final class TicketShow extends Component private function freshTicket(Ticket $ticket): Ticket { $ticket = $ticket->fresh([ - 'owner', - 'agent', + 'owner.company', + 'agent.company', 'status', 'priority', 'category', @@ -307,39 +310,6 @@ final class TicketShow extends Component return $ticket; } - /** - * Validate that the selected agent exists and belongs to this ticket's company context. - */ - private function validateAssignableAgent(?int $agentId): void - { - if ($agentId === null) { - return; - } - - $rule = Rule::exists('users', 'id'); - - if ($this->ticket->company_id !== null && Schema::hasColumn('users', 'company_id')) { - $ticketCompanyId = (int) $this->ticket->company_id; - $currentAgentId = $this->ticket->agent_id === null ? null : (int) $this->ticket->agent_id; - - $rule->where(function ($query) use ($ticketCompanyId, $currentAgentId): void { - $query->where(function ($tenantQuery) use ($ticketCompanyId): void { - $tenantQuery->where('company_id', $ticketCompanyId) - ->orWhereNull('company_id'); - }); - - if ($currentAgentId !== null) { - $query->orWhere('id', $currentAgentId); - } - }); - } - - validator( - ['agent_id' => $agentId], - ['agent_id' => ['nullable', 'integer', $rule]], - )->validate(); - } - /** * Return assignable users for this ticket. * @@ -365,7 +335,7 @@ final class TicketShow extends Component ->orWhereNull('company_id'); }) ->when($this->agent_id !== null, function ($query): void { - $query->orWhereKey($this->agent_id); + $query->orWhere('id', $this->agent_id); }) ->get(); diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php index b198ff27..592992eb 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php @@ -202,10 +202,10 @@ @can('update', $ticket)
-
+

Ticket controls

-

Assignment, status and priority

-

Choose a value, then save the specific change.

+

Assignment, status and priority

+

Use the explicit update buttons below to save each change.

@if (session('ticket-action-updated')) @@ -214,53 +214,53 @@
@endif -
+
+ @csrf - + @forelse ($agents as $agent) - + @empty @endforelse @error('agent_id')

{{ $message }}

@enderror - -
+ -
+
+ @csrf - @foreach ($statuses as $status) - + @endforeach @error('status_id')

{{ $message }}

@enderror - -
+ -
+
+ @csrf - @foreach ($priorities as $priority) - + @endforeach @error('priority_id')

{{ $message }}

@enderror - -
+
@endcan diff --git a/Modules/Helpdesk/Routes/web.php b/Modules/Helpdesk/Routes/web.php index 037fd28f..8d61de36 100644 --- a/Modules/Helpdesk/Routes/web.php +++ b/Modules/Helpdesk/Routes/web.php @@ -6,6 +6,7 @@ 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\TicketControlsController; use Modules\Helpdesk\Http\Controllers\WorkflowOpenController; use Modules\Helpdesk\Livewire\KanbanBoard; use Modules\Helpdesk\Livewire\SettingsManager; @@ -25,6 +26,9 @@ Route::get('/tickets', TicketIndex::class)->name('tickets.index'); 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::post('/tickets/{ticket}/agent', [TicketControlsController::class, 'updateAgent'])->name('tickets.agent.update'); +Route::post('/tickets/{ticket}/status', [TicketControlsController::class, 'updateStatus'])->name('tickets.status.update'); +Route::post('/tickets/{ticket}/priority', [TicketControlsController::class, 'updatePriority'])->name('tickets.priority.update'); Route::get('/tickets/{ticket}', TicketShow::class)->name('tickets.show'); Route::get('/attachments/{attachment}/download', AttachmentDownloadController::class) ->whereNumber('attachment') diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index a2bae294..d89569f5 100644 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -183,3 +183,17 @@ versions: - Replaced native due date inputs on ticket create/edit with a reusable Helpdesk Flatpickr date-picker component. - Added Flatpickr module dependency and dark Helpdesk calendar styling. - Kept module.json version in sync with changelog.yaml. + + "1.3.9": + changed: + - Fixed TicketControlsController authorization by using Laravel Gate authorization instead of relying on a missing controller authorize helper. + - Kept normal CSRF-protected POST ticket controls for agent, status and priority persistence. + - Restricted posted agent assignments to tenant/company users or global users when the users.company_id column exists. + - Kept compact ticket control buttons and module.json version in sync with changelog.yaml. + + "1.3.10": + changed: + - Fixed the ticket detail and ticket edit agent options queries by replacing unsupported orWhereKey usage with supported id comparisons. + - Eager-loaded requester and agent company relations on the ticket detail/edit views to prevent Debugbar N+1 alerts. + - Kept agent, status and priority persistence on CSRF-protected POST routes with compact action buttons. + - Kept module.json version in sync with changelog.yaml. diff --git a/Modules/Helpdesk/module.json b/Modules/Helpdesk/module.json index 1e4d3519..aaafa75a 100644 --- a/Modules/Helpdesk/module.json +++ b/Modules/Helpdesk/module.json @@ -22,5 +22,5 @@ "Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider" ], "files": [], - "version": "1.3.7" + "version": "1.3.10" } diff --git a/Modules/Http/Controllers/TicketControlsController.php b/Modules/Http/Controllers/TicketControlsController.php new file mode 100644 index 00000000..c7c36b2a --- /dev/null +++ b/Modules/Http/Controllers/TicketControlsController.php @@ -0,0 +1,161 @@ +validate([ + 'agent_id' => [ + 'nullable', + 'integer', + Rule::exists('users', 'id')->where(function ($query) use ($ticket): void { + if ($ticket->company_id !== null && Schema::hasColumn('users', 'company_id')) { + $query->where(function ($query) use ($ticket): void { + $query->where('company_id', $ticket->company_id) + ->orWhereNull('company_id'); + }); + } + }), + ], + ]); + + $agentId = $this->nullableInt($validated['agent_id'] ?? null); + + $this->updateTicket($ticket, [ + 'agent_id' => $agentId, + 'last_activity_at' => now(), + ]); + + $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); + + return redirect() + ->route('helpdesk.tickets.show', $fresh) + ->with('ticket-action-updated', $agentId === null + ? 'Ticket is now unassigned.' + : 'Ticket assigned to '.($fresh->agent?->name ?? 'selected agent').'.'); + } + + /** + * Persist the selected ticket status from the ticket detail controls. + */ + public function updateStatus(Request $request, Ticket $ticket, WorkflowNotificationService $workflow, SlaService $sla): RedirectResponse + { + Gate::authorize('update', $ticket); + + $validated = $request->validate([ + 'status_id' => ['required', 'integer', Rule::exists('helpdesk_statuses', 'id')], + ]); + + $status = Status::query()->findOrFail((int) $validated['status_id']); + + $this->updateTicket($ticket, [ + 'status_id' => $status->id, + 'completed_at' => $status->is_closed ? now() : null, + 'last_activity_at' => now(), + ]); + + $this->ensureBoardContainsStatus($ticket, (int) $status->id); + + $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + + if ($status->is_closed) { + $sla->recordClosure($fresh); + } + + $workflow->dispatch($fresh, $status->is_closed ? 'ticket.closed' : 'ticket.updated', Auth::user()); + + return redirect() + ->route('helpdesk.tickets.show', $fresh) + ->with('ticket-action-updated', 'Status updated to '.($fresh->status?->name ?? 'selected status').'.'); + } + + /** + * Persist the selected ticket priority from the ticket detail controls. + */ + public function updatePriority(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse + { + Gate::authorize('update', $ticket); + + $validated = $request->validate([ + 'priority_id' => ['required', 'integer', Rule::exists('helpdesk_priorities', 'id')], + ]); + + $this->updateTicket($ticket, [ + 'priority_id' => (int) $validated['priority_id'], + 'last_activity_at' => now(), + ]); + + $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); + + return redirect() + ->route('helpdesk.tickets.show', $fresh) + ->with('ticket-action-updated', 'Priority updated to '.($fresh->priority?->name ?? 'selected priority').'.'); + } + + /** + * Apply a direct guarded ticket update without relying on Livewire hydration. + * + * @param array $updates + */ + private function updateTicket(Ticket $ticket, array $updates): void + { + if (Schema::hasColumn('helpdesk_tickets', 'updated_at')) { + $updates['updated_at'] = now(); + } + + DB::table('helpdesk_tickets') + ->where('id', $ticket->id) + ->update($updates); + } + + /** + * Keep the active Kanban board usable when a ticket is moved to a status not yet assigned to the board. + */ + private function ensureBoardContainsStatus(Ticket $ticket, int $statusId): void + { + if ($ticket->board_id === null || ! Schema::hasTable('helpdesk_kanban_board_statuses')) { + return; + } + + DB::table('helpdesk_kanban_board_statuses')->updateOrInsert( + ['board_id' => $ticket->board_id, 'status_id' => $statusId], + ['updated_at' => now(), 'created_at' => now()], + ); + } + + /** + * Convert form select values to nullable integers. + */ + private function nullableInt(mixed $value): ?int + { + if ($value === null || $value === '' || $value === '0' || $value === 0) { + return null; + } + + return (int) $value; + } +} diff --git a/Modules/Resources/views/livewire/ticket-show.blade.php b/Modules/Resources/views/livewire/ticket-show.blade.php new file mode 100644 index 00000000..592992eb --- /dev/null +++ b/Modules/Resources/views/livewire/ticket-show.blade.php @@ -0,0 +1,268 @@ +@vite([ + 'resources/css/app.css', + 'resources/js/app.js', + ]) +
+
+
+
+
+
+ {{ $ticket->reference }} + {{ $ticket->status->name }} + {{ $ticket->priority->name }} +
+

{{ $ticket->subject }}

+

Raised by {{ $ticket->owner?->name ?? 'Unknown user' }} · Assigned to {{ $ticket->agent?->name ?? 'Unassigned' }}

+
+
+ + Back + + @can('update', $ticket) + + Edit + + @endcan +
+
+
+
+ +
+
+
+
+ {!! $ticket->body !!} +
+
+ + @if ($ticket->attachments->isNotEmpty()) +
+
+
+
+

+ + + + Ticket attachments +

+

+ Supporting screenshots, documents and logs linked to this ticket. +

+
+ + + {{ $ticket->attachments->count() }} file(s) + +
+
+ + +
+ @endif + +
+

Conversation

+ + @if ($ticket->comments->isNotEmpty()) + @include('helpdesk::partials.comment-thread', ['comments' => $ticket->comments, 'level' => 0]) + @else +
No responses yet.
+ @endif +
+ +
+

Add reply

+ @if ($cannedResponses->isNotEmpty()) +
+ + + @foreach ($cannedResponses as $response) + + @endforeach + +
+ Insert response +
+
+ @endif + + + + @can('update', $ticket) +
+ +
+ @endcan + +
+ + + @error('attachments.*')
{{ $message }}
@enderror +
+ +
+ + Post reply + Posting... + +
+ +
+ + +
+
diff --git a/Modules/Routes/web.php b/Modules/Routes/web.php new file mode 100644 index 00000000..8d61de36 --- /dev/null +++ b/Modules/Routes/web.php @@ -0,0 +1,35 @@ +name('workflow.open'); + +Route::get('/', HelpdeskController::class)->name('index'); + +Route::get('/settings', SettingsManager::class)->name('settings'); +Route::get('/kanban', KanbanBoard::class)->name('kanban'); + +Route::get('/tickets', TicketIndex::class)->name('tickets.index'); +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::post('/tickets/{ticket}/agent', [TicketControlsController::class, 'updateAgent'])->name('tickets.agent.update'); +Route::post('/tickets/{ticket}/status', [TicketControlsController::class, 'updateStatus'])->name('tickets.status.update'); +Route::post('/tickets/{ticket}/priority', [TicketControlsController::class, 'updatePriority'])->name('tickets.priority.update'); +Route::get('/tickets/{ticket}', TicketShow::class)->name('tickets.show'); +Route::get('/attachments/{attachment}/download', AttachmentDownloadController::class) + ->whereNumber('attachment') + ->name('attachments.download'); diff --git a/Modules/changelog.yaml b/Modules/changelog.yaml new file mode 100644 index 00000000..3cdfc162 --- /dev/null +++ b/Modules/changelog.yaml @@ -0,0 +1,192 @@ +- version: 1.3.6 + date: 2026-06-16 + changes: + - Backfilled status columns onto Kanban boards that have none so selected boards render usable workflow columns. + - Added runtime board-column repair to keep newly-created boards usable immediately. + - Tightened the Kanban board selector width and dark styling. + + +- version: 1.3.5 + date: 2026-06-16 + changes: + - Fixed Kanban board filtering so the selected board only shows tickets assigned to that single board. + - Added a backfill migration for existing tickets without a board assignment. + - Restyled the active board selector and per-ticket board selector with compact dark controls. + + +- version: 1.3.4 + date: 2026-06-16 + changes: + - Added single-board ticket assignment via helpdesk_tickets.board_id. + - Added Kanban board owner support. + - Added board-to-board ticket moves while enforcing one active board per ticket. + - Added status move alerts/toasts when tickets move between workflow statuses. + - Added create/edit ticket board selection and default-board assignment for new tickets. + +- version: 1.3.3 + date: '2026-06-16' + changes: + - Reworked Helpdesk action styling so header, support, settings, table, save, reset, edit and delete buttons use reliable coloured native styling. + - Replaced settings section card dynamic Tailwind colour classes with inline styles so card colours always render. + - Improved Kanban board dropdown styling. + - Hardened ticket create inputs and Select2 controls to prevent white flashes during save. + +url: https://wms.vm +versions: + "1.0.0": + changed: + - Initial Module Definition + "1.0.1": + changed: + - Added Vite Support and removed webpack + "1.0.2": + changed: + - Stabilised the Livewire rich text editor so ticket details no longer clear during typing or submit early. + - Added Helpdesk Kanban board for workflow status movement. + - Added Kanban PHPUnit and Dusk coverage. + - Added Kanban navigation links to the dashboard and ticket list. + + "1.0.3": + changed: + - Fixed Helpdesk dashboard rendering by removing dependency on missing x-app-layout/x-layouts.app components. + - Registered anonymous Helpdesk Blade components so x-helpdesk component views resolve correctly. + - Registered the Kanban Livewire component in the Helpdesk service provider. + - Rebuilt the ticket create page to use the correct subject/body Livewire properties. + - Stabilised the rich HTML editor so typing no longer clears the field or accidentally submits the ticket form. + - Added root layout support-menu integration instructions for enabled Helpdesk modules. + + "1.0.4": + changed: + - Fixed the Helpdesk page header so dark WMS pages do not render a blank white hero. + - Changed ticket create save to validate all fields and return to the tickets index after creation to avoid route-model redirect issues. + - Added a root layout replacement containing the Support sidebar menu and Livewire update endpoint configuration. + - Kept module.json version in sync with changelog.yaml. + + "1.0.5": + changed: + - Added the missing POST helpdesk.tickets.store route so native form submission to /helpdesk/tickets no longer returns 404. + - Added TicketStoreController as a safe non-JavaScript fallback while keeping the Livewire ticket create flow intact. + - Updated the ticket create form with method, action, CSRF, multipart support and named fields for fallback posting. + - Kept module.json version in sync with changelog.yaml. + + "1.0.6": + changed: + - Rebuilt the Helpdesk database seeder with the requested Categories, Team, Priority and Status lookup values. + - Added contrasting colour assignments for seeded lookup records. + - Corrected the seeder namespace to Modules\\Helpdesk\\Database\\Seeders for PSR-4 module autoloading. + - Kept module.json version in sync with changelog.yaml. + + "1.0.9": + changed: + - Added editable tickets with assignment, status, priority, category, team, project, due date and details updates. + - Changed the default ticket creation status to New. + - Added configurable Kanban boards with status-column assignment and a default Standard Support board. + - Rebuilt the Kanban board into a professional drag-and-drop board with SortableJS fallback loading. + - Added blue Back and green Edit actions on ticket view pages. + - Improved Helpdesk settings styling, blue edit actions, and a dedicated Kanban Boards CRUD section. + - Marked mandatory ticket fields and enhanced create/edit ticket forms with Select2-capable dropdowns. + - Kept module.json version in sync with changelog.yaml. + + "1.1.3": + changed: + - Fixed ticket update permissions so platform admins, helpdesk admins, assigned agents and ticket owners can edit tickets where appropriate. + - Added the missing ticket edit Blade view with assignment, status, priority, category, project, team, due date and details controls. + - Replaced the Kanban drag dependency path with native browser drag-and-drop to prevent blocked scripts from breaking ticket movement. + - Added Kanban Boards CRUD to Helpdesk settings with configurable status columns. + - Improved ticket create and edit form layout, mandatory markers, dark footer styling and searchable Select2-capable dropdown loading. + - Kept module.json version in sync with changelog.yaml. + + "1.2.0": + changed: + - Added RedmineUP-inspired canned responses with professional reply macro tokens for ticket, requester, agent, status, priority, category, project, team and URL values. + - Added SLA policies, first-response and resolution due dates, breach tracking, SLA event history and ticket/Kanban breach indicators. + - Added incoming email automation rule models and service support for matching sender, subject, mailbox and category to auto-set priority, category, team, project, assignee and status. + - Added database-driven autoresponder enhancements with optional read-receipt tracking tokens, workflow log open timestamps and open counts. + - Added customer/requester profile context on ticket detail pages with tags and richer support metadata. + - Added satisfaction rating data model for closed-ticket feedback reporting by agent, team and category. + - Added ticket tags, checklist templates, checklist items, checklist progress and agent checklist toggling. + - Added product/service/build linkage for tickets with support cost/time fields and product-aware administration. + - Extended Helpdesk settings with professional CRUD sections for canned responses, SLA policies, automation rules, checklist templates, tags and products/services. + - Extended ticket create/edit/show and Kanban screens to include product/service linkage, SLA status, tags and checklist progress without breaking the existing workflow. + - Added PHPUnit coverage for automation, SLA, canned response macro rendering, checklist toggling and product linkage, plus Dusk coverage for the new settings sections. + - Kept module.json version in sync with changelog.yaml. + + "1.2.1": + changed: + - Added signed Git webhook ingestion endpoint at POST /api/helpdesk/git/webhook/{repository} for registered repositories. + - Added GitHub X-Hub-Signature-256 HMAC validation, GitLab X-Gitlab-Token validation and a generic X-Helpdesk-Token fallback for internal providers. + - Added automatic commit persistence and Helpdesk ticket linking from HD-YYYY-00000 references in commit messages, branch names and commit URLs. + - Added helpdesk_git_webhook_logs for auditable accepted, ignored and rejected provider deliveries. + - Added Git webhook endpoint visibility and delivery counts to the Helpdesk Git repository settings table. + - Added provider setup documentation for GitHub, GitLab and generic signed Git payloads. + - Added PHPUnit coverage for accepted GitHub/GitLab push webhooks, invalid signature rejection and ignored non-push events. + - Kept module.json version in sync with changelog.yaml. + + "1.2.2": + changed: + - Fixed Helpdesk settings rendering during rolling upgrades by defaulting newly added RedmineUP/Git settings collections in the Blade view. + - Prevented undefined variable errors for canned responses, SLA policies, automation rules, checklist templates, tags and products when compiled views and Livewire component code are briefly out of sync. + - Kept module.json version in sync with changelog.yaml. + + "1.2.3": + changed: + - Fixed Helpdesk settings Livewire actions for the RedmineUP-inspired sections so SLA, canned responses, automation, products, tags and checklists can be saved from /helpdesk/settings. + - Added SettingsManager mount-time form normalisation to survive stale Livewire snapshots after module upgrades. + - Added a defensive workflow tracking-pixel URL fallback so ticket creation cannot fail when route caches are stale. + - Registered the workflow open pixel route in the web route file as a backup to the module provider route. + - Kept module.json version in sync with changelog.yaml. + + + "1.2.5": + changed: + - Fixed the Helpdesk settings default-company Livewire action by adding saveDefaultHelpdeskCompany to SettingsManager. + - Added default flags for Helpdesk statuses as well as priorities, categories, teams and projects. + - Updated ticket creation to use the configured default status and lookup defaults. + - Added selectable ticket tags to the create ticket workflow and synced tags onto new tickets. + - Kept the one-line ticket subject input and dark form repaint fixes. + - Kept module.json version in sync with changelog.yaml. + + + "1.2.6": + changed: + - Fixed the Helpdesk settings default-company Livewire method and made the default company property resilient to select empty-string values. + - Confirmed default statuses, priorities, categories, teams and projects are applied during ticket creation. + - Confirmed ticket tags can be assigned on create and edit and are synced to the ticket-tag pivot. + - Kept module.json version in sync with changelog.yaml. + + "1.2.8": + changed: + - Replaced white Flux action buttons on the Helpdesk dashboard, ticket list, Kanban board and settings header with explicit high-contrast coloured action buttons. + - Standardised + New Ticket actions as green buttons across Helpdesk entry points. + - Kept module.json version in sync with changelog.yaml. + + + "1.2.9": + changed: + - Applied explicit Helpdesk action colour conventions across dashboard, tickets, Kanban and settings pages. + - Coloured settings section navigation tiles by domain so Statuses, Priorities, Categories, Teams, Projects, Boards, Workflow, SLA, Tags, Products and Defaults are visually distinct. + - Coloured save, reset, edit, delete, settings, ticket, Kanban, reply and update action buttons consistently. + - Kept module.json version in sync with changelog.yaml. + + + "1.3.0": + changed: + - Fixed malformed Helpdesk settings action buttons that caused Blade parse errors. + - Applied explicit coloured action buttons across settings tables, settings forms and Kanban board configuration actions. + - Kept module.json version in sync with changelog.yaml. + + + "1.3.7": + changed: + - Fixed ticket create/edit clear actions by adding public Livewire clearForm methods to both components. + - Stabilised Select2 clearing by loading module assets on ticket forms, adding placeholder support, manually syncing Select2 changes back to Livewire and clearing browser widgets after Livewire reset events. + - Replaced native due date inputs on ticket create/edit with a reusable Helpdesk Flatpickr date-picker component. + - Added Flatpickr module dependency and dark Helpdesk calendar styling. + - Kept module.json version in sync with changelog.yaml. + + "1.3.9": + changed: + - Fixed TicketControlsController authorization by using Laravel Gate authorization instead of relying on a missing controller authorize helper. + - Kept normal CSRF-protected POST ticket controls for agent, status and priority persistence. + - Restricted posted agent assignments to tenant/company users or global users when the users.company_id column exists. + - Kept compact ticket control buttons and module.json version in sync with changelog.yaml. diff --git a/Modules/module.json b/Modules/module.json new file mode 100644 index 00000000..1e4d3519 --- /dev/null +++ b/Modules/module.json @@ -0,0 +1,26 @@ +{ + "name": "Helpdesk", + "alias": "helpdesk", + "description": "Professional Livewire/Flux helpdesk module with ticketing, Kanban, SLA, canned responses, automation rules, checklists, ratings, read receipts, product/service linkage and signed Git webhook tracking.", + "keywords": [ + "helpdesk", + "tickets", + "livewire", + "flux", + "sla", + "automation", + "canned-responses", + "kanban", + "checklists", + "git-webhooks", + "github", + "gitlab", + "audit" + ], + "priority": 0, + "providers": [ + "Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider" + ], + "files": [], + "version": "1.3.7" +}