Add searching feature
This commit is contained in:
@ -91,25 +91,33 @@ function App() {
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [feedsRes, statsRes, envRes, requestsRes, episodesRes] = await Promise.all([
|
||||
fetch("/api/admin/feeds"),
|
||||
fetch("/api/admin/stats"),
|
||||
fetch("/api/admin/env"),
|
||||
fetch("/api/admin/feed-requests"),
|
||||
fetch("/api/admin/episodes"),
|
||||
]);
|
||||
const [feedsRes, statsRes, envRes, requestsRes, episodesRes] =
|
||||
await Promise.all([
|
||||
fetch("/api/admin/feeds"),
|
||||
fetch("/api/admin/stats"),
|
||||
fetch("/api/admin/env"),
|
||||
fetch("/api/admin/feed-requests"),
|
||||
fetch("/api/admin/episodes"),
|
||||
]);
|
||||
|
||||
if (!feedsRes.ok || !statsRes.ok || !envRes.ok || !requestsRes.ok || !episodesRes.ok) {
|
||||
if (
|
||||
!feedsRes.ok ||
|
||||
!statsRes.ok ||
|
||||
!envRes.ok ||
|
||||
!requestsRes.ok ||
|
||||
!episodesRes.ok
|
||||
) {
|
||||
throw new Error("Failed to load data");
|
||||
}
|
||||
|
||||
const [feedsData, statsData, envData, requestsData, episodesData] = await Promise.all([
|
||||
feedsRes.json(),
|
||||
statsRes.json(),
|
||||
envRes.json(),
|
||||
requestsRes.json(),
|
||||
episodesRes.json(),
|
||||
]);
|
||||
const [feedsData, statsData, envData, requestsData, episodesData] =
|
||||
await Promise.all([
|
||||
feedsRes.json(),
|
||||
statsRes.json(),
|
||||
envRes.json(),
|
||||
requestsRes.json(),
|
||||
episodesRes.json(),
|
||||
]);
|
||||
|
||||
setFeeds(feedsData);
|
||||
setStats(statsData);
|
||||
@ -580,20 +588,36 @@ function App() {
|
||||
</p>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<div className="stats-grid" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
|
||||
<div
|
||||
className="stats-grid"
|
||||
style={{ gridTemplateColumns: "repeat(3, 1fr)" }}
|
||||
>
|
||||
<div className="stat-card">
|
||||
<div className="value">{episodes.length}</div>
|
||||
<div className="label">総エピソード数</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="value">
|
||||
{episodes.filter(ep => ep.feedCategory).map(ep => ep.feedCategory).filter((category, index, arr) => arr.indexOf(category) === index).length}
|
||||
{
|
||||
episodes
|
||||
.filter((ep) => ep.feedCategory)
|
||||
.map((ep) => ep.feedCategory)
|
||||
.filter(
|
||||
(category, index, arr) =>
|
||||
arr.indexOf(category) === index,
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
<div className="label">カテゴリー数</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="value">
|
||||
{episodes.reduce((acc, ep) => acc + (ep.fileSize || 0), 0) / (1024 * 1024) > 1
|
||||
{episodes.reduce(
|
||||
(acc, ep) => acc + (ep.fileSize || 0),
|
||||
0,
|
||||
) /
|
||||
(1024 * 1024) >
|
||||
1
|
||||
? `${Math.round(episodes.reduce((acc, ep) => acc + (ep.fileSize || 0), 0) / (1024 * 1024))}MB`
|
||||
: `${Math.round(episodes.reduce((acc, ep) => acc + (ep.fileSize || 0), 0) / 1024)}KB`}
|
||||
</div>
|
||||
@ -620,37 +644,79 @@ function App() {
|
||||
<li key={episode.id} className="feed-item">
|
||||
<div className="feed-info">
|
||||
<h3>{episode.title}</h3>
|
||||
<div className="url" style={{ fontSize: "14px", color: "#666" }}>
|
||||
<div
|
||||
className="url"
|
||||
style={{ fontSize: "14px", color: "#666" }}
|
||||
>
|
||||
フィード: {episode.feedTitle || episode.feedUrl}
|
||||
{episode.feedCategory && (
|
||||
<span style={{ marginLeft: "8px", padding: "2px 6px", background: "#e9ecef", borderRadius: "4px", fontSize: "12px" }}>
|
||||
<span
|
||||
style={{
|
||||
marginLeft: "8px",
|
||||
padding: "2px 6px",
|
||||
background: "#e9ecef",
|
||||
borderRadius: "4px",
|
||||
fontSize: "12px",
|
||||
}}
|
||||
>
|
||||
{episode.feedCategory}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="url" style={{ fontSize: "14px", color: "#666" }}>
|
||||
記事: <a href={episode.articleLink} target="_blank" rel="noopener noreferrer">{episode.articleTitle}</a>
|
||||
<div
|
||||
className="url"
|
||||
style={{ fontSize: "14px", color: "#666" }}
|
||||
>
|
||||
記事:{" "}
|
||||
<a
|
||||
href={episode.articleLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{episode.articleTitle}
|
||||
</a>
|
||||
</div>
|
||||
{episode.description && (
|
||||
<div style={{ fontSize: "14px", color: "#777", marginTop: "4px" }}>
|
||||
{episode.description.length > 100
|
||||
? episode.description.substring(0, 100) + "..."
|
||||
<div
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
color: "#777",
|
||||
marginTop: "4px",
|
||||
}}
|
||||
>
|
||||
{episode.description.length > 100
|
||||
? episode.description.substring(0, 100) + "..."
|
||||
: episode.description}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ fontSize: "12px", color: "#999", marginTop: "8px" }}>
|
||||
<span>作成日: {new Date(episode.createdAt).toLocaleString("ja-JP")}</span>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "#999",
|
||||
marginTop: "8px",
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
作成日:{" "}
|
||||
{new Date(episode.createdAt).toLocaleString(
|
||||
"ja-JP",
|
||||
)}
|
||||
</span>
|
||||
{episode.duration && (
|
||||
<>
|
||||
<span style={{ margin: "0 8px" }}>|</span>
|
||||
<span>再生時間: {Math.round(episode.duration / 60)}分</span>
|
||||
<span>
|
||||
再生時間: {Math.round(episode.duration / 60)}
|
||||
分
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{episode.fileSize && (
|
||||
<>
|
||||
<span style={{ margin: "0 8px" }}>|</span>
|
||||
<span>
|
||||
ファイルサイズ: {episode.fileSize > 1024 * 1024
|
||||
ファイルサイズ:{" "}
|
||||
{episode.fileSize > 1024 * 1024
|
||||
? `${Math.round(episode.fileSize / (1024 * 1024))}MB`
|
||||
: `${Math.round(episode.fileSize / 1024)}KB`}
|
||||
</span>
|
||||
@ -658,14 +724,14 @@ function App() {
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<a
|
||||
href={episode.audioPath}
|
||||
target="_blank"
|
||||
<a
|
||||
href={episode.audioPath}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "#007bff",
|
||||
textDecoration: "none"
|
||||
textDecoration: "none",
|
||||
}}
|
||||
>
|
||||
🎵 音声ファイルを再生
|
||||
@ -675,7 +741,9 @@ function App() {
|
||||
<div className="feed-actions">
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={() => deleteEpisode(episode.id, episode.title)}
|
||||
onClick={() =>
|
||||
deleteEpisode(episode.id, episode.title)
|
||||
}
|
||||
>
|
||||
削除
|
||||
</button>
|
||||
|
Reference in New Issue
Block a user