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:
ben
2026-08-12 21:27:03 +02:00
parent 5f0f61995c
commit 8a66cf6f63
7 changed files with 344 additions and 67 deletions
+70 -1
View File
@@ -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');
}
}