RLWO7JI4ZIH2LNR6OM5JSRQ7YEMW5BY7ZNUXIRYPROLSHDUHJ4PQC EVQYH44JJD5YTCCLSK2I3PPQFA57KJJYHKQKOKVAIKKYWLIEB2VQC HOKPVOTWZFYXYNKLYBD6XCRSKGNBRABIY5LBU7A5I7F6K7E45MNQC IUHUM6OZ5KYEAYQCIYNG5Q4QLQRAQNMBWKYGV2ZDJFNY5W4DOUNQC DZURLJKGFFIAG4KUZTLCURERBXHFWNSNUFYYB7PXCXFUJWPIHWZQC 3BOFJXKAMRCAQSJQD3AZWYG64A3IO54F4773ZOVWHJSVOQY34CSQC YBJGFUTFTV4SBPUVC6LBETGMPMLS47LSQPL3OJAAPH7BROBQD7DQC ADZREFCOSVSO6I66BOLGTZFJI4Y7J3WJO2PNHCHE2W7Y5PZPX4OAC RELTAIDEZ4Y2SC2WQROQ4IEJO5BVPULTVLLJ4BA23WE5FVT5QOQAC ETAXJ5YIOUH75V6ZKHUGQWTRWIRZVJAUG5LYXYZEJDS7SXPNEZSAC PY24MQAJYYL2IQCG5SIXN4J4DXYNG7QHNZMRBTCWKLJ7EK5DUFZAC SFLRF3YSORJMAHAO3DVL7EXOKLAXFNMJKGJOBTYXPPIVM75GM5KAC TPANU3OQD53OXF5IGXJELSD5FFMY7KHDSVZTPSUG3VZBUAPNPK6QC OQBBKXHMSDFTIYQPOAT4QTOELZP6JURH524SQPWOXWGN6DG5ZR6QC 4SDYLIN2RF7RT4U6T4PKLXRQ7ON4SZOTDHMWFQQQE27E4EMFEQVQC YZMPDME2RWQGRPDXPSO7UVAZNK5LWVNCWRFWYU62ORL7MJX64UVAC return undefined; // No cleanup needed if not open}, [isOpen, onClose, togglePlayPause, state.wavesurfer]);
document.addEventListener("keydown", handleKeyDown);return () => document.removeEventListener("keydown", handleKeyDown);}, [togglePlayPause, state.wavesurfer]);
<div className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"><div className="fixed inset-0 z-50 flex items-center justify-center p-4"><div className="relative w-full h-full max-w-none bg-background border rounded-lg shadow-lg overflow-hidden">{/* Header */}<div className="flex items-center justify-between p-4 border-b bg-muted/50"><div className="flex-1 min-w-0"><h2 className="text-lg font-semibold truncate">{file.fileName}</h2><p className="text-sm text-muted-foreground">{state.fileContext ? (<>{state.fileContext.dataset &&`Dataset: ${state.fileContext.dataset.name}`}{state.fileContext.dataset &&state.fileContext.location &&" • "}{state.fileContext.location &&`Location: ${state.fileContext.location.name}`}{(state.fileContext.dataset ||state.fileContext.location) &&state.fileContext.cluster &&" • "}{state.fileContext.cluster &&`Cluster: ${state.fileContext.cluster.name}`}</>) : state.fileContextLoading ? ("Loading context...") : ("Audio File")}</p>
<div className="card p-6 bg-white shadow-sm rounded-lg"><div className="flex items-center justify-between mb-6"><div className="flex-1 min-w-0"><h2 className="text-lg font-semibold truncate">{file.fileName}</h2><p className="text-sm text-muted-foreground">{state.fileContext ? (<>{state.fileContext.dataset &&`Dataset: ${state.fileContext.dataset.name}`}{state.fileContext.dataset &&state.fileContext.location &&" • "}{state.fileContext.location &&`Location: ${state.fileContext.location.name}`}{(state.fileContext.dataset ||state.fileContext.location) &&state.fileContext.cluster &&" • "}{state.fileContext.cluster &&`Cluster: ${state.fileContext.cluster.name}`}</>) : state.fileContextLoading ? ("Loading context...") : ("Audio File")}</p></div>{/* Filter dropdown */}{state.selections.length > 0 &&getAvailableFilters().length > 0 && (<div className="ml-4"><selectvalue={state.selectedFilterId || "all"}onChange={(e) =>setState((prev) => ({...prev,selectedFilterId:e.target.value === "all" ? null : e.target.value,}))}className="text-sm rounded-md border border-gray-300 bg-white py-2 px-3 shadow-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"><option value="all">All Models</option>{getAvailableFilters().map((filter) => (<option key={filter.id} value={filter.id}>{filter.name || `Filter ${filter.id}`}</option>))}</select></div>)}</div><div className="space-y-6">{/* Online file - show waveform */}{file.upload && (<div className="space-y-4">{state.audioLoading && (<div className="flex items-center justify-center h-32 text-muted-foreground">Loading audio...</div>)}{state.audioError && (<div className="flex items-center gap-2 p-3 bg-destructive/10 text-destructive rounded-md"><AlertCircle className="h-4 w-4" />{state.audioError}</div>)}{/* Audio Visualization */}<div className="bg-muted/20 rounded-md p-3 space-y-1">{/* Waveform */}<div ref={waveformRef} className="w-full h-24" />{/* Timeline */}<divref={timelineRef}className="w-full"style={{ height: "20px" }}/>{/* Spectrogram */}<divref={spectrogramRef}className="w-full"style={{ height: "256px" }}/>
{/* Filter dropdown in header */}{state.selections.length > 0 &&getAvailableFilters().length > 0 && (<div className="mx-4"><selectvalue={state.selectedFilterId || "all"}onChange={(e) =>setState((prev) => ({...prev,selectedFilterId:e.target.value === "all" ? null : e.target.value,}))}className="text-sm rounded-md border border-gray-300 bg-white py-2 px-3 shadow-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
{/* Controls */}{state.wavesurfer &&!state.audioLoading &&!state.audioError && (<div className="flex items-center gap-4 p-4 bg-muted/20 rounded-md"><Buttonvariant="outline"size="icon"onClick={togglePlayPause}disabled={state.audioLoading}
<option value="all">All Models</option>{getAvailableFilters().map((filter) => (<option key={filter.id} value={filter.id}>{filter.name || `Filter ${filter.id}`}</option>))}</select>
{state.isPlaying ? (<Pause className="h-4 w-4" />) : (<Play className="h-4 w-4" />)}</Button><div className="text-sm text-muted-foreground">Duration: {formatTime(file.duration)} • Sample Rate:{" "}{(file.sampleRate / 1000).toFixed(1)} kHz</div>
{/* Main Content */}<div className="flex-1 p-4 overflow-y-auto max-h-[calc(100vh-8rem)]">{/* Online file - show waveform */}{file.upload && (
{/* Offline file handling */}{!file.upload && (<div className="space-y-4">{/* Show file picker for owner, info message for non-owner */}{userIsOwner ? (<div className="text-center p-8"><Upload className="h-12 w-12 mx-auto text-muted-foreground mb-4" /><h3 className="text-lg font-medium mb-2">Load Local File</h3><p className="text-sm text-muted-foreground mb-4">This file is not available online. Load it from your localstorage to play and analyze.</p><ButtononClick={() => void openFilePicker().catch(console.error)}>{state.filePickerSupported ? "Browse Files" : "Select File"}</Button></div>) : (<div className="text-center p-8"><AlertCircle className="h-12 w-12 mx-auto text-muted-foreground mb-4" /><h3 className="text-lg font-medium mb-2">{state.selections.length > 0 ? "Audio Not Available" : "File Not Available"}</h3><p className="text-sm text-muted-foreground">{state.selections.length > 0? "This file is not available for playback, but you can view the selections below.": "This file is not available online and you don't have permission to load local files."}</p></div>)}{/* Show waveform if local file is loaded (owner only) */}{state.localFile && userIsOwner && (
{/* Offline file handling */}{!file.upload && (<div className="space-y-4">{/* Show file picker for owner, info message for non-owner */}{userIsOwner ? (<div className="text-center p-8"><Upload className="h-12 w-12 mx-auto text-muted-foreground mb-4" /><h3 className="text-lg font-medium mb-2">Load Local File</h3><p className="text-sm text-muted-foreground mb-4">This file is not available online. Load it from your localstorage to play and analyze.</p><ButtononClick={() => void openFilePicker().catch(console.error)}>{state.filePickerSupported ? "Browse Files" : "Select File"}</Button></div>) : (<div className="text-center p-8"><AlertCircle className="h-12 w-12 mx-auto text-muted-foreground mb-4" /><h3 className="text-lg font-medium mb-2">{state.selections.length > 0 ? "Audio Not Available" : "File Not Available"}</h3><p className="text-sm text-muted-foreground">{state.selections.length > 0? "This file is not available for playback, but you can view the selections below.": "This file is not available online and you don't have permission to load local files."}</p></div>)}{/* Show waveform if local file is loaded (owner only) */}{state.localFile && userIsOwner && (<div className="space-y-4"><div className="p-3 bg-green-50 text-green-800 rounded-md">Loaded: {state.localFile.name}</div>{state.audioLoading && (<div className="flex items-center justify-center h-32 text-muted-foreground">Loading audio...</div>)}{/* Audio Visualization */}<div className="bg-muted/20 rounded-md p-3 space-y-1">{/* Waveform */}<div ref={waveformRef} className="w-full h-24" />{/* Timeline */}<divref={timelineRef}className="w-full"style={{ height: "20px" }}/>{/* Spectrogram */}<divref={spectrogramRef}className="w-full"style={{ height: "256px" }}/></div>{/* Controls */}{state.wavesurfer &&!state.audioLoading &&!state.audioError && (<div className="flex items-center gap-4 p-4 bg-muted/20 rounded-md"><Buttonvariant="outline"size="icon"onClick={togglePlayPause}disabled={state.audioLoading}>{state.isPlaying ? (<Pause className="h-4 w-4" />) : (<Play className="h-4 w-4" />)}</Button><div className="text-sm text-muted-foreground">Duration: {formatTime(file.duration)} • Sample Rate:{" "}{(file.sampleRate / 1000).toFixed(1)} kHz</div></div>)}</div>)}
{state.audioError && (<div className="flex items-center gap-2 p-3 bg-destructive/10 text-destructive rounded-md"><AlertCircle className="h-4 w-4" />{state.audioError}</div>)}
{state.audioError && (<div className="flex items-center gap-2 p-3 bg-destructive/10 text-destructive rounded-md"><AlertCircle className="h-4 w-4" />{state.audioError}
{/* Selections Table */}<div className="mt-6 border-t pt-4">{state.selectionsLoading && (<div className="text-center py-8 text-muted-foreground">Loading selections...</div>)}
{/* Selections Table */}{state.selections.length > 0 && (<div className="space-y-4">{state.selectionsLoading && (<div className="text-center py-8 text-muted-foreground">Loading selections...</div>)}
{!state.selectionsLoading && !state.selectionsError && state.selections.length === 0 && (<div className="text-center py-8 text-muted-foreground">No selections found for this file.</div>)}{state.selections.length > 0 && (<div className="w-full overflow-visible"><Table><TableHeader className="bg-muted"><TableRow className="border-b-2 border-primary/20"><TableHead className="py-3 font-bold text-sm uppercase">Start (s)</TableHead><TableHead className="py-3 font-bold text-sm uppercase">End (s)</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Description</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Distance</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Comment</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Species</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Species Certainty</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Call Type</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Call Type Certainty</TableHead>{canEditSelections && (<TableHead className="w-[120px] py-3 font-bold text-sm uppercase text-center">Actions</TableHead>)}</TableRow></TableHeader><TableBody>
{!state.selectionsLoading && !state.selectionsError && (<div className="w-full overflow-x-auto"><Table><TableHeader className="bg-muted"><TableRow className="border-b-2 border-primary/20"><TableHead className="py-3 font-bold text-sm uppercase">Start (s)</TableHead><TableHead className="py-3 font-bold text-sm uppercase">End (s)</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Description</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Distance</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Comment</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Species</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Species Certainty</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Call Type</TableHead><TableHead className="py-3 font-bold text-sm uppercase">Call Type Certainty</TableHead>{canEditSelections && (<TableHead className="w-[120px] py-3 font-bold text-sm uppercase text-center">Actions</TableHead>)}</TableRow></TableHeader><TableBody>