diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9057510..38a9949 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,6 +3,7 @@ import "./app/globals.css"; import Dashboard from "./components/Dashboard"; import EpisodePlayer from "./components/EpisodePlayer"; import FeedManager from "./components/FeedManager"; +import React from "react"; type TabType = "dashboard" | "episodes" | "feeds"; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 8113fe5..d5c3816 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import FeedManager from "../components/FeedManager"; import EpisodePlayer from "../components/EpisodePlayer"; import Dashboard from "../components/Dashboard"; +import React from "react"; export const metadata = { title: "Voice RSS Summary", diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx index f1a75b6..500845c 100644 --- a/frontend/src/components/Dashboard.tsx +++ b/frontend/src/components/Dashboard.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import React from "react"; interface Stats { totalFeeds: number; @@ -66,6 +67,7 @@ export default function Dashboard() { } } catch (error) { alert("エラーが発生しました。"); + console.error("Batch process trigger error:", error); } }; diff --git a/frontend/src/components/EpisodePlayer.tsx b/frontend/src/components/EpisodePlayer.tsx index a9a4817..163d4aa 100644 --- a/frontend/src/components/EpisodePlayer.tsx +++ b/frontend/src/components/EpisodePlayer.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, useRef } from "react"; +import React from "react"; interface Episode { id: string; @@ -55,7 +56,7 @@ export default function EpisodePlayer() { audio.removeEventListener("loadedmetadata", updateDuration); audio.removeEventListener("ended", handleEnded); }; - }, [selectedEpisode]); + }, [selectedEpisode, isPlaying, audioRef, currentTime, duration]); const fetchEpisodes = async () => { try { diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx index 5408a6a..e7b8804 100644 --- a/frontend/src/components/FeedList.tsx +++ b/frontend/src/components/FeedList.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import React from "react"; interface FeedItem { id: string; @@ -18,12 +19,15 @@ interface FeedListProps { categoryFilter?: string; } -export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedListProps = {}) { +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'); + const [sortBy, setSortBy] = useState<"date" | "title">("date"); + const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); useEffect(() => { fetchFeeds(); @@ -47,43 +51,48 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL }; const filteredAndSortedFeeds = feeds - .filter(feed => { - const matchesSearch = !searchTerm || + .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; - + + 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; + 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') => { + const handleSort = (field: "date" | "title") => { if (sortBy === field) { - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); } else { setSortBy(field); - setSortOrder('desc'); + 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' + return new Date(dateString).toLocaleDateString("ja-JP", { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", }); } catch { return dateString; @@ -94,7 +103,10 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL return (
{[...Array(5)].map((_, i) => ( -
+
@@ -115,15 +127,14 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL
-⚠️ + ⚠️
-

エラーが発生しました

+

+ エラーが発生しました +

{error}

-
@@ -137,36 +148,34 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL {/* Sort Controls */}
- 並び替え: + + 並び替え: +
@@ -179,17 +188,23 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL {/* Feed Cards */} {filteredAndSortedFeeds.length === 0 ? (
-
- +
+

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

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

) : ( @@ -199,18 +214,23 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL key={feed.id} className="group glass-effect rounded-3xl border border-white/20 hover:border-white/40 hover:shadow-2xl transition-all duration-300 overflow-hidden" style={{ - animationDelay: `${index * 0.05}s` + animationDelay: `${index * 0.05}s`, }} >
{/* Article Icon */}
-
- +
+
- + {/* Article Content */}
{/* Header */} @@ -219,17 +239,19 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL

{feed.title}

- + {/* Meta Info */}
{feed.source?.title && ( - {feed.source.title} + + {feed.source.title} + )} {formatDate(feed.pubDate)}
- + {/* Category Badge */} {feed.category && ( @@ -237,14 +259,14 @@ export default function FeedList({ searchTerm = "", categoryFilter = "" }: FeedL )}
- + {/* Content Snippet */} {feed.contentSnippet && (

{feed.contentSnippet}

)} - + {/* Actions */}
元記事を読む → - -
- #{index + 1} -
+ +
#{index + 1}
diff --git a/frontend/src/components/FeedManager.tsx b/frontend/src/components/FeedManager.tsx index 22886f4..aa9c128 100644 --- a/frontend/src/components/FeedManager.tsx +++ b/frontend/src/components/FeedManager.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import React from "react"; interface Feed { id: string; @@ -76,6 +77,7 @@ export default function FeedManager() { } } catch (err) { alert("エラーが発生しました"); + console.error("Feed addition error:", err); } finally { setAddingFeed(false); } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 4aff025..722f6c5 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,9 +1,10 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.tsx' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; +import React from "react"; -createRoot(document.getElementById('root')!).render( +createRoot(document.getElementById("root")!).render( , -) +);