import type { CalendarEntryState, SubscriptionCalendarEntry, SubscriptionMediaKind } from "@umt/contracts"; import { CalendarDays, ChevronLeft, ChevronRight } from "lucide-react"; import { Link } from "react-router"; import { useSubscriptionCalendar } from "~/api/subscriptions.ts"; import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { Button } from "~/components/ui/button.tsx"; import { cn } from "~/lib/utils.ts"; import { CALENDAR_STATE_LABEL, entriesByDate, monthGrid, monthKey, monthStart, SUB_KIND_LABEL, shiftMonth, todayKey, } from "./model.ts"; // 订阅日历(设计 §4):月视图,事件 = 追踪中订阅范围内集的播出日期(同日同季聚合),电影取上映日期。 // 状态色只表示入库状态:已入库 success、部分 warning、未入库 destructive、未播出无彩。条目链到订阅详情。 const WEEKDAYS = ["一", "二", "三", "四", "五", "六", "日"]; const STATE_CLS: Record = { have: "border-success/40 bg-success/10", partial: "border-warning/40 bg-warning/10", missing: "border-destructive/40 bg-destructive/10", upcoming: "border-border bg-foreground/[0.04]", }; const DOT_CLS: Record = { have: "bg-success", partial: "bg-warning", missing: "bg-destructive", upcoming: "bg-muted-foreground/50", }; function Entry({ e }: { e: SubscriptionCalendarEntry }) { return ( 1 ? ` ${e.have}/${e.total}` : ""}`} className={cn( "flex min-w-0 items-center gap-1 rounded-tight border px-1.5 py-0.5 text-meta text-foreground transition-colors duration-micro hover:bg-foreground/[0.06]", STATE_CLS[e.state], )} > {e.title} {e.label} {CALENDAR_STATE_LABEL[e.state]} ); } export function SubscriptionCalendarView({ month, onMonth, mediaKind, }: { /** YYYY-MM;空 = 本月 */ month: string; onMonth: (m: string) => void; mediaKind?: SubscriptionMediaKind | undefined; }) { const first = monthStart(month); const grid = monthGrid(first); const from = grid[0]!.date; const to = grid.at(-1)!.date; const q = useSubscriptionCalendar(from, to, mediaKind); const byDate = entriesByDate(q.data?.entries ?? []); const today = todayKey(); const title = `${first.getFullYear()} 年 ${first.getMonth() + 1} 月`; const unknown = q.data?.unknown ?? []; return (

{title}

    {(Object.keys(CALENDAR_STATE_LABEL) as CalendarEntryState[]).map((k) => (
  • {CALENDAR_STATE_LABEL[k]}
  • ))}
{q.isPending ? ( ) : q.isError ? ( q.refetch()} /> ) : ( <>
{WEEKDAYS.map((w) => (
周{w}
))} {grid.map((d, i) => { const list = byDate.get(d.date) ?? []; return (
{list.map((e) => ( ))}
); })}
{!q.data?.entries.length && !unknown.length ? ( ) : null} {unknown.length ? (

播出日期未知 {unknown.length}

TMDB 还没有给出这些集的播出日期,元数据刷新后更新。

    {unknown.map((u) => (
  • {u.title} {SUB_KIND_LABEL[u.mediaKind]} {u.seasonNumber !== null ? ` · S${String(u.seasonNumber).padStart(2, "0")}` : ""} {u.episodes.length ? ` · ${u.episodes.length} 集` : ""}
  • ))}
) : null} )}
); }