Files
WMS/Modules/Helpdesk/Livewire/TicketTimer.php
T
2026-09-21 10:14:57 +01:00

220 lines
6.0 KiB
PHP
Executable File

<?php
declare(strict_types=1);
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\Validation\ValidationException;
use Livewire\Component;
use Modules\Helpdesk\Models\Ticket;
use Modules\Helpdesk\Models\TicketTimeLog;
class TicketTimer extends Component
{
public Ticket $ticket;
public ?TicketTimeLog $activeTimer = null;
public string $description = '';
public ?string $message = null;
public int $elapsedSeconds = 0;
/**
* Mount the ticket timer and load any active database-backed timer.
*/
public function mount(Ticket $ticket): void
{
$this->ticket = $ticket;
$this->refreshActiveTimer();
}
/**
* Refresh the timer every poll cycle while the timer is running.
*/
public function refreshTimer(): void
{
$this->refreshActiveTimer();
}
/**
* Start a database-backed timer for the current authenticated user.
*
* @throws ValidationException
*/
public function startTimer(): void
{
$user = $this->authenticatedUser();
if ($this->activeUserTimerQuery($user)->exists()) {
throw ValidationException::withMessages([
'timer' => 'You already have a running timer.',
]);
}
$startedAt = Carbon::now();
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) {
throw ValidationException::withMessages([
'timer' => 'You already have a running timer.',
]);
}
$this->activeTimer = $timer->fresh();
$this->elapsedSeconds = 0;
$this->description = '';
$this->message = 'Timer started.';
$this->dispatch('timer-started');
$this->dispatch('helpdesk-ticket-time-log-updated');
}
/**
* End the current user's active timer and persist seconds/minutes.
*
* @throws ValidationException
*/
public function endTimer(): void
{
$user = $this->authenticatedUser();
$timer = $this->activeUserTimerQuery($user)->first();
if (! $timer instanceof TicketTimeLog) {
throw ValidationException::withMessages([
'timer' => 'There is no running timer to end.',
]);
}
$endedAt = Carbon::now();
$description = trim($this->description) !== '' ? trim($this->description) : $timer->description;
$seconds = max(1, (int) $timer->started_at->diffInSeconds($endedAt));
$minutes = (int) ceil($seconds / 60);
$timer->forceFill([
'ended_at' => $endedAt,
'seconds' => $seconds,
'minutes' => $minutes,
'description' => $description,
'logged_at' => $endedAt,
])->save();
$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;
$this->description = '';
$this->message = 'Timer ended and time was added to the ticket.';
$this->dispatch('timer-ended');
$this->dispatch('helpdesk-ticket-time-log-updated');
}
/**
* Get the currently elapsed timer duration.
*/
public function getElapsedTimeProperty(): string
{
return gmdate('H:i:s', $this->elapsedSeconds);
}
/**
* Determine whether the current user has a running timer.
*/
public function getIsRunningProperty(): bool
{
return $this->activeTimer instanceof TicketTimeLog;
}
/**
* Render the timer view.
*/
public function render()
{
return view('helpdesk::livewire.ticket-timer');
}
/**
* Refresh the active timer state from the database.
*/
private function refreshActiveTimer(): void
{
$user = Auth::user();
if (! $user instanceof Authenticatable) {
$this->activeTimer = null;
$this->elapsedSeconds = 0;
return;
}
$this->activeTimer = $this->activeUserTimerQuery($user)->first();
$this->elapsedSeconds = $this->activeTimer instanceof TicketTimeLog
? max(0, (int) $this->activeTimer->started_at->diffInSeconds(Carbon::now()))
: 0;
}
/**
* Build the active timer query for the authenticated user and current ticket.
*/
private function activeUserTimerQuery(Authenticatable $user): Builder
{
return TicketTimeLog::query()
->where('ticket_id', $this->ticket->getKey())
->where('user_id', $user->getAuthIdentifier())
->whereNull('ended_at');
}
/**
* Return the authenticated user or fail validation cleanly.
*
* @throws 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 safe timer description.
*/
private function normalisedDescription(): string
{
$description = trim($this->description);
return $description !== '' ? $description : 'Timer entry';
}
}