Files
k-tv/k-tv-frontend/app/(main)/dashboard/page.tsx
Gabriel Kaszewski 477de2c49d feat: add schedule sheet and tag input components
- Implemented ScheduleSheet component to display channel schedules with a timeline view.
- Added DayRow subcomponent for rendering daily schedule slots with color coding.
- Integrated ScheduleSheet into the DashboardPage for viewing schedules of selected channels.
- Created TagInput component for managing tags with add and remove functionality.
- Updated package dependencies to include zod version 4.3.6.
2026-03-11 21:14:42 +01:00

161 lines
5.1 KiB
TypeScript

"use client";
import { useState } from "react";
import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
useChannels,
useCreateChannel,
useUpdateChannel,
useDeleteChannel,
useGenerateSchedule,
} from "@/hooks/use-channels";
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 type { ChannelResponse, ProgrammingBlock, RecyclePolicy } from "@/lib/types";
export default function DashboardPage() {
const { data: channels, isLoading, error } = useChannels();
const createChannel = useCreateChannel();
const updateChannel = useUpdateChannel();
const deleteChannel = useDeleteChannel();
const generateSchedule = useGenerateSchedule();
const [createOpen, setCreateOpen] = useState(false);
const [editChannel, setEditChannel] = useState<ChannelResponse | null>(null);
const [deleteTarget, setDeleteTarget] = useState<ChannelResponse | null>(null);
const [scheduleChannel, setScheduleChannel] = useState<ChannelResponse | null>(null);
const handleCreate = (data: {
name: string;
timezone: string;
description: string;
}) => {
createChannel.mutate(
{ name: data.name, timezone: data.timezone, description: data.description || undefined },
{ onSuccess: () => setCreateOpen(false) },
);
};
const handleEdit = (
id: string,
data: {
name: string;
description: string;
timezone: string;
schedule_config: { blocks: ProgrammingBlock[] };
recycle_policy: RecyclePolicy;
},
) => {
updateChannel.mutate(
{ id, data },
{ onSuccess: () => setEditChannel(null) },
);
};
const handleDelete = () => {
if (!deleteTarget) return;
deleteChannel.mutate(deleteTarget.id, {
onSuccess: () => setDeleteTarget(null),
});
};
return (
<div className="mx-auto w-full max-w-5xl space-y-6 px-6 py-8">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-xl font-semibold text-zinc-100">My Channels</h1>
<p className="mt-0.5 text-sm text-zinc-500">
Build your broadcast lineup
</p>
</div>
<Button onClick={() => setCreateOpen(true)}>
<Plus className="size-4" />
New channel
</Button>
</div>
{/* Content */}
{isLoading && (
<div className="flex items-center justify-center py-20">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-zinc-700 border-t-zinc-300" />
</div>
)}
{error && (
<div className="rounded-lg border border-red-900/50 bg-red-950/20 px-4 py-3 text-sm text-red-400">
{error.message}
</div>
)}
{channels && channels.length === 0 && (
<div className="flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-zinc-800 py-20 text-center">
<p className="text-sm text-zinc-500">No channels yet</p>
<Button variant="outline" onClick={() => setCreateOpen(true)}>
<Plus className="size-4" />
Create your first channel
</Button>
</div>
)}
{channels && channels.length > 0 && (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{channels.map((channel) => (
<ChannelCard
key={channel.id}
channel={channel}
isGenerating={
generateSchedule.isPending &&
generateSchedule.variables === channel.id
}
onEdit={() => setEditChannel(channel)}
onDelete={() => setDeleteTarget(channel)}
onGenerateSchedule={() => generateSchedule.mutate(channel.id)}
onViewSchedule={() => setScheduleChannel(channel)}
/>
))}
</div>
)}
{/* Dialogs / sheets */}
<CreateChannelDialog
open={createOpen}
onOpenChange={setCreateOpen}
onSubmit={handleCreate}
isPending={createChannel.isPending}
error={createChannel.error?.message}
/>
<EditChannelSheet
channel={editChannel}
open={!!editChannel}
onOpenChange={(open) => { if (!open) setEditChannel(null); }}
onSubmit={handleEdit}
isPending={updateChannel.isPending}
error={updateChannel.error?.message}
/>
<ScheduleSheet
channel={scheduleChannel}
open={!!scheduleChannel}
onOpenChange={(open) => { if (!open) setScheduleChannel(null); }}
/>
{deleteTarget && (
<DeleteChannelDialog
channelName={deleteTarget.name}
open={!!deleteTarget}
onOpenChange={(open) => { if (!open) setDeleteTarget(null); }}
onConfirm={handleDelete}
isPending={deleteChannel.isPending}
/>
)}
</div>
);
}