"use client"; import type { ActiveJobsPayload } from "yt-dlp-transcript-common/views/activeJobs"; import type { WorkersPayload } from "yt-dlp-transcript-common/views/workers"; import type { WidgetActionablePayload } from "yt-dlp-transcript-common/views/widgetActionable"; import type { WidgetSyncPayload } from "yt-dlp-transcript-common/views/widgetSync"; import { usePolledPayload, useNow } from "../../lib/usePolledPayload"; import { PipelineBand } from "./PipelineBand"; import { NeedsWorkPanel } from "./NeedsWorkPanel"; import { QuickAddPanel } from "./QuickAddPanel"; import type { ChannelRowView } from "yt-dlp-transcript-common/views/channelRow"; import { ChannelsTable } from "../../channels/components/ChannelsTable"; import { DASHBOARD_COLUMNS, type ChannelRowExtras, } from "../../channels/components/channelColumns"; import { ChannelSyncButton } from "../../channels/components/ChannelSyncButton"; import { InlineActionButton } from "../actions/InlineActionButton"; import { PrioritizeButton } from "../actions/PrioritizeButton"; // Poll cadences mirror the monitor widget: job/worker state moves by the second; // the worklist and sync/scheduler markers move by the minute, so they poll no // faster than every 15s regardless. const FAST_MS = 1000; const SLOW_MS = 15000; export type DashboardCockpitInitial = { jobs: ActiveJobsPayload; workers: WorkersPayload; actionable: WidgetActionablePayload; sync: WidgetSyncPayload; // Built server-side in app/page.tsx and passed straight through — never into // client state — so a global AutoRefresh re-seeds them live. channels: ChannelRowView[]; changelogSource: string | null; }; // The mission-control cockpit. A server component (app/page.tsx) SSRs the // initial payloads for a flicker-free first paint; this client shell then polls // the same /api endpoints the widget uses so the whole surface updates in place. export function DashboardCockpit({ initial, }: { initial: DashboardCockpitInitial; }) { const now = useNow(); const { data: jobs } = usePolledPayload( "/api/view/activeJobs", true, FAST_MS, initial.jobs, ); const { data: workers, refetch: refetchWorkers } = usePolledPayload( "/api/view/workers", true, FAST_MS, initial.workers, ); const { data: actionable } = usePolledPayload( "/api/view/widgetActionable", true, SLOW_MS, initial.actionable, ); const { data: sync, refetch: refetchSync } = usePolledPayload( "/api/view/widgetSync", true, SLOW_MS, initial.sync, ); return (

Dashboard

); } // The dashboard's channels table: the shared channel table with six columns, // a relative "last sync" that ticks live, and per-row inline actions (Sync, // Download missing, and a one-click "Top of queue" that prioritizes the channel // for auto-download). function DashboardChannels({ channels, now, }: { channels: ChannelRowView[]; now: number | null; }) { if (channels.length === 0) return null; const rowExtras: Record = {}; for (const c of channels) { rowExtras[c.slug] = { actions: (
{c.hasUrl ? ( <> {c.work.undownloaded > 0 && ( )} ) : ( no URL )}
), }; } return (

Channels

); }