"use client"; import { useState } from "react"; import { toast } from "sonner"; import { useChannels, useCreateChannel, useUpdateChannel, useDeleteChannel, useGenerateSchedule, } from "@/hooks/use-channels"; import { useAuthContext } from "@/context/auth-context"; import { useConfig } from "@/hooks/use-config"; import { useRescanLibrary } from "@/hooks/use-library"; import { useChannelOrder } from "@/hooks/use-channel-order"; import { useImportChannel } from "@/hooks/use-import-channel"; import { useRegenerateAllSchedules } from "@/hooks/use-regenerate-all"; import { exportChannel } from "@/lib/channel-export"; import { DashboardHeader } from "./components/dashboard-header"; import { ChannelCard } from "./components/channel-card"; import { CreateChannelDialog } from "./components/create-channel-dialog"; import { DeleteChannelDialog } from "./components/delete-channel-dialog"; import { EditChannelSheet } from "./components/edit-channel-sheet"; import { ScheduleSheet } from "./components/schedule-sheet"; import { ImportChannelDialog, type ChannelImportData, } from "./components/import-channel-dialog"; import { IptvExportDialog } from "./components/iptv-export-dialog"; import { TranscodeSettingsDialog } from "./components/transcode-settings-dialog"; import type { ChannelResponse, ProgrammingBlock, RecyclePolicy, } from "@/lib/types"; export default function DashboardPage() { const { token } = useAuthContext(); const { data: channels, isLoading, error } = useChannels(); const { data: config } = useConfig(); const capabilities = config?.provider_capabilities; const createChannel = useCreateChannel(); const updateChannel = useUpdateChannel(); const deleteChannel = useDeleteChannel(); const generateSchedule = useGenerateSchedule(); const rescanLibrary = useRescanLibrary(); const { sortedChannels, handleMoveUp, handleMoveDown } = useChannelOrder(channels); const { isRegeneratingAll, handleRegenerateAll } = useRegenerateAllSchedules(channels, token); const importChannel = useImportChannel(token); // Dialog state const [iptvOpen, setIptvOpen] = useState(false); const [transcodeOpen, setTranscodeOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false); const [importOpen, setImportOpen] = useState(false); const [editChannel, setEditChannel] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [scheduleChannel, setScheduleChannel] = useState(null); const handleCreate = (data: { name: string; timezone: string; description: string; access_mode?: import("@/lib/types").AccessMode; access_password?: string; }) => { createChannel.mutate( { name: data.name, timezone: data.timezone, description: data.description || undefined, access_mode: data.access_mode, access_password: data.access_password, }, { onSuccess: () => setCreateOpen(false) }, ); }; const handleEdit = ( id: string, data: { name: string; description: string; timezone: string; schedule_config: { blocks: ProgrammingBlock[] }; recycle_policy: RecyclePolicy; auto_schedule: boolean; access_mode?: import("@/lib/types").AccessMode; access_password?: string; logo?: string | null; logo_position?: import("@/lib/types").LogoPosition; logo_opacity?: number; webhook_url?: string | null; webhook_poll_interval_secs?: number; webhook_body_template?: string | null; webhook_headers?: string | null; }, ) => { updateChannel.mutate( { id, data }, { onSuccess: () => setEditChannel(null) }, ); }; const handleImport = async (data: ChannelImportData) => { const ok = await importChannel.handleImport(data); if (ok) setImportOpen(false); }; const handleDelete = () => { if (!deleteTarget) return; deleteChannel.mutate(deleteTarget.id, { onSuccess: () => setDeleteTarget(null), }); }; const canTranscode = !!config?.providers?.some((p) => p.capabilities.transcode); const canRescan = !!capabilities?.rescan; return (
0} canTranscode={canTranscode} canRescan={canRescan} isRegeneratingAll={isRegeneratingAll} isRescanPending={rescanLibrary.isPending} capabilities={capabilities} onTranscodeOpen={() => setTranscodeOpen(true)} onRescan={() => rescanLibrary.mutate(undefined, { onSuccess: (d) => toast.success(`Rescan complete: ${d.items_found} files found`), onError: () => toast.error("Rescan failed"), }) } onRegenerateAll={handleRegenerateAll} onIptvOpen={() => setIptvOpen(true)} onImportOpen={() => setImportOpen(true)} onCreateOpen={() => setCreateOpen(true)} /> {isLoading && (
)} {error && (
{error.message}
)} {!isLoading && channels && channels.length === 0 && (

No channels yet

)} {sortedChannels.length > 0 && (
{sortedChannels.map((channel, idx) => ( setEditChannel(channel)} onDelete={() => setDeleteTarget(channel)} onGenerateSchedule={() => generateSchedule.mutate(channel.id)} onViewSchedule={() => setScheduleChannel(channel)} onExport={() => exportChannel(channel)} onMoveUp={() => handleMoveUp(channel.id)} onMoveDown={() => handleMoveDown(channel.id)} /> ))}
)} {/* Dialogs / sheets */} {token && ( )} { if (!open) { setImportOpen(false); importChannel.clearError(); } }} onSubmit={handleImport} isPending={importChannel.isPending} error={importChannel.error} /> { if (!open) setEditChannel(null); }} onSubmit={handleEdit} isPending={updateChannel.isPending} error={updateChannel.error?.message} providers={config?.providers ?? []} /> { if (!open) setScheduleChannel(null); }} /> {deleteTarget && ( { if (!open) setDeleteTarget(null); }} onConfirm={handleDelete} isPending={deleteChannel.isPending} /> )}
); }