Improve recordings list sorting and live show-page status updates.
Add sortable columns with a fixed status width, and keep the show page in sync via Echo, polling, and Alpine hydrate while transcription runs.
This commit is contained in:
@@ -5,6 +5,8 @@ namespace App\Livewire\Recordings;
|
||||
use App\Models\Recording;
|
||||
use Flux\Flux;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Livewire\Attributes\Layout;
|
||||
@@ -25,9 +27,26 @@ class Index extends Component
|
||||
#[Url(as: 'q', history: true)]
|
||||
public string $search = '';
|
||||
|
||||
#[Url(as: 'sort', history: true)]
|
||||
public string $sortBy = 'uploaded';
|
||||
|
||||
#[Url(as: 'dir', history: true)]
|
||||
public string $sortDirection = 'desc';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const SORTABLE = [
|
||||
'title' => 'title',
|
||||
'duration' => 'duration_seconds',
|
||||
'status' => 'transcription_status',
|
||||
'uploaded' => 'created_at',
|
||||
];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->userId = (int) Auth::id();
|
||||
$this->normalizeSort();
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
@@ -35,6 +54,22 @@ class Index extends Component
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function sort(string $column): void
|
||||
{
|
||||
if ($column !== 'words' && ! array_key_exists($column, self::SORTABLE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->sortBy === $column) {
|
||||
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortBy = $column;
|
||||
$this->sortDirection = $column === 'uploaded' ? 'desc' : 'asc';
|
||||
}
|
||||
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-render when any of this user's recordings broadcast a status change.
|
||||
*/
|
||||
@@ -127,7 +162,9 @@ class Index extends Component
|
||||
|
||||
$totalCount = $user->recordings()->count();
|
||||
|
||||
$query = $user->recordings()->latest();
|
||||
$this->normalizeSort();
|
||||
|
||||
$query = $user->recordings();
|
||||
|
||||
$search = trim($this->search);
|
||||
|
||||
@@ -135,6 +172,8 @@ class Index extends Component
|
||||
$query->search($search);
|
||||
}
|
||||
|
||||
$this->applySort($query);
|
||||
|
||||
$recordings = $query->paginate(20);
|
||||
|
||||
$pendingCount = $user->recordings()
|
||||
@@ -155,4 +194,34 @@ class Index extends Component
|
||||
'totalCount' => $totalCount,
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeSort(): void
|
||||
{
|
||||
if ($this->sortBy !== 'words' && ! array_key_exists($this->sortBy, self::SORTABLE)) {
|
||||
$this->sortBy = 'uploaded';
|
||||
}
|
||||
|
||||
if (! in_array($this->sortDirection, ['asc', 'desc'], true)) {
|
||||
$this->sortDirection = 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder<Recording>|HasMany<Recording, User> $query
|
||||
*/
|
||||
private function applySort(Builder|HasMany $query): void
|
||||
{
|
||||
$direction = $this->sortDirection === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
if ($this->sortBy === 'words') {
|
||||
$query->orderByRaw(
|
||||
'CASE WHEN transcript IS NULL OR TRIM(transcript) = ? THEN 0 ELSE LENGTH(TRIM(transcript)) - LENGTH(REPLACE(TRIM(transcript), ?, ?)) + 1 END '.$direction,
|
||||
['', ' ', ''],
|
||||
);
|
||||
} else {
|
||||
$query->orderBy(self::SORTABLE[$this->sortBy], $direction);
|
||||
}
|
||||
|
||||
$query->orderByDesc('id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use Flux\Flux;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Layout('layouts.app')]
|
||||
@@ -14,6 +15,8 @@ class Show extends Component
|
||||
{
|
||||
public Recording $recording;
|
||||
|
||||
public int $userId;
|
||||
|
||||
public function mount(Recording $recording): void
|
||||
{
|
||||
Gate::authorize('view', $recording);
|
||||
@@ -22,6 +25,22 @@ class Show extends Component
|
||||
$recording->refresh();
|
||||
|
||||
$this->recording = $recording;
|
||||
$this->userId = (int) $recording->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-render when this recording broadcasts a status change (same channel as the index).
|
||||
*
|
||||
* @param array<string, mixed> $event
|
||||
*/
|
||||
#[On('echo-private:user.{userId}.recordings,.RecordingTranscriptionUpdated')]
|
||||
public function onTranscriptionUpdated(array $event = []): void
|
||||
{
|
||||
if (isset($event['id']) && (int) $event['id'] !== (int) $this->recording->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->recording->refresh();
|
||||
}
|
||||
|
||||
public function startTranscription(): void
|
||||
@@ -64,6 +83,10 @@ class Show extends Component
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
if ($this->recording->isTranscribing()) {
|
||||
$this->recording->refresh();
|
||||
}
|
||||
|
||||
return view('livewire.recordings.show')
|
||||
->title($this->recording->title);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user