feat: add import functionality for channel configurations and export option for channels

This commit is contained in:
2026-03-11 21:37:18 +01:00
parent 8cc3439d2e
commit 37167fc19c
3 changed files with 350 additions and 6 deletions

View File

@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Plus } from "lucide-react";
import { Plus, Upload } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
useChannels,
@@ -10,14 +10,20 @@ import {
useDeleteChannel,
useGenerateSchedule,
} from "@/hooks/use-channels";
import { useAuthContext } from "@/context/auth-context";
import { api } from "@/lib/api";
import { useQueryClient } from "@tanstack/react-query";
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 type { ChannelResponse, ProgrammingBlock, RecyclePolicy } from "@/lib/types";
export default function DashboardPage() {
const { token } = useAuthContext();
const queryClient = useQueryClient();
const { data: channels, isLoading, error } = useChannels();
const createChannel = useCreateChannel();
@@ -26,6 +32,9 @@ export default function DashboardPage() {
const generateSchedule = useGenerateSchedule();
const [createOpen, setCreateOpen] = useState(false);
const [importOpen, setImportOpen] = useState(false);
const [importPending, setImportPending] = useState(false);
const [importError, setImportError] = useState<string | null>(null);
const [editChannel, setEditChannel] = useState<ChannelResponse | null>(null);
const [deleteTarget, setDeleteTarget] = useState<ChannelResponse | null>(null);
const [scheduleChannel, setScheduleChannel] = useState<ChannelResponse | null>(null);
@@ -57,6 +66,46 @@ export default function DashboardPage() {
);
};
const handleImport = async (data: ChannelImportData) => {
if (!token) return;
setImportPending(true);
setImportError(null);
try {
const created = await api.channels.create(
{ name: data.name, timezone: data.timezone, description: data.description },
token,
);
await api.channels.update(
created.id,
{ schedule_config: { blocks: data.blocks }, recycle_policy: data.recycle_policy },
token,
);
await queryClient.invalidateQueries({ queryKey: ["channels"] });
setImportOpen(false);
} catch (e) {
setImportError(e instanceof Error ? e.message : "Import failed");
} finally {
setImportPending(false);
}
};
const handleExport = (channel: ChannelResponse) => {
const payload = {
name: channel.name,
description: channel.description ?? undefined,
timezone: channel.timezone,
blocks: channel.schedule_config.blocks,
recycle_policy: channel.recycle_policy,
};
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${channel.name.toLowerCase().replace(/\s+/g, "-")}.json`;
a.click();
URL.revokeObjectURL(url);
};
const handleDelete = () => {
if (!deleteTarget) return;
deleteChannel.mutate(deleteTarget.id, {
@@ -74,10 +123,16 @@ export default function DashboardPage() {
Build your broadcast lineup
</p>
</div>
<Button onClick={() => setCreateOpen(true)}>
<Plus className="size-4" />
New channel
</Button>
<div className="flex gap-2">
<Button variant="outline" onClick={() => setImportOpen(true)} className="border-zinc-700 text-zinc-300 hover:text-zinc-100">
<Upload className="size-4" />
Import
</Button>
<Button onClick={() => setCreateOpen(true)}>
<Plus className="size-4" />
New channel
</Button>
</div>
</div>
{/* Content */}
@@ -117,12 +172,21 @@ export default function DashboardPage() {
onDelete={() => setDeleteTarget(channel)}
onGenerateSchedule={() => generateSchedule.mutate(channel.id)}
onViewSchedule={() => setScheduleChannel(channel)}
onExport={() => handleExport(channel)}
/>
))}
</div>
)}
{/* Dialogs / sheets */}
<ImportChannelDialog
open={importOpen}
onOpenChange={(open) => { if (!open) { setImportOpen(false); setImportError(null); } }}
onSubmit={handleImport}
isPending={importPending}
error={importError}
/>
<CreateChannelDialog
open={createOpen}
onOpenChange={setCreateOpen}