From 0159a638f643729282fed53e8db300afdea20834 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 22 Jun 2026 14:22:58 +0100 Subject: [PATCH] phases one of time logged now with log display --- Modules/Helpdesk/Livewire/TicketTimer.php | 105 +++----- .../views/livewire/ticket-show.blade.php | 235 ++++++++++-------- .../views/livewire/ticket-timer.blade.php | 1 + .../partials/ticket-time-timer.blade.php | 3 +- .../partials/ticket-timer-include.blade.php | 1 - .../TicketTimerStartDisplayFilesTest.php | 25 ++ 6 files changed, 195 insertions(+), 175 deletions(-) delete mode 100644 Modules/Helpdesk/Resources/views/partials/ticket-timer-include.blade.php create mode 100644 Modules/Helpdesk/Tests/Feature/TicketTimerStartDisplayFilesTest.php diff --git a/Modules/Helpdesk/Livewire/TicketTimer.php b/Modules/Helpdesk/Livewire/TicketTimer.php index 3bae285f..f9f7a8d2 100644 --- a/Modules/Helpdesk/Livewire/TicketTimer.php +++ b/Modules/Helpdesk/Livewire/TicketTimer.php @@ -5,8 +5,8 @@ namespace Modules\Helpdesk\Livewire; use Carbon\Carbon; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\QueryException; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Gate; use Illuminate\Validation\ValidationException; use Livewire\Component; use Modules\Helpdesk\Models\Ticket; @@ -25,7 +25,7 @@ class TicketTimer extends Component public int $elapsedSeconds = 0; /** - * Mount the ticket timer component. + * Mount the ticket timer and load any active database-backed timer. */ public function mount(Ticket $ticket): void { @@ -34,7 +34,7 @@ class TicketTimer extends Component } /** - * Start a user-specific timer for this ticket. + * Start a database-backed timer for the current authenticated user. * * @throws \Illuminate\Validation\ValidationException */ @@ -42,51 +42,47 @@ class TicketTimer extends Component { $user = $this->authenticatedUser(); - Gate::authorize('update', $this->ticket); - if ($this->activeUserTimerQuery($user)->exists()) { throw ValidationException::withMessages([ - 'timer' => 'You already have a running timer. End it before starting another one.', - ]); - } - - if ($this->activeAnyTicketTimerQuery($user)->exists()) { - throw ValidationException::withMessages([ - 'timer' => 'You already have a running timer on another ticket. End it before starting another one.', + 'timer' => 'You already have a running timer.', ]); } $startedAt = Carbon::now(); - TicketTimeLog::query()->create([ - 'ticket_id' => $this->ticket->getKey(), - 'user_id' => $user->getAuthIdentifier(), - 'minutes' => 0, - 'seconds' => 0, - 'description' => $this->normalisedDescription(), - 'started_at' => $startedAt, - 'ended_at' => null, - 'logged_at' => $startedAt, - ]); + try { + $timer = TicketTimeLog::query()->create([ + 'ticket_id' => $this->ticket->getKey(), + 'user_id' => $user->getAuthIdentifier(), + 'minutes' => 0, + 'seconds' => 0, + 'description' => $this->normalisedDescription(), + 'started_at' => $startedAt, + 'ended_at' => null, + 'logged_at' => $startedAt, + ]); + } catch (QueryException $exception) { + throw ValidationException::withMessages([ + 'timer' => 'You already have a running timer.', + ]); + } + $this->activeTimer = $timer->fresh(); + $this->elapsedSeconds = 0; $this->description = ''; $this->message = 'Timer started.'; - $this->refreshActiveTimer(); $this->dispatch('timer-started'); } /** - * End the active user timer and add the elapsed time to the ticket. + * End the current user's active timer and persist seconds/minutes. * * @throws \Illuminate\Validation\ValidationException */ public function endTimer(): void { $user = $this->authenticatedUser(); - - Gate::authorize('update', $this->ticket); - $timer = $this->activeUserTimerQuery($user)->first(); if (! $timer instanceof TicketTimeLog) { @@ -108,7 +104,14 @@ class TicketTimer extends Component 'logged_at' => $endedAt, ])->save(); - $this->updateTicketTimeSpentFromSeconds(); + $totalSeconds = (int) TicketTimeLog::query() + ->where('ticket_id', $this->ticket->getKey()) + ->whereNotNull('ended_at') + ->sum('seconds'); + + $this->ticket->forceFill([ + 'time_spent_minutes' => (int) ceil($totalSeconds / 60), + ])->save(); $this->activeTimer = null; $this->elapsedSeconds = 0; @@ -119,15 +122,15 @@ class TicketTimer extends Component } /** - * Render the ticket timer view. + * Render the timer view. */ - public function render(): mixed + public function render() { return view('helpdesk::livewire.ticket-timer'); } /** - * Refresh the active timer from the database. + * Refresh the active timer state from the database. */ private function refreshActiveTimer(): void { @@ -142,7 +145,7 @@ class TicketTimer extends Component $this->activeTimer = $this->activeUserTimerQuery($user)->first(); $this->elapsedSeconds = $this->activeTimer instanceof TicketTimeLog - ? (int) $this->activeTimer->started_at->diffInSeconds(Carbon::now()) + ? $this->activeTimer->started_at->diffInSeconds(Carbon::now()) : 0; } @@ -157,38 +160,6 @@ class TicketTimer extends Component ->whereNull('ended_at'); } - /** - * Build the active timer query for the authenticated user across all tickets. - */ - private function activeAnyTicketTimerQuery(Authenticatable $user): Builder - { - return TicketTimeLog::query() - ->where('user_id', $user->getAuthIdentifier()) - ->whereNull('ended_at'); - } - - /** - * Update the ticket's rounded minute total using stored seconds. - */ - private function updateTicketTimeSpentFromSeconds(): void - { - $totalSeconds = (int) TicketTimeLog::query() - ->where('ticket_id', $this->ticket->getKey()) - ->whereNotNull('ended_at') - ->sum('seconds'); - - if ($totalSeconds === 0) { - $totalSeconds = (int) TicketTimeLog::query() - ->where('ticket_id', $this->ticket->getKey()) - ->whereNotNull('ended_at') - ->sum('minutes') * 60; - } - - $this->ticket->forceFill([ - 'time_spent_minutes' => (int) ceil($totalSeconds / 60), - ])->save(); - } - /** * Return the authenticated user or fail validation cleanly. * @@ -200,7 +171,7 @@ class TicketTimer extends Component if (! $user instanceof Authenticatable) { throw ValidationException::withMessages([ - 'timer' => 'You must be signed in to use the ticket timer.', + 'timer' => 'You must be logged in to use the timer.', ]); } @@ -208,12 +179,12 @@ class TicketTimer extends Component } /** - * Return a safe note for the timer. + * Return the safe timer description. */ private function normalisedDescription(): string { $description = trim($this->description); - return $description !== '' ? $description : 'Work timer'; + return $description !== '' ? $description : 'Timer entry'; } } diff --git a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php index df5e8ff6..a236820a 100644 --- a/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php +++ b/Modules/Helpdesk/Resources/views/livewire/ticket-show.blade.php @@ -1,5 +1,5 @@ @php - use Illuminate\Support\Str; + use Illuminate\Support\Str;use Modules\Helpdesk\Models\TicketTimeLog; $currentUser = auth()->user(); $currentCompany = $currentUser?->company; @@ -8,7 +8,11 @@ Str::lower((string) ($currentCompany?->slug ?? '')) === 'mpba-limited' || Str::contains(Str::lower((string) ($currentCompany?->name ?? '')), 'mpba'); $visibleCommentCount = $ticket->comments->count(); - $ticketTimeLogs = collect($ticket->timeLogs ?? []); + $ticketTimeLogs = TicketTimeLog::query() + ->with('user') + ->where('ticket_id', $ticket->getKey()) + ->orderByDesc('started_at') + ->get(); @endphp
@@ -33,14 +37,14 @@
+ class="inline-flex items-center rounded-lg bg-blue-600 px-3 py-2 text-xs font-bold text-white shadow-sm hover:bg-blue-700"> Back @can('update', $ticket) + class="inline-flex items-center rounded-lg bg-emerald-600 px-3 py-2 text-xs font-bold text-white shadow-sm hover:bg-emerald-700"> Edit @@ -119,29 +123,31 @@
@can('update', $ticket)
+ enctype="multipart/form-data" + class="rounded-xl border border-blue-500/25 bg-slate-950/70 p-3 shadow-sm" + data-helpdesk-ticket-attachment-form> @csrf
-

Add files

-

+

Add + files

+

No files selected

@@ -149,11 +155,11 @@
@error('attachments') -

{{ $message }}

+

{{ $message }}

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

{{ $message }}

+

{{ $message }}

@enderror @endcan @@ -167,7 +173,7 @@
@foreach ($ticket->attachments as $attachment) + class="group/file flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-xs transition hover:bg-zinc-50 dark:border-zinc-700 dark:hover:bg-zinc-800"> {{ $attachment->original_name ?? ($attachment->original_filename ?? ($attachment->filename ?? ($attachment->file_name ?? basename($attachment->path ?? ($attachment->file_path ?? ''))))) }} @@ -211,7 +217,7 @@ class="inline-flex shrink-0 items-center gap-2 rounded-xl bg-white/15 px-3 py-1.5 text-xs font-extrabold ring-1 ring-white/20"> {{ $visibleCommentCount }} item{{ $visibleCommentCount === 1 ? '' : 's' }} + aria-hidden="true"> @@ -219,23 +225,23 @@
+ class="block w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-xs text-zinc-900 shadow-sm focus:border-red-500 focus:ring-red-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100" + data-helpdesk-comment-search>
@@ -259,9 +265,9 @@
+ enctype="multipart/form-data" + class="overflow-hidden rounded-2xl border border-zinc-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-900" + data-helpdesk-comment-form> @csrf
@@ -356,18 +362,20 @@ ); @endphp - - + + @if ($cannedResponses->isNotEmpty())
+ placeholder="Response subject" + class="block min-h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm font-semibold text-zinc-900 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100" + data-helpdesk-comment-subject>
+ class="mb-2 block text-xs font-bold text-zinc-700 dark:text-zinc-200">Comment
+ data-helpdesk-html-toolbar="ticket-comment-body-editor"> + class="rounded-lg border px-2 py-1 text-xs font-black">B + + class="rounded-lg border px-2 py-1 text-xs italic">I + + class="rounded-lg border px-2 py-1 text-xs underline">U + + class="rounded-lg border px-2 py-1 text-xs">List + + class="rounded-lg border px-2 py-1 text-xs">Quote + + class="rounded-lg border px-2 py-1 text-xs">Code +
+ role="textbox" aria-multiline="true" data-helpdesk-rich-editor + data-helpdesk-sync-target="ticket-comment-body" + class="prose block min-h-[12rem] w-full max-w-none rounded-xl border border-zinc-300 bg-white p-4 text-sm leading-6 text-zinc-900 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-100 dark:prose-invert"> {!! $oldCommentBody !!}
- +
@can('update', $ticket) @@ -430,27 +445,28 @@ class="rounded-xl border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-950">
-
@error('comment_attachments') -

{{ $message }}

+

{{ $message }}

@enderror @error('comment_attachments.*') -

{{ $message }}

+

{{ $message }}

@enderror
@@ -458,7 +474,7 @@ @endif @@ -466,7 +482,7 @@
@@ -475,7 +491,7 @@