feat: add import functionality for channel configurations and export option for channels
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user