"use client"; import Link from "next/link"; import { useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { Tv } from "lucide-react"; import { api, ApiRequestError } 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, isLoaded } = useAuthContext(); const { data: slots, isError, error, isPending, isFetching, } = useQuery({ queryKey: ["guide-epg", channel.id, token], 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); }, enabled: isLoaded, refetchInterval: 30_000, retry: false, }); // eslint-disable-next-line react-hooks/purity -- Date.now() inside useMemo is stable for EPG slot matching const now = useMemo(() => 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 (
Loading…
) : isError && error instanceof ApiRequestError && error.status === 401 ? (Sign in to view this channel
) : ({isError || !slots?.length ? "No signal — schedule may not be generated yet" : "Nothing airing right now"}
)} {/* Upcoming */} {upcoming.length > 0 && (Loading channels…
} {!isLoading && channels?.length === 0 && (No channels yet.{" "} Create one in the dashboard.
)}