import { useEffect, useMemo, useState } from "react"; type Show = { id: number; ep_num: number; ep_title: string; season_name: string; start_time: string; playback_length: string; date_created: string; }; const GET_URL = "/api/v1/shows"; const POST_URL = "/api/v1/current"; const HHMM = /^(\d{1,2}):([0-5]\d)$/; function toHHMMSS(v: string): string | null { const m = v.trim().match(HHMM); if (!m) return null; const h = Math.abs(parseInt(m[1], 10)); if (h > 23) return null; const hh = String(h).padStart(2, "0"); const mm = m[2]; return `${hh}:${mm}:00`; } function toHHMM(v: string): string | null { const m = v.trim().match(/^(\d{1,2}):([0-5]\d)$/); if (!m) return null; const h = Number(m[1]); if (h > 23) return null; return `${String(h).padStart(2, "0")}:${m[2]}`; } export default function ShowsPage() { const [shows, setShows] = useState([]); const [loading, setLoading] = useState(true); const [posting, setPosting] = useState(false); const [error, setError] = useState(null); // フォーム状態 const [selectedId, setSelectedId] = useState(null); const [startTime, setStartTime] = useState(""); function formatPlaybackLen(v: string): string { const parts = v.split(":").map(Number); if (parts.length === 2) { // already MM:SS const [mm, ss] = parts; return `${String(mm).padStart(2, "0")}:${String(ss).padStart(2, "0")}`; } if (parts.length === 3) { const [hh, mm, ss] = parts; if (hh <= 0) { // show MM:SS when hours are zero return `${String(mm).padStart(2, "0")}:${String(ss).padStart(2, "0")}`; } // show HH:MM:SS when hours exist return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}:${String(ss).padStart(2, "0")}`; } return v; // fallback (unexpected format) } // 一覧取得 useEffect(() => { let cancelled = false; (async () => { try { setLoading(true); const res = await fetch(GET_URL, { cache: "no-store" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = (await res.json()) as Show[]; if (!cancelled) setShows(data); } catch (e: any) { if (!cancelled) setError(e.message || "番組一覧の取得に失敗しました。"); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, []); const current = useMemo(() => shows.find(s => s.id === selectedId) || null, [shows, selectedId]); async function submit() { setError(null); if (!selectedId) { setError("エピソードを選択してください。"); return; } let payload: any = { id: selectedId }; if (startTime) { const normalized = toHHMMSS(startTime); if (!normalized) { setError("開始時刻は HH:MM の形式で入力してください。"); return; } payload.start_time = normalized; // API expects HH:MM:SS } try { setPosting(true); const res = await fetch(POST_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) throw new Error(`POST 失敗 (${res.status})`); setStartTime(""); } catch (e: any) { setError(e.message || "現在のエピソード設定に失敗しました。"); } finally { setPosting(false); } } return (

エピソード一覧

エピソードを選択し、必要であれば開始時刻(HH:MM)を入力して「現在のエピソードに設定」を押してください。

{loading &&
読み込み中…
} {error &&
{error}
} {!loading && shows.length === 0 &&
エピソードがありません。
} {shows.length > 0 && (
{shows.map(s => ( ))}
)}
setStartTime(e.target.value)} onBlur={(e) => { const v = toHHMM(e.target.value); if (v) setStartTime(v); }} />
{current && (
選択中:第{current.ep_num}話「{current.ep_title}」
)}
); }