import { useEffect, useState } from "react"; import React from "react"; interface FeedItem { id: string; title: string; link: string; pubDate: string; contentSnippet?: string; source?: { title?: string; url?: string; }; category?: string; } interface FeedListProps { searchTerm?: string; categoryFilter?: string; } export default function FeedList({ searchTerm = "", categoryFilter = "", }: FeedListProps = {}) { const [feeds, setFeeds] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [sortBy, setSortBy] = useState<"date" | "title">("date"); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); useEffect(() => { fetchFeeds(); }, []); const fetchFeeds = async () => { try { setLoading(true); const response = await fetch("/api/feeds"); if (!response.ok) { throw new Error("フィードの取得に失敗しました"); } const data = await response.json(); setFeeds(data); setError(null); } catch (err) { setError(err instanceof Error ? err.message : "エラーが発生しました"); } finally { setLoading(false); } }; const filteredAndSortedFeeds = feeds .filter((feed) => { const matchesSearch = !searchTerm || feed.title.toLowerCase().includes(searchTerm.toLowerCase()) || feed.contentSnippet?.toLowerCase().includes(searchTerm.toLowerCase()) || feed.source?.title?.toLowerCase().includes(searchTerm.toLowerCase()); const matchesCategory = !categoryFilter || feed.category === categoryFilter; return matchesSearch && matchesCategory; }) .sort((a, b) => { const multiplier = sortOrder === "asc" ? 1 : -1; if (sortBy === "date") { return ( (new Date(a.pubDate).getTime() - new Date(b.pubDate).getTime()) * multiplier ); } else { return a.title.localeCompare(b.title) * multiplier; } }); const handleSort = (field: "date" | "title") => { if (sortBy === field) { setSortOrder(sortOrder === "asc" ? "desc" : "asc"); } else { setSortBy(field); setSortOrder("desc"); } }; const formatDate = (dateString: string) => { try { return new Date(dateString).toLocaleDateString("ja-JP", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } catch { return dateString; } }; if (loading) { return (
{[...Array(5)].map((_, i) => (
))}
); } if (error) { return (
⚠️

エラーが発生しました

{error}

); } return (
{/* Sort Controls */}
並び替え:
{filteredAndSortedFeeds.length} / {feeds.length} 件表示中
{/* Feed Cards */} {filteredAndSortedFeeds.length === 0 ? (

{searchTerm || categoryFilter ? "検索結果がありません" : "フィードがありません"}

{searchTerm || categoryFilter ? "別のキーワードやカテゴリで検索してみてください" : "RSSフィードを追加してバッチ処理を実行してください"}

) : (
{filteredAndSortedFeeds.map((feed, index) => (
{/* Article Icon */}
{/* Article Content */}
{/* Header */}

{feed.title}

{/* Meta Info */}
{feed.source?.title && ( {feed.source.title} )} {formatDate(feed.pubDate)}
{/* Category Badge */} {feed.category && ( {feed.category} )}
{/* Content Snippet */} {feed.contentSnippet && (

{feed.contentSnippet}

)} {/* Actions */}
))}
)}
); }