This commit is contained in:
2025-06-08 17:48:07 +09:00
parent 41a433893b
commit f34c601ec0
7 changed files with 705 additions and 13 deletions

View File

@ -27,10 +27,14 @@ interface EpisodeWithFeedInfo {
feedId: string;
feedTitle?: string;
feedUrl: string;
feedCategory?: string;
}
function EpisodeList() {
const [episodes, setEpisodes] = useState<EpisodeWithFeedInfo[]>([]);
const [filteredEpisodes, setFilteredEpisodes] = useState<EpisodeWithFeedInfo[]>([]);
const [categories, setCategories] = useState<string[]>([]);
const [selectedCategory, setSelectedCategory] = useState<string>("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [currentAudio, setCurrentAudio] = useState<string | null>(null);
@ -38,8 +42,13 @@ function EpisodeList() {
useEffect(() => {
fetchEpisodes();
fetchCategories();
}, [useDatabase]);
useEffect(() => {
filterEpisodesByCategory();
}, [episodes, selectedCategory]);
const fetchEpisodes = async () => {
try {
setLoading(true);
@ -106,6 +115,29 @@ function EpisodeList() {
}
};
const fetchCategories = async () => {
try {
const response = await fetch("/api/categories");
if (response.ok) {
const data = await response.json();
setCategories(data.categories || []);
}
} catch (err) {
console.error("Error fetching categories:", err);
}
};
const filterEpisodesByCategory = () => {
if (!selectedCategory) {
setFilteredEpisodes(episodes);
} else {
const filtered = episodes.filter(episode =>
episode.feedCategory === selectedCategory
);
setFilteredEpisodes(filtered);
}
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString("ja-JP");
};
@ -165,6 +197,21 @@ function EpisodeList() {
return <div className="error">{error}</div>;
}
if (filteredEpisodes.length === 0 && episodes.length > 0 && selectedCategory) {
return (
<div className="empty-state">
<p>{selectedCategory}</p>
<button
className="btn btn-secondary"
onClick={() => setSelectedCategory("")}
style={{ marginTop: "10px" }}
>
</button>
</div>
);
}
if (episodes.length === 0) {
return (
<div className="empty-state">
@ -193,8 +240,27 @@ function EpisodeList() {
alignItems: "center",
}}
>
<h2> ({episodes.length})</h2>
<h2> ({selectedCategory ? `${filteredEpisodes.length}/${episodes.length}` : episodes.length})</h2>
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
{categories.length > 0 && (
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
style={{
padding: "5px 10px",
fontSize: "14px",
border: "1px solid #ccc",
borderRadius: "4px",
}}
>
<option value=""></option>
{categories.map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
)}
<span style={{ fontSize: "12px", color: "#666" }}>
: {useDatabase ? "データベース" : "XML"}
</span>
@ -214,7 +280,7 @@ function EpisodeList() {
</tr>
</thead>
<tbody>
{episodes.map((episode) => (
{filteredEpisodes.map((episode) => (
<tr key={episode.id}>
<td>
<div style={{ marginBottom: "8px" }}>
@ -242,6 +308,11 @@ function EpisodeList() {
>
{episode.feedTitle}
</Link>
{episode.feedCategory && (
<span style={{ marginLeft: "8px", color: "#999", fontSize: "11px" }}>
({episode.feedCategory})
</span>
)}
</div>
)}
{episode.articleTitle &&