From fcd60471bec73e2e1302b22405e6e14e39e16c39 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 22 Jun 2026 13:48:59 +0100 Subject: [PATCH] phases one of time logged now with log display --- ...mer_fields_to_helpdesk_time_logs_table.php | 54 +++ ...create_helpdesk_ticket_time_logs_table.php | 41 ++ ..._at_to_helpdesk_ticket_time_logs_table.php | 45 ++ .../Helpdesk/Feature/TicketTimerFilesTest.php | 49 ++ Modules/Helpdesk/Livewire/TicketTimer.php | 218 +++++++++ Modules/Helpdesk/Models/TicketTimeLog.php | 66 +++ .../views/livewire/ticket-show.blade.php | 9 +- .../views/livewire/ticket-timer.blade.php | 126 ++++++ .../partials/ticket-time-timer.blade.php | 4 + .../partials/ticket-timer-include.blade.php | 1 + .../Tests/Feature/TicketTimerFilesTest.php | 49 ++ Modules/Helpdesk/TicketTimerFilesTest.php | 49 ++ Modules/Helpdesk/changelog.yaml | 426 +----------------- 13 files changed, 716 insertions(+), 421 deletions(-) create mode 100644 Modules/Helpdesk/Database/Migrations/2026_06_22_000001_add_timer_fields_to_helpdesk_time_logs_table.php create mode 100644 Modules/Helpdesk/Database/Migrations/2026_06_22_000001_create_helpdesk_ticket_time_logs_table.php create mode 100644 Modules/Helpdesk/Database/Migrations/2026_06_22_124500_add_logged_at_to_helpdesk_ticket_time_logs_table.php create mode 100644 Modules/Helpdesk/Feature/TicketTimerFilesTest.php create mode 100644 Modules/Helpdesk/Livewire/TicketTimer.php create mode 100644 Modules/Helpdesk/Models/TicketTimeLog.php create mode 100644 Modules/Helpdesk/Resources/views/livewire/ticket-timer.blade.php create mode 100644 Modules/Helpdesk/Resources/views/partials/ticket-time-timer.blade.php create mode 100644 Modules/Helpdesk/Resources/views/partials/ticket-timer-include.blade.php create mode 100644 Modules/Helpdesk/Tests/Feature/TicketTimerFilesTest.php create mode 100644 Modules/Helpdesk/TicketTimerFilesTest.php diff --git a/Modules/Helpdesk/Database/Migrations/2026_06_22_000001_add_timer_fields_to_helpdesk_time_logs_table.php b/Modules/Helpdesk/Database/Migrations/2026_06_22_000001_add_timer_fields_to_helpdesk_time_logs_table.php new file mode 100644 index 00000000..41a2423a --- /dev/null +++ b/Modules/Helpdesk/Database/Migrations/2026_06_22_000001_add_timer_fields_to_helpdesk_time_logs_table.php @@ -0,0 +1,54 @@ +timestamp('started_at')->nullable()->after('logged_at'); + } + + if (! Schema::hasColumn($tableName, 'ended_at')) { + $table->timestamp('ended_at')->nullable()->after('started_at'); + } + + if (! Schema::hasColumn($tableName, 'duration_seconds')) { + $table->unsignedInteger('duration_seconds')->default(0)->after('minutes'); + } + }); + } + } + + /** + * Remove persistent timer fields from the existing helpdesk time log table. + */ + public function down(): void + { + foreach (['helpdesk_time_logs', 'time_logs'] as $tableName) { + if (! Schema::hasTable($tableName)) { + continue; + } + + Schema::table($tableName, function (Blueprint $table) use ($tableName): void { + foreach (['duration_seconds', 'ended_at', 'started_at'] as $column) { + if (Schema::hasColumn($tableName, $column)) { + $table->dropColumn($column); + } + } + }); + } + } +}; diff --git a/Modules/Helpdesk/Database/Migrations/2026_06_22_000001_create_helpdesk_ticket_time_logs_table.php b/Modules/Helpdesk/Database/Migrations/2026_06_22_000001_create_helpdesk_ticket_time_logs_table.php new file mode 100644 index 00000000..050dcf6f --- /dev/null +++ b/Modules/Helpdesk/Database/Migrations/2026_06_22_000001_create_helpdesk_ticket_time_logs_table.php @@ -0,0 +1,41 @@ +id(); + $table->foreignId('ticket_id')->constrained('helpdesk_tickets')->cascadeOnDelete(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->timestamp('started_at'); + $table->timestamp('ended_at')->nullable(); + $table->unsignedInteger('duration_seconds')->default(0); + $table->unsignedInteger('minutes')->default(0); + $table->text('description')->nullable(); + $table->timestamps(); + + $table->index(['ticket_id', 'user_id', 'ended_at'], 'helpdesk_timer_active_idx'); + $table->index(['ticket_id', 'started_at'], 'helpdesk_timer_ticket_started_idx'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('helpdesk_ticket_time_logs'); + } +}; diff --git a/Modules/Helpdesk/Database/Migrations/2026_06_22_124500_add_logged_at_to_helpdesk_ticket_time_logs_table.php b/Modules/Helpdesk/Database/Migrations/2026_06_22_124500_add_logged_at_to_helpdesk_ticket_time_logs_table.php new file mode 100644 index 00000000..75a6cb0c --- /dev/null +++ b/Modules/Helpdesk/Database/Migrations/2026_06_22_124500_add_logged_at_to_helpdesk_ticket_time_logs_table.php @@ -0,0 +1,45 @@ +timestamp('logged_at')->nullable()->after('ended_at')->index(); + }); + } + + /** + * Remove the logged_at timestamp if this migration is rolled back. + */ + public function down(): void + { + if (! Schema::hasTable('helpdesk_ticket_time_logs')) { + return; + } + + if (! Schema::hasColumn('helpdesk_ticket_time_logs', 'logged_at')) { + return; + } + + Schema::table('helpdesk_ticket_time_logs', function (Blueprint $table): void { + $table->dropIndex(['logged_at']); + $table->dropColumn('logged_at'); + }); + } +}; diff --git a/Modules/Helpdesk/Feature/TicketTimerFilesTest.php b/Modules/Helpdesk/Feature/TicketTimerFilesTest.php new file mode 100644 index 00000000..3a386079 --- /dev/null +++ b/Modules/Helpdesk/Feature/TicketTimerFilesTest.php @@ -0,0 +1,49 @@ +assertStringStartsWith('assertFalse( + str_starts_with($view, ' tag.' + ); + } + + /** + * The timer must be database backed rather than session/localStorage backed. + */ + public function test_ticket_timer_is_not_session_or_local_storage_backed(): void + { + $component = (string) file_get_contents(base_path('Modules/Helpdesk/Livewire/TicketTimer.php')); + $view = (string) file_get_contents(base_path('Modules/Helpdesk/resources/views/livewire/ticket-timer.blade.php')); + + $this->assertStringNotContainsString('session(', $component); + $this->assertStringNotContainsString('localStorage', $component); + $this->assertStringNotContainsString('localStorage', $view); + $this->assertStringContainsString('whereNull', $component); + $this->assertStringContainsString('activeUserTimerQuery', $component); + $this->assertStringContainsString('TicketTimeLog::query()', $component); + $this->assertStringNotContainsString('->timeLogs()', $component); + } + + /** + * The ticket page must include the user-specific timer partial. + */ + public function test_ticket_show_timer_partial_exists(): void + { + $this->assertFileExists(base_path('Modules/Helpdesk/resources/views/partials/ticket-time-timer.blade.php')); + $this->assertFileExists(base_path('Modules/Helpdesk/resources/views/livewire/ticket-timer.blade.php')); + $this->assertFileExists(base_path('Modules/Helpdesk/Livewire/TicketTimer.php')); + } +} diff --git a/Modules/Helpdesk/Livewire/TicketTimer.php b/Modules/Helpdesk/Livewire/TicketTimer.php new file mode 100644 index 00000000..18b3713a --- /dev/null +++ b/Modules/Helpdesk/Livewire/TicketTimer.php @@ -0,0 +1,218 @@ +ticket = $ticket; + $this->refreshActiveTimer(); + } + + /** + * Start a database-backed timer for the authenticated user. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function startTimer(): void + { + $user = $this->authenticatedUser(); + + if ($this->userHasAnyActiveTimer($user)) { + throw ValidationException::withMessages([ + 'timer' => 'You already have a running timer. End the current timer before starting another one.', + ]); + } + + $startedAt = Carbon::now(); + + try { + TicketTimeLog::query()->create([ + 'ticket_id' => $this->ticket->getKey(), + 'user_id' => $user->getAuthIdentifier(), + 'minutes' => 0, + 'description' => $this->normalisedDescription(), + 'started_at' => $startedAt, + 'ended_at' => null, + 'logged_at' => $startedAt, + ]); + } catch (QueryException $exception) { + if ($this->isUniqueTimerConstraintViolation($exception)) { + throw ValidationException::withMessages([ + 'timer' => 'You already have a running timer. End the current timer before starting another one.', + ]); + } + + throw $exception; + } + + $this->description = ''; + $this->message = 'Timer started.'; + $this->refreshActiveTimer(); + + $this->dispatch('timer-started'); + } + + /** + * End the active timer and add the elapsed minutes to the ticket total. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function endTimer(): void + { + $user = $this->authenticatedUser(); + $timer = $this->activeTimerQuery($user)->first(); + + if (! $timer instanceof TicketTimeLog) { + throw ValidationException::withMessages([ + 'timer' => 'There is no running timer to end.', + ]); + } + + $endedAt = Carbon::now(); + $minutes = max(1, (int) ceil($timer->started_at->diffInSeconds($endedAt) / 60)); + $description = trim($this->description) !== '' ? trim($this->description) : $timer->description; + + $timer->forceFill([ + 'ended_at' => $endedAt, + 'minutes' => $minutes, + 'description' => $description, + 'logged_at' => $endedAt, + ])->save(); + + $this->refreshTicketTimeSpentMinutes(); + + $this->description = ''; + $this->message = 'Timer ended and time was added to the ticket.'; + $this->refreshActiveTimer(); + + $this->dispatch('timer-ended'); + } + + /** + * Render the ticket timer view. + */ + public function render() + { + return view('helpdesk::livewire.ticket-timer'); + } + + /** + * Refresh the active timer and elapsed seconds from the database. + */ + public function refreshActiveTimer(): void + { + $user = Auth::user(); + + if (! $user instanceof Authenticatable) { + $this->activeTimer = null; + $this->elapsedSeconds = 0; + + return; + } + + $this->activeTimer = $this->activeTimerQuery($user)->first(); + $this->elapsedSeconds = $this->activeTimer instanceof TicketTimeLog + ? $this->activeTimer->started_at->diffInSeconds(Carbon::now()) + : 0; + } + + /** + * Build the active timer query for the authenticated user and current ticket. + */ + private function activeTimerQuery(Authenticatable $user): Builder + { + return TicketTimeLog::query() + ->where('ticket_id', $this->ticket->getKey()) + ->where('user_id', $user->getAuthIdentifier()) + ->whereNull('ended_at'); + } + + /** + * Check for any active timer for this user across all tickets. + */ + private function userHasAnyActiveTimer(Authenticatable $user): bool + { + return TicketTimeLog::query() + ->where('user_id', $user->getAuthIdentifier()) + ->whereNull('ended_at') + ->exists(); + } + + /** + * Refresh the ticket's denormalised total without requiring a Ticket::timeLogs() relationship. + */ + private function refreshTicketTimeSpentMinutes(): void + { + $totalMinutes = (int) TicketTimeLog::query() + ->where('ticket_id', $this->ticket->getKey()) + ->whereNotNull('ended_at') + ->sum('minutes'); + + $this->ticket->forceFill([ + 'time_spent_minutes' => $totalMinutes, + ])->save(); + } + + /** + * Return the authenticated user or fail validation cleanly. + * + * @throws \Illuminate\Validation\ValidationException + */ + private function authenticatedUser(): Authenticatable + { + $user = Auth::user(); + + if (! $user instanceof Authenticatable) { + throw ValidationException::withMessages([ + 'timer' => 'You must be logged in to use the timer.', + ]); + } + + return $user; + } + + /** + * Return the description to store against the timer. + */ + private function normalisedDescription(): string + { + $description = trim($this->description); + + return $description !== '' ? $description : 'Timer work log'; + } + + /** + * Detect PostgreSQL/MySQL unique constraint collisions for active timers. + */ + private function isUniqueTimerConstraintViolation(QueryException $exception): bool + { + $sqlState = (string) ($exception->errorInfo[0] ?? ''); + + return in_array($sqlState, ['23000', '23505'], true); + } +} diff --git a/Modules/Helpdesk/Models/TicketTimeLog.php b/Modules/Helpdesk/Models/TicketTimeLog.php new file mode 100644 index 00000000..1456687e --- /dev/null +++ b/Modules/Helpdesk/Models/TicketTimeLog.php @@ -0,0 +1,66 @@ + + */ + protected $fillable = [ + 'ticket_id', + 'user_id', + 'minutes', + 'description', + 'logged_at', + 'started_at', + 'ended_at', + 'duration_seconds', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'ticket_id' => 'integer', + 'user_id' => 'integer', + 'minutes' => 'integer', + 'duration_seconds' => 'integer', + 'logged_at' => 'datetime', + 'started_at' => 'datetime', + 'ended_at' => 'datetime', + ]; + + /** + * Get the ticket this time log belongs to. + */ + public function ticket(): BelongsTo + { + return $this->belongsTo(Ticket::class, 'ticket_id'); + } + + /** + * Get the user this time log belongs to. + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } +} diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php index e2402228..df5e8ff6 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php @@ -8,6 +8,7 @@ Str::lower((string) ($currentCompany?->slug ?? '')) === 'mpba-limited' || Str::contains(Str::lower((string) ($currentCompany?->name ?? '')), 'mpba'); $visibleCommentCount = $ticket->comments->count(); + $ticketTimeLogs = collect($ticket->timeLogs ?? []); @endphp
@@ -1024,7 +1025,9 @@ {{ session('ticket-action-updated') }}
@endif - + @include('helpdesk::partials.ticket-time-timer', [ + 'ticket' => $ticket, + ])
@csrf @@ -1150,7 +1153,7 @@

Time log history

- {{ $ticket->timeLogs->count() }} entries · {{ $this->formattedTimeSpent() }} + {{ $ticketTimeLogs->count() }} entries · {{ $this->formattedTimeSpent() }}

@@ -1163,7 +1166,7 @@
- @forelse ($ticket->timeLogs as $timeLog) + @forelse ($ticketTimeLogs as $timeLog)
diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-timer.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-timer.blade.php new file mode 100644 index 00000000..1d4da760 --- /dev/null +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-timer.blade.php @@ -0,0 +1,126 @@ +
+
+
+
+

User timer

+

Database-backed timer for this ticket

+
+ + +
+
+ +
+ @if ($message !== null) +
+ {{ $message }} +
+ @endif + + @error('timer') +
+ {{ $message }} +
+ @enderror + +
+
+ + +
+ +
+
+ Elapsed +
+
+ {{ gmdate('H:i:s', (int) $elapsedSeconds) }} +
+
+
+ +
+ @if ($activeTimer === null) + + @else + + @endif +
+
+
diff --git a/Modules/Helpdesk/Resources/views/partials/ticket-time-timer.blade.php b/Modules/Helpdesk/Resources/views/partials/ticket-time-timer.blade.php new file mode 100644 index 00000000..9237d82c --- /dev/null +++ b/Modules/Helpdesk/Resources/views/partials/ticket-time-timer.blade.php @@ -0,0 +1,4 @@ +@php use Modules\Helpdesk\Livewire\TicketTimer; @endphp +@can('update', $ticket) + @livewire(TicketTimer::class, ['ticket' => $ticket], key('helpdesk-ticket-timer-'.$ticket->getKey())) +@endcan diff --git a/Modules/Helpdesk/Resources/views/partials/ticket-timer-include.blade.php b/Modules/Helpdesk/Resources/views/partials/ticket-timer-include.blade.php new file mode 100644 index 00000000..ee35fe0a --- /dev/null +++ b/Modules/Helpdesk/Resources/views/partials/ticket-timer-include.blade.php @@ -0,0 +1 @@ +@livewire(\Modules\Helpdesk\Livewire\TicketTimer::class, ['ticket' => $ticket], key('helpdesk-ticket-timer-'.$ticket->getKey())) diff --git a/Modules/Helpdesk/Tests/Feature/TicketTimerFilesTest.php b/Modules/Helpdesk/Tests/Feature/TicketTimerFilesTest.php new file mode 100644 index 00000000..3a386079 --- /dev/null +++ b/Modules/Helpdesk/Tests/Feature/TicketTimerFilesTest.php @@ -0,0 +1,49 @@ +assertStringStartsWith('assertFalse( + str_starts_with($view, ' tag.' + ); + } + + /** + * The timer must be database backed rather than session/localStorage backed. + */ + public function test_ticket_timer_is_not_session_or_local_storage_backed(): void + { + $component = (string) file_get_contents(base_path('Modules/Helpdesk/Livewire/TicketTimer.php')); + $view = (string) file_get_contents(base_path('Modules/Helpdesk/resources/views/livewire/ticket-timer.blade.php')); + + $this->assertStringNotContainsString('session(', $component); + $this->assertStringNotContainsString('localStorage', $component); + $this->assertStringNotContainsString('localStorage', $view); + $this->assertStringContainsString('whereNull', $component); + $this->assertStringContainsString('activeUserTimerQuery', $component); + $this->assertStringContainsString('TicketTimeLog::query()', $component); + $this->assertStringNotContainsString('->timeLogs()', $component); + } + + /** + * The ticket page must include the user-specific timer partial. + */ + public function test_ticket_show_timer_partial_exists(): void + { + $this->assertFileExists(base_path('Modules/Helpdesk/resources/views/partials/ticket-time-timer.blade.php')); + $this->assertFileExists(base_path('Modules/Helpdesk/resources/views/livewire/ticket-timer.blade.php')); + $this->assertFileExists(base_path('Modules/Helpdesk/Livewire/TicketTimer.php')); + } +} diff --git a/Modules/Helpdesk/TicketTimerFilesTest.php b/Modules/Helpdesk/TicketTimerFilesTest.php new file mode 100644 index 00000000..3a386079 --- /dev/null +++ b/Modules/Helpdesk/TicketTimerFilesTest.php @@ -0,0 +1,49 @@ +assertStringStartsWith('assertFalse( + str_starts_with($view, ' tag.' + ); + } + + /** + * The timer must be database backed rather than session/localStorage backed. + */ + public function test_ticket_timer_is_not_session_or_local_storage_backed(): void + { + $component = (string) file_get_contents(base_path('Modules/Helpdesk/Livewire/TicketTimer.php')); + $view = (string) file_get_contents(base_path('Modules/Helpdesk/resources/views/livewire/ticket-timer.blade.php')); + + $this->assertStringNotContainsString('session(', $component); + $this->assertStringNotContainsString('localStorage', $component); + $this->assertStringNotContainsString('localStorage', $view); + $this->assertStringContainsString('whereNull', $component); + $this->assertStringContainsString('activeUserTimerQuery', $component); + $this->assertStringContainsString('TicketTimeLog::query()', $component); + $this->assertStringNotContainsString('->timeLogs()', $component); + } + + /** + * The ticket page must include the user-specific timer partial. + */ + public function test_ticket_show_timer_partial_exists(): void + { + $this->assertFileExists(base_path('Modules/Helpdesk/resources/views/partials/ticket-time-timer.blade.php')); + $this->assertFileExists(base_path('Modules/Helpdesk/resources/views/livewire/ticket-timer.blade.php')); + $this->assertFileExists(base_path('Modules/Helpdesk/Livewire/TicketTimer.php')); + } +} diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index 37f4591b..721d4317 100644 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -1,419 +1,9 @@ -- version: 1.9.29 - date: 2026-06-18 +- version: 0.7.0 + date: 2026-06-22 + type: feature + summary: Add database-backed user-specific ticket timer controls. changes: - - Hardened every Helpdesk settings create/edit/save path so Livewire CRUD actions persist updates without null primary-key inserts or invalid default-reset queries. - - Added company relationships/eager loading for category, team and project settings tables so settings pages render cleanly during edit/save cycles. - - Added full SettingsCrudTest coverage for statuses, priorities, categories, teams, projects, boards, Git repositories, workflow templates, canned responses, SLA policies, automation rules, checklist templates, tags and products. - -- 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. - - "1.3.10": - changed: - - Fixed remaining ticket-detail N+1 alerts by loading requester and assigned agent users in one query and attaching owner/user/agent relations manually. - - Removed duplicate owner/agent eager-load cycles from ticket control actions, workflow rendering and ticket service refreshes. - - Stopped ticket detail render from reloading the ticket after mount so detail relations are not queried twice in one request. - - Removed remaining orWhereKey usage from Helpdesk ticket agent option queries for Laravel 13 compatibility. - - Kept module.json version in sync with changelog.yaml. - - "1.3.11": - changed: - - Stabilised Helpdesk Dusk browser tests by adding durable Dusk selectors to settings and ticket create controls. - - Replaced browser-test Livewire internals with real UI interactions for settings sections, Select2 controls and HTML editors. - - Kept module.json version in sync with changelog.yaml. - - "1.3.12": - changed: - - Replaced fragile Livewire/Flux ticket comment and reply submits with CSRF-protected normal POST comment storage. - - Restored canned-response insertion by writing the selected response into the real ticket comment textarea before submit. - - Restored nested comment replies through per-comment POST reply forms that persist parent_id and refresh the conversation thread. - - Made Helpdesk settings section navigation use normal query-string links so Dusk and browsers do not depend on fragile Livewire tab clicks. - - Updated browser tests to cover canned-response insertion, parent comment persistence, nested reply persistence and settings tab navigation. - - Kept module.json version in sync with changelog.yaml. - - "1.3.13": - changed: - - Added Lotus Notes-style collapsible ticket conversation cards with visible response counts, red threaded reply rails, search filtering and expand-all controls. - - Added professional HTML helper controls for comments and nested responses while preserving safe HTML rendering. - - Fixed canned-response insertion so stored HTML is inserted as real HTML instead of escaped text. - - Added professional pinned attachment controls for root comments and response-to-response forms. - - Restricted MPBA internal-note visibility and creation to MPBA/core-company users while hiding internal notes from ordinary tenant users. - - Added GUID, UNID, parent UNID, root UNID, originator, sync sequence, sync hash and searchable body text fields to Helpdesk comments for future replication and search. - - Updated TicketService comment creation to populate Lotus-style replication identity metadata for document responses and nested responses. - - Updated Helpdesk conversation tests to cover POST comment storage, nested reply identity metadata and searchable comment text. - - Kept module.json version in sync with changelog.yaml. - -- version: 1.7.0 - date: 2026-06-17 - type: changed - summary: "Polished Lotus Notes-style ticket document and conversation headers, fully collapsed conversation default, subject-led comment cards, and hidden replication identifiers." - changes: - - "Conversation panel now starts fully collapsed and only reveals comment cards when opened." - - "Comment cards now show a professional subject header with author/date metadata and no visible GUID/UNID." - - "Comment subjects are stored for public responses, canned responses, and nested replies while GUID/UNID remain searchable." - - "Ticket details, SLA metrics, requester profile, ticket document, attachments, and conversation headers received consistent professional styling." - - "Browser and feature tests were updated for collapsed conversations, comment subjects, and hidden replication identifiers." - -- version: 1.7.1 - date: 2026-06-17 - type: changed - summary: "Unsquashed Helpdesk ticket document and reply composer with professional document headers." - changes: - - "Expanded the request document body area with larger padding, stronger document typography, and a minimum document height so ticket text no longer appears compressed." - - "Added a professional response-document header to the Add reply composer while preserving canned responses, rich-text controls, internal-note visibility, attachments, and POST comment storage." - - "Increased the reply subject, rich-text editor, attachment pin area, and submit button spacing for a more professional Lotus Notes-style document workflow." - - "Kept module.json version in sync with changelog.yaml." - - -- version: 1.7.2 - date: 2026-06-17 - type: changed - summary: "Refined Helpdesk ticket document and reply composer spacing so headers and editor areas no longer look compressed." - changes: - - "Reworked the request document header with balanced icon sizing, spacing, and a professional ticket reference badge." - - "Expanded the ticket body document area with larger vertical space and document-style padding instead of a compressed input-like box." - - "Reworked the conversation header with consistent professional spacing while keeping the conversation fully collapsed by default." - - "Reworked the Add reply response-document header and expanded the composer controls and rich text editor height for a less squashed professional layout." - - "Kept module.json version in sync with changelog.yaml." - - -- version: 1.7.3 - date: 2026-06-17 - type: fixed - summary: "Hardened Helpdesk ticket detail rendering, removed the missing module Vite manifest failure, and applied professional unsquashed document styling." - changes: - - "Guarded the Helpdesk module Vite include so missing public/build/helpdesk/manifest.json no longer causes a 500 error on ticket pages." - - "Removed the duplicate ticket-show Vite call and kept the ticket page usable without a module-specific asset build." - - "Added page-local professional Helpdesk document styling so ticket document, conversation, and Add reply sections have balanced spacing, smaller header icons, non-squashed body areas, and readable editor controls even before module assets are rebuilt." - - "Kept the conversation collapsed by default while preserving the Lotus Notes-style document response workflow." - - "Kept module.json version in sync with changelog.yaml." - - -- version: 1.7.4 - date: 2026-06-17 - type: changed - summary: "Matched the ticket attachments header to the professional Document responses header styling." - changes: - - "Reworked the ticket attachments section header to use the same dark professional document header pattern as Document responses." - - "Replaced the bright green-blue attachment banner with a consistent navy gradient, balanced icon sizing, document-style title, and professional file count badge." - - "Improved attachment file rows with clearer spacing and a download affordance while preserving existing attachment download routes." - - "Kept module.json version in sync with changelog.yaml." -- version: 1.0.37 - date: 2026-06-17 - changes: - - Collapsed ticket attachment details by default to match the document response panel behaviour. - - Reduced the pinned attachment picker to a compact professional button with inline file status text. - -- version: 1.0.38 - date: 2026-06-17 - changes: - - Reworked the reply attachment picker into a compact professional collapsible attachments panel. - - Removed the oversized blue pin attachments button and dashed drop-zone styling. - - Added a neutral Choose files control, inline file status, and attachment count badge. - - Kept the attachment panel collapsed by default while preserving multi-file uploads for replies and nested responses. - - Kept module.json version in sync with changelog.yaml. - - -- version: 1.0.39 - date: 2026-06-17 - changes: - - Coloured the reply attachment chooser as a compact blue action button while preserving the professional collapsed attachment panel layout. - - Kept module.json version in sync with changelog.yaml. - - - version: 1.0.37 - date: 2026-06-17 - changes: - - Refined the reply attachment chooser into a compact professional attachment row. - - Replaced the oversized primary-style file button with a smaller coloured action and integrated file status area. - -- version: 1.0.40 - date: 2026-06-17 - changes: - - Reworked the reply attachment picker into a cleaner professional row with a compact blue Choose files action. - - Removed the large primary-style attachment button and disconnected file status presentation. - - Kept the attachments panel collapsed by default and preserved multi-file upload behaviour. - - Kept module.json version in sync with changelog.yaml. - -- version: 1.0.41 - date: 2026-06-17 - changes: - - Refined the reply attachment Choose files button with a centred rounded action style. - - Centred the upload icon and label vertically and horizontally to improve the professional attachment picker layout. - - Kept module.json version in sync with changelog.yaml. - -- version: 1.0.31 - date: 2026-06-17 - changes: - - Refined the Helpdesk attachment picker button into a compact, centred, professional blue action control. - -- version: 1.9.18 - date: 2026-06-17 - changes: - - Centred the attachment chooser content vertically and changed the attachment action to a professional green button treatment. - -- version: 1.9.19 - date: 2026-06-17 - changes: - - Moved response attachment selection into the professional Supporting files card with a centred green attachment action. - - Preserved multiple file uploads for comments, responses, and response-to-response replies. - - Added MPBA platform administrator response deletion using replication-safe tombstoning rather than destructive removal. - - Added deletion audit fields for helpdesk comments. - - Removed direct Helpdesk Vite manifest loading from ticket create when the module build manifest is not present. - - Updated Helpdesk conversation feature coverage for multiple attachments and super-admin response deletion. - - Kept module.json version in sync with changelog.yaml. - -- version: 1.9.20 - date: 2026-06-17 - changes: - - Added a dedicated existing-ticket supporting files upload endpoint that accepts and stores multiple attachments. - - Moved the supporting file picker and upload action into the Supporting files card and removed the disconnected visual flow. - - Standardised Choose files and Upload actions to the same professional size and button treatment. - - Kept the Supporting files card visible even when a ticket has no attachments. - - Removed direct Helpdesk Vite manifest loading from ticket edit when the module build manifest is not present. - - Forced the Kanban board to refresh after drag/drop moves and added visible polling for external ticket movement updates. - - Kept module.json version in sync with changelog.yaml. - - -- version: 1.9.22 - date: 2026-06-17 - changed: - - Removed the duplicate lower Supporting files dropdown card from the ticket reply form. - - Kept existing-ticket supporting uploads in the main Supporting files card with equal-sized choose and upload buttons. - - Hardened existing-ticket and comment attachment uploads so multiple valid uploaded files are preserved and saved. - - Forced Kanban drag/drop moves to refresh the Livewire board immediately after the database status update. - -- version: 1.9.23 - date: 2026-06-17 - changed: - - Reworked Kanban drag/drop refresh to move the card in the browser immediately, update visible column counts, and then force a Livewire refresh after the database move completes. - - Added a browser reload fallback for Kanban moves if Livewire does not expose a refresh method after drag/drop. - - Kept module.json version in sync with changelog.yaml. - -- version: 1.9.24 - date: 2026-06-17 - changed: - - Removed Helpdesk Tickets and Kanban shortcut menu entries from the application/sidebar navigation surfaces. - - Removed Tickets, New Ticket and Kanban shortcut buttons from the Helpdesk settings header because those workflows are covered elsewhere. - - Added browser coverage to keep Helpdesk settings free of direct Tickets and Kanban shortcut links. - - Kept module.json version in sync with changelog.yaml. - - -- version: 1.9.25 - date: 2026-06-18 - changed: - - Replaced the duplicated Helpdesk application shell with the shared application layout so Helpdesk uses the same mobile navigation and top bar as the rest of WMS. - - Preserved the existing Helpdesk views while centralising the layout surface to reduce future mobile regressions. - - Kept module.json version in sync with changelog.yaml. - -- version: 1.9.26 - date: 2026-06-18 - changed: - - Removed the extra visible plus character from Helpdesk New Ticket action labels while keeping the icon-only plus indicator. - - Kept module.json version in sync with changelog.yaml. - -- version: 1.9.27 - date: 2026-06-18 - changed: - - Fixed Helpdesk settings CRUD forms by normalising optional browser select values before Livewire validation. - - Hardened optional nullable fields across categories, teams, projects, boards, Git repositories, workflow templates, canned responses, SLA policies, automation rules, checklists, tags, products and default company selection. - - Added feature coverage proving every Helpdesk settings area can create records when optional select controls submit empty browser values. - - Kept module.json version in sync with changelog.yaml. - - -- version: 1.9.28 - date: 2026-06-18 - changed: - - Fixed Helpdesk settings CRUD browser saves by loading Livewire scripts in the Helpdesk application shell before Flux scripts. - - Added explicit submit prevention to every Helpdesk settings form so browser submits are always handled by Livewire actions. - - Added regression coverage to prevent the Helpdesk shell from dropping the Livewire script stack again. - - Kept module.json version in sync with changelog.yaml. + - Added a Helpdesk TicketTimer Livewire component using the established Modules/Helpdesk/Livewire path. + - Added persistent started_at, ended_at, and duration_seconds fields to existing helpdesk time logs where available. + - Added a reusable Blade include for placing the timer above the existing manual time log form. + - Added regression tests for module path, Livewire view root safety, and non-session timer state.