Protect recordings per user, seed a demo login on container start, and bind-mount the app with Vite HMR for local Compose development.
147 lines
8.5 KiB
PHP
147 lines
8.5 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'Recordings')
|
|
|
|
@section('content')
|
|
@php
|
|
$indexRows = $recordings->map(function ($recording) {
|
|
return [
|
|
'id' => $recording->id,
|
|
'status' => $recording->transcription_status,
|
|
'status_label' => $recording->transcriptionStatusLabel(),
|
|
'progress' => $recording->transcription_progress,
|
|
'percent' => $recording->transcription_percent,
|
|
'is_active' => $recording->isTranscribing(),
|
|
'word_count' => $recording->word_count,
|
|
'word_count_display' => $recording->word_count > 0
|
|
? number_format($recording->word_count)
|
|
: '—',
|
|
'badge_class' => match ($recording->transcription_status) {
|
|
'done' => 'bg-teal-50 text-teal-800 ring-teal-600/20 dark:bg-teal-950 dark:text-teal-200 dark:ring-teal-400/30',
|
|
'processing' => 'bg-amber-50 text-amber-800 ring-amber-600/20 dark:bg-amber-950 dark:text-amber-200 dark:ring-amber-400/30',
|
|
'pending' => 'bg-amber-50 text-amber-800 ring-amber-600/20 dark:bg-amber-950 dark:text-amber-200 dark:ring-amber-400/30',
|
|
'failed' => 'bg-red-50 text-red-800 ring-red-600/20 dark:bg-red-950 dark:text-red-200 dark:ring-red-400/30',
|
|
'cancelled' => 'bg-stone-100 text-stone-700 ring-stone-500/20 dark:bg-zinc-800 dark:text-zinc-300 dark:ring-zinc-500/30',
|
|
default => 'bg-stone-100 text-stone-700 ring-stone-500/20 dark:bg-zinc-800 dark:text-zinc-300 dark:ring-zinc-500/30',
|
|
},
|
|
];
|
|
})->values();
|
|
@endphp
|
|
|
|
<div
|
|
x-data="recordingsIndex(@js([
|
|
'recordings' => $indexRows,
|
|
'pendingCount' => $pendingCount ?? 0,
|
|
]))"
|
|
x-init="
|
|
start();
|
|
return () => destroy();
|
|
"
|
|
>
|
|
<div class="mb-8 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-semibold tracking-tight">Recordings</h1>
|
|
<p class="mt-1 text-sm text-stone-600 dark:text-zinc-400">Manage pocket-recorder audio and transcripts.</p>
|
|
</div>
|
|
<div class="flex flex-col gap-2 sm:items-end">
|
|
<form method="GET" action="{{ route('recordings.index') }}" class="flex gap-2">
|
|
<input
|
|
type="search"
|
|
name="q"
|
|
value="{{ $search ?? '' }}"
|
|
placeholder="Search title, artist, transcript…"
|
|
class="w-full min-w-[16rem] rounded border border-stone-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-teal-600 focus:outline-none focus:ring-1 focus:ring-teal-600 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder:text-zinc-500"
|
|
>
|
|
<button type="submit" class="rounded border border-stone-300 bg-white px-3 py-2 text-sm hover:bg-stone-50 dark:border-zinc-600 dark:bg-zinc-800 dark:hover:bg-zinc-700">
|
|
Search
|
|
</button>
|
|
</form>
|
|
<form
|
|
method="POST"
|
|
action="{{ route('recordings.transcribe-pending') }}"
|
|
x-show="pendingCount > 0"
|
|
x-cloak
|
|
>
|
|
@csrf
|
|
<button type="submit" class="text-sm font-medium text-teal-700 hover:underline dark:text-teal-300">
|
|
Queue <span x-text="pendingCount"></span> pending
|
|
<span x-text="pendingCount === 1 ? 'transcription' : 'transcriptions'"></span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
@if ($recordings->isEmpty())
|
|
<div class="rounded border border-dashed border-stone-300 bg-white px-6 py-16 text-center dark:border-zinc-600 dark:bg-zinc-800">
|
|
<p class="text-stone-600 dark:text-zinc-400">No recordings yet.</p>
|
|
<a href="{{ route('recordings.create') }}" class="mt-4 inline-block text-sm font-medium text-teal-700 hover:underline dark:text-teal-300">
|
|
Upload your first MP3
|
|
</a>
|
|
</div>
|
|
@else
|
|
<div class="overflow-hidden rounded border border-stone-200 bg-white shadow-sm dark:border-zinc-700 dark:bg-zinc-800">
|
|
<table class="min-w-full divide-y divide-stone-200 text-sm dark:divide-zinc-700">
|
|
<thead class="bg-stone-50 text-left text-xs font-medium uppercase tracking-wide text-stone-500 dark:bg-zinc-900/50 dark:text-zinc-400">
|
|
<tr>
|
|
<th class="px-4 py-3">Title</th>
|
|
<th class="px-4 py-3">Duration</th>
|
|
<th class="px-4 py-3">Words</th>
|
|
<th class="px-4 py-3">Status</th>
|
|
<th class="px-4 py-3">Uploaded</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-stone-100 dark:divide-zinc-700">
|
|
@foreach ($recordings as $recording)
|
|
<tr class="hover:bg-stone-50 dark:hover:bg-zinc-700/50">
|
|
<td class="px-4 py-3">
|
|
<a href="{{ route('recordings.show', $recording) }}" class="font-medium text-teal-800 hover:underline dark:text-teal-300">
|
|
{{ $recording->title }}
|
|
</a>
|
|
@if ($recording->artist)
|
|
<div class="text-xs text-stone-500 dark:text-zinc-400">{{ $recording->artist }}</div>
|
|
@endif
|
|
@if ($snippet = $recording->transcriptSnippet($search ?: null))
|
|
<p class="mt-1 max-w-xl text-xs leading-relaxed text-stone-500 dark:text-zinc-400">
|
|
{{ $snippet }}
|
|
</p>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3 text-stone-600 dark:text-zinc-400">{{ $recording->duration_formatted }}</td>
|
|
<td
|
|
class="px-4 py-3 tabular-nums text-stone-600 dark:text-zinc-400"
|
|
x-text="row({{ $recording->id }}).word_count_display"
|
|
>
|
|
{{ $recording->word_count > 0 ? number_format($recording->word_count) : '—' }}
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span
|
|
class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ring-inset"
|
|
:class="row({{ $recording->id }}).badge_class"
|
|
x-text="row({{ $recording->id }}).status_label"
|
|
>
|
|
{{ $recording->transcriptionStatusLabel() }}
|
|
</span>
|
|
<div
|
|
class="mt-1 max-w-[14rem] truncate text-xs text-amber-700 dark:text-amber-300"
|
|
x-show="row({{ $recording->id }}).is_active && row({{ $recording->id }}).progress"
|
|
x-cloak
|
|
:title="row({{ $recording->id }}).progress"
|
|
>
|
|
<span x-text="row({{ $recording->id }}).percent ? (row({{ $recording->id }}).percent + '% · ') : ''"></span>
|
|
<span x-text="row({{ $recording->id }}).progress"></span>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3 text-stone-600 dark:text-zinc-400">{{ $recording->created_at?->format('Y-m-d H:i') }}</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="mt-6">
|
|
{{ $recordings->links() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endsection
|