Remove category list component

This commit is contained in:
2025-06-08 22:28:33 +09:00
parent e8c2313f06
commit 2a81654d16
2 changed files with 0 additions and 401 deletions

View File

@ -1,5 +1,4 @@
import { Link, Route, Routes, useLocation } from "react-router-dom";
import CategoryList from "./components/CategoryList";
import EpisodeDetail from "./components/EpisodeDetail";
import EpisodeList from "./components/EpisodeList";
import FeedDetail from "./components/FeedDetail";
@ -12,7 +11,6 @@ function App() {
const isMainPage = [
"/",
"/feeds",
"/categories",
"/feed-requests",
"/rss-endpoints",
].includes(location.pathname);
@ -41,12 +39,6 @@ function App() {
>
</Link>
<Link
to="/categories"
className={`tab ${location.pathname === "/categories" ? "active" : ""}`}
>
</Link>
<Link
to="/feed-requests"
className={`tab ${location.pathname === "/feed-requests" ? "active" : ""}`}
@ -67,7 +59,6 @@ function App() {
<Routes>
<Route path="/" element={<EpisodeList />} />
<Route path="/feeds" element={<FeedList />} />
<Route path="/categories" element={<CategoryList />} />
<Route path="/feed-requests" element={<FeedManager />} />
<Route path="/rss-endpoints" element={<RSSEndpoints />} />
<Route path="/episode/:episodeId" element={<EpisodeDetail />} />

View File

@ -1,392 +0,0 @@
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
interface Feed {
id: string;
url: string;
title?: string;
description?: string;
category?: string;
lastUpdated?: string;
createdAt: string;
active: boolean;
}
interface CategoryGroup {
[category: string]: Feed[];
}
function CategoryList() {
const [groupedFeeds, setGroupedFeeds] = useState<CategoryGroup>({});
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [filteredFeeds, setFilteredFeeds] = useState<Feed[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchCategoriesAndFeeds();
}, []);
useEffect(() => {
if (selectedCategory && groupedFeeds[selectedCategory]) {
setFilteredFeeds(groupedFeeds[selectedCategory]);
} else {
setFilteredFeeds([]);
}
}, [selectedCategory, groupedFeeds]);
const fetchCategoriesAndFeeds = async () => {
try {
setLoading(true);
setError(null);
// Fetch grouped feeds
const [groupedResponse, categoriesResponse] = await Promise.all([
fetch("/api/feeds/grouped-by-category"),
fetch("/api/categories"),
]);
if (!groupedResponse.ok || !categoriesResponse.ok) {
throw new Error("カテゴリデータの取得に失敗しました");
}
const groupedData = await groupedResponse.json();
await categoriesResponse.json();
setGroupedFeeds(groupedData.groupedFeeds || {});
} catch (err) {
console.error("Category fetch error:", err);
setError(err instanceof Error ? err.message : "エラーが発生しました");
} finally {
setLoading(false);
}
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString("ja-JP");
};
const getFeedCount = (category: string) => {
return groupedFeeds[category]?.length || 0;
};
if (loading) {
return <div className="loading">...</div>;
}
if (error) {
return <div className="error">{error}</div>;
}
const availableCategories = Object.keys(groupedFeeds).filter(
(category) => groupedFeeds[category] && groupedFeeds[category].length > 0,
);
if (availableCategories.length === 0) {
return (
<div className="empty-state">
<p></p>
<p>
</p>
<button
className="btn btn-secondary"
onClick={fetchCategoriesAndFeeds}
style={{ marginTop: "10px" }}
>
</button>
</div>
);
}
return (
<div>
<div
style={{
marginBottom: "20px",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<h2> ({availableCategories.length})</h2>
<button className="btn btn-secondary" onClick={fetchCategoriesAndFeeds}>
</button>
</div>
{!selectedCategory ? (
<div className="category-grid">
{availableCategories.map((category) => (
<div key={category} className="category-card">
<div className="category-header">
<h3
className="category-title"
onClick={() => setSelectedCategory(category)}
>
{category}
</h3>
<div className="category-stats">
<span className="feed-count">
{getFeedCount(category)}
</span>
</div>
</div>
<div className="category-feeds-preview">
{groupedFeeds[category]?.slice(0, 3).map((feed) => (
<div key={feed.id} className="feed-preview">
<Link
to={`/feeds/${feed.id}`}
className="feed-preview-link"
>
{feed.title || feed.url}
</Link>
</div>
))}
{getFeedCount(category) > 3 && (
<div className="more-feeds">
{getFeedCount(category) - 3} ...
</div>
)}
</div>
<div className="category-actions">
<button
className="btn btn-primary"
onClick={() => setSelectedCategory(category)}
>
</button>
</div>
</div>
))}
</div>
) : (
<div>
<div className="category-detail-header">
<button
className="btn btn-secondary"
onClick={() => setSelectedCategory(null)}
>
</button>
<h3>: {selectedCategory}</h3>
<p>{filteredFeeds.length} </p>
</div>
<div className="feed-grid">
{filteredFeeds.map((feed) => (
<div key={feed.id} className="feed-card">
<div className="feed-card-header">
<h4 className="feed-title">
<Link to={`/feeds/${feed.id}`} className="feed-link">
{feed.title || feed.url}
</Link>
</h4>
<div className="feed-url">
<a
href={feed.url}
target="_blank"
rel="noopener noreferrer"
>
{feed.url}
</a>
</div>
</div>
{feed.description && (
<div className="feed-description">{feed.description}</div>
)}
<div className="feed-meta">
<div>: {formatDate(feed.createdAt)}</div>
{feed.lastUpdated && (
<div>: {formatDate(feed.lastUpdated)}</div>
)}
</div>
<div className="feed-actions">
<Link to={`/feeds/${feed.id}`} className="btn btn-primary">
</Link>
</div>
</div>
))}
</div>
</div>
)}
<style>{`
.category-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.category-card {
border: 1px solid #e9ecef;
border-radius: 12px;
padding: 20px;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.category-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}
.category-header {
margin-bottom: 15px;
}
.category-title {
margin: 0 0 8px 0;
font-size: 20px;
color: #2c3e50;
cursor: pointer;
transition: color 0.2s ease;
}
.category-title:hover {
color: #007bff;
}
.category-stats {
display: flex;
gap: 15px;
font-size: 14px;
color: #6c757d;
}
.feed-count {
background: #f8f9fa;
padding: 4px 8px;
border-radius: 4px;
}
.category-feeds-preview {
margin-bottom: 15px;
}
.feed-preview {
margin-bottom: 6px;
font-size: 14px;
}
.feed-preview-link {
color: #007bff;
text-decoration: none;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.feed-preview-link:hover {
text-decoration: underline;
}
.more-feeds {
font-size: 12px;
color: #6c757d;
font-style: italic;
}
.category-actions {
text-align: right;
}
.category-detail-header {
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 1px solid #e9ecef;
}
.category-detail-header h3 {
margin: 10px 0 5px 0;
color: #2c3e50;
}
.category-detail-header p {
margin: 0;
color: #6c757d;
}
.feed-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.feed-card {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.feed-card-header {
margin-bottom: 15px;
}
.feed-title {
margin: 0 0 8px 0;
font-size: 16px;
}
.feed-link {
text-decoration: none;
color: #007bff;
}
.feed-link:hover {
text-decoration: underline;
}
.feed-url {
font-size: 12px;
color: #666;
word-break: break-all;
}
.feed-url a {
color: #666;
text-decoration: none;
}
.feed-url a:hover {
color: #007bff;
}
.feed-description {
margin-bottom: 15px;
color: #333;
line-height: 1.5;
font-size: 14px;
}
.feed-meta {
margin-bottom: 15px;
font-size: 12px;
color: #666;
}
.feed-meta div {
margin-bottom: 4px;
}
.feed-actions {
text-align: right;
}
`}</style>
</div>
);
}
export default CategoryList;