Files
VoiceRSSSummary/frontend/src/components/EpisodeList.tsx
2025-06-07 13:45:48 +09:00

138 lines
3.8 KiB
TypeScript

import { useState, useEffect } from 'react'
interface Episode {
id: string
title: string
audioPath: string
createdAt: string
article?: {
link: string
}
feed?: {
title: string
}
}
function EpisodeList() {
const [episodes, setEpisodes] = useState<Episode[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [currentAudio, setCurrentAudio] = useState<string | null>(null)
useEffect(() => {
fetchEpisodes()
}, [])
const fetchEpisodes = async () => {
try {
setLoading(true)
const response = await fetch('/api/episodes')
if (!response.ok) throw new Error('エピソードの取得に失敗しました')
const data = await response.json()
setEpisodes(data)
} catch (err) {
setError(err instanceof Error ? err.message : 'エラーが発生しました')
} finally {
setLoading(false)
}
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString('ja-JP')
}
const playAudio = (audioPath: string) => {
if (currentAudio) {
const currentPlayer = document.getElementById(currentAudio) as HTMLAudioElement
if (currentPlayer) {
currentPlayer.pause()
currentPlayer.currentTime = 0
}
}
setCurrentAudio(audioPath)
}
if (loading) {
return <div className="loading">...</div>
}
if (error) {
return <div className="error">{error}</div>
}
if (episodes.length === 0) {
return (
<div className="empty-state">
<p></p>
<p>RSSフィードを追加してください</p>
</div>
)
}
return (
<div>
<div style={{ marginBottom: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2> ({episodes.length})</h2>
<button className="btn btn-secondary" onClick={fetchEpisodes}>
</button>
</div>
<table className="table">
<thead>
<tr>
<th style={{ width: '40%' }}></th>
<th style={{ width: '20%' }}></th>
<th style={{ width: '20%' }}></th>
<th style={{ width: '20%' }}></th>
</tr>
</thead>
<tbody>
{episodes.map((episode) => (
<tr key={episode.id}>
<td>
<div style={{ marginBottom: '8px' }}>
<strong>{episode.title}</strong>
</div>
{episode.article?.link && (
<a
href={episode.article.link}
target="_blank"
rel="noopener noreferrer"
style={{ fontSize: '12px', color: '#666' }}
>
</a>
)}
</td>
<td>{episode.feed?.title || '不明'}</td>
<td>{formatDate(episode.createdAt)}</td>
<td>
<button
className="btn btn-primary"
onClick={() => playAudio(episode.audioPath)}
style={{ marginBottom: '8px' }}
>
</button>
{currentAudio === episode.audioPath && (
<div>
<audio
id={episode.audioPath}
controls
className="audio-player"
src={`/podcast_audio/${episode.audioPath}`}
onEnded={() => setCurrentAudio(null)}
/>
</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
export default EpisodeList