"use client"; import Link from "next/link"; import { useQuery } from "@tanstack/react-query"; import { Tv } from "lucide-react"; import { api } from "@/lib/api"; import { useChannels } from "@/hooks/use-channels"; import { useAuthContext } from "@/context/auth-context"; import type { ChannelResponse, ScheduledSlotResponse } from "@/lib/types"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function fmtTime(iso: string) { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } function fmtDuration(secs: number) { const h = Math.floor(secs / 3600); const m = Math.floor((secs % 3600) / 60); return h > 0 ? `${h}h ${m}m` : `${m}m`; } function slotLabel(slot: ScheduledSlotResponse) { const { item } = slot; if (item.content_type === "episode" && item.series_name) { const ep = [ item.season_number != null ? `S${item.season_number}` : "", item.episode_number != null ? `E${String(item.episode_number).padStart(2, "0")}` : "", ] .filter(Boolean) .join(""); return ep ? `${item.series_name} ${ep} – ${item.title}` : `${item.series_name} – ${item.title}`; } return item.year ? `${item.title} (${item.year})` : item.title; } // --------------------------------------------------------------------------- // ChannelRow — fetches its own EPG slice and renders current + upcoming // --------------------------------------------------------------------------- function ChannelRow({ channel }: { channel: ChannelResponse }) { const { token } = useAuthContext(); const { data: slots, isError, isPending } = useQuery({ queryKey: ["guide-epg", channel.id], queryFn: () => { const now = new Date(); const from = now.toISOString(); const until = new Date(now.getTime() + 4 * 60 * 60 * 1000).toISOString(); return api.schedule.getEpg(channel.id, token ?? "", from, until); }, refetchInterval: 30_000, retry: false, }); const now = Date.now(); const current = slots?.find( (s) => new Date(s.start_at).getTime() <= now && now < new Date(s.end_at).getTime(), ); const upcoming = slots?.filter((s) => new Date(s.start_at).getTime() > now).slice(0, 3) ?? []; const progress = current ? (now - new Date(current.start_at).getTime()) / (new Date(current.end_at).getTime() - new Date(current.start_at).getTime()) : 0; const remaining = current ? Math.ceil((new Date(current.end_at).getTime() - now) / 60_000) : 0; return (
{/* Channel header */}
{channel.name} {channel.description && ( — {channel.description} )}
Watch →
{/* Currently airing */} {current ? (
Now {slotLabel(current)}
{remaining}m left
) : isPending ? (

Loading…

) : (

{isError || !slots?.length ? "No signal — schedule may not be generated yet" : "Nothing airing right now"}

)} {/* Upcoming */} {upcoming.length > 0 && (
    {upcoming.map((slot) => (
  • {fmtTime(slot.start_at)} {slotLabel(slot)}
    {fmtDuration(slot.item.duration_secs)}
  • ))}
)}
); } // --------------------------------------------------------------------------- // Page // --------------------------------------------------------------------------- export default function GuidePage() { const { data: channels, isLoading } = useChannels(); const now = new Date(); const timeLabel = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); const dateLabel = now.toLocaleDateString([], { weekday: "long", month: "long", day: "numeric", }); return (

Channel Guide

{dateLabel} · {timeLabel}
{isLoading &&

Loading channels…

} {!isLoading && channels?.length === 0 && (

No channels yet.{" "} Create one in the dashboard.

)}
{channels?.map((channel) => ( ))}
); }