import { useRef, useState, useEffect } from 'react' import { getKeyColor, getBPMColor } from '../utils/musicColors' interface Clip { id: string title?: string prompt: string model: string duration_seconds: number status: string error_message?: string file_size_bytes?: number artwork_filename?: string | null bpm?: number | null key?: string | null created_at: string } interface Props { clip: Clip onClose: () => void onRename: (id: string, title: string) => void onDelete: (id: string) => void onUploadArtwork: (id: string, file: File) => void onRemoveArtwork: (id: string) => void onPlay?: (clip: Clip) => void } const modelName = (modelId: string) => { const names: Record = { 'lyria-3-clip-preview': 'Clip Preview', 'lyria-3-pro-preview': 'Pro', } return names[modelId] || modelId } export default function DetailPanel({ clip, onClose, onRename, onDelete, onUploadArtwork, onRemoveArtwork, onPlay }: Props) { const artworkInputRef = useRef(null) const [isEditingTitle, setIsEditingTitle] = useState(false) const [editTitle, setEditTitle] = useState(clip.title || '') const titleInputRef = useRef(null) // Focus and select when entering edit mode useEffect(() => { if (isEditingTitle && titleInputRef.current) { titleInputRef.current.focus() titleInputRef.current.select() } }, [isEditingTitle]) const startEditing = () => { setEditTitle(clip.title || '') setIsEditingTitle(true) } const saveTitle = () => { if (editTitle.trim() !== (clip.title || '')) { onRename(clip.id, editTitle.trim()) } setIsEditingTitle(false) } const cancelEditing = () => { setEditTitle(clip.title || '') setIsEditingTitle(false) } const handleTitleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { saveTitle() } else if (e.key === 'Escape') { cancelEditing() } } const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }) } const statusColor = (status: string) => { switch (status) { case 'ready': return 'bg-green-500/20 text-green-400' case 'generating': return 'bg-yellow-500/20 text-yellow-400' case 'failed': return 'bg-red-500/20 text-red-400' default: return 'bg-white/10 text-lyria-muted' } } const statusLabel = (status: string) => { switch (status) { case 'ready': return 'Ready' case 'generating': return 'Generating...' case 'failed': return 'Failed' default: return status } } return ( <> {/* Backdrop */}
{/* Panel */}
{/* Header */}

Track Details

{/* Album Artwork */}
{clip.artwork_filename && ( )}
{clip.artwork_filename ? (
onPlay?.(clip)} > Album art
) : (
onPlay?.(clip)} > 𝄞 No album art
)} { const file = e.target.files?.[0] if (file) { onUploadArtwork(clip.id, file) } e.target.value = '' }} />
{/* FFT Waveform */} {clip.status === 'ready' && (
Waveform
)} {/* Title — inline editable */}
{!isEditingTitle && ( )}
{isEditingTitle ? ( setEditTitle(e.target.value)} onBlur={saveTitle} onKeyDown={handleTitleKeyDown} className="w-full bg-white/5 border border-lyria-accent/50 rounded-md px-3 py-2 text-white text-lg font-medium outline-none focus:border-lyria-accent focus:ring-1 focus:ring-lyria-accent/30 transition-all" placeholder="Track title..." /> ) : (

{clip.title || Untitled}

)}
{/* Prompt */}

{clip.prompt}

{/* Status */}
{statusLabel(clip.status)}
{/* Error message */} {clip.status === 'failed' && clip.error_message && (

{clip.error_message}

)} {/* Metadata grid */}

{modelName(clip.model)}

{clip.duration_seconds}s

{clip.file_size_bytes && (

{(clip.file_size_bytes / 1024).toFixed(0)} KB

)} {clip.bpm && (() => { const c = getBPMColor(clip.bpm) return c ? (
{Math.round(clip.bpm)}
) : null })()} {clip.key && (() => { const c = getKeyColor(clip.key) return c ? (
{clip.key}
) : null })()}

{formatDate(clip.created_at)}

{/* ID (for reference) */}

{clip.id}

{/* Actions */}
Download MP3
) }