106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import type { MediaItemResponse } from "@/lib/types";
|
||
|
||
interface ChannelInfoProps {
|
||
channelNumber: number;
|
||
channelName: string;
|
||
item: MediaItemResponse;
|
||
showStartTime: string;
|
||
showEndTime: string;
|
||
/** Progress through the current show, 0–100 */
|
||
progress: number;
|
||
}
|
||
|
||
function formatEpisodeLabel(item: MediaItemResponse): string | null {
|
||
if (item.content_type !== "episode") return null;
|
||
const parts: string[] = [];
|
||
if (item.season_number != null) parts.push(`S${item.season_number}`);
|
||
if (item.episode_number != null) parts.push(`E${item.episode_number}`);
|
||
return parts.length > 0 ? parts.join(" · ") : null;
|
||
}
|
||
|
||
export function ChannelInfo({
|
||
channelNumber,
|
||
channelName,
|
||
item,
|
||
showStartTime,
|
||
showEndTime,
|
||
progress,
|
||
}: ChannelInfoProps) {
|
||
const clampedProgress = Math.min(100, Math.max(0, progress));
|
||
|
||
const isEpisode = item.content_type === "episode";
|
||
const episodeLabel = formatEpisodeLabel(item);
|
||
// For episodes: series name as headline (fall back to episode title if missing)
|
||
const headline = isEpisode && item.series_name ? item.series_name : item.title;
|
||
// Subtitle: only include episode title when series name is the headline (otherwise
|
||
// the title is already the headline and repeating it would duplicate it)
|
||
const subtitle = isEpisode
|
||
? item.series_name
|
||
? [episodeLabel, item.title].filter(Boolean).join(" · ")
|
||
: episodeLabel // title is the headline — just show S·E label
|
||
: item.year
|
||
? String(item.year)
|
||
: null;
|
||
|
||
return (
|
||
<div className="flex flex-col gap-2 rounded-lg bg-black/60 p-4 backdrop-blur-md w-80">
|
||
{/* Channel badge */}
|
||
<div className="flex items-center gap-2">
|
||
<span className="flex h-7 min-w-9 items-center justify-center rounded bg-white px-1.5 font-mono text-xs font-bold text-black">
|
||
{channelNumber}
|
||
</span>
|
||
<span className="truncate text-sm font-medium text-zinc-300">
|
||
{channelName}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Title block */}
|
||
<div className="space-y-0.5">
|
||
<p className="text-base font-semibold leading-tight text-white">
|
||
{headline}
|
||
</p>
|
||
{subtitle && (
|
||
<p className="text-xs text-zinc-400">{subtitle}</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Description */}
|
||
{item.description && (
|
||
<p className="line-clamp-2 text-xs leading-relaxed text-zinc-500">
|
||
{item.description}
|
||
</p>
|
||
)}
|
||
|
||
{/* Genres */}
|
||
{item.genres.length > 0 && (
|
||
<div className="flex flex-wrap gap-1">
|
||
{item.genres.slice(0, 4).map((g) => (
|
||
<span
|
||
key={g}
|
||
className="rounded bg-zinc-800/80 px-1.5 py-0.5 text-[10px] text-zinc-400"
|
||
>
|
||
{g}
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Progress bar */}
|
||
<div className="flex flex-col gap-1">
|
||
<div className="h-1 w-full overflow-hidden rounded-full bg-zinc-700">
|
||
<div
|
||
className="h-full rounded-full bg-white transition-all duration-500"
|
||
style={{ width: `${clampedProgress}%` }}
|
||
/>
|
||
</div>
|
||
<div className="flex justify-between font-mono text-[10px] text-zinc-500">
|
||
<span>{showStartTime}</span>
|
||
<span>{showEndTime}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export type { ChannelInfoProps };
|