feat(frontend): schedule history dialog with rollback, wire ConfigHistorySheet

This commit is contained in:
2026-03-17 14:48:39 +01:00
parent ba6abad602
commit 6d350940b9
4 changed files with 125 additions and 1 deletions

View File

@@ -11,6 +11,7 @@ import {
Download,
ChevronUp,
ChevronDown,
History,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { useActiveSchedule } from "@/hooks/use-channels";
@@ -29,6 +30,7 @@ interface ChannelCardProps {
onExport: () => void;
onMoveUp: () => void;
onMoveDown: () => void;
onScheduleHistory: () => void;
}
function useScheduleStatus(channelId: string) {
@@ -69,6 +71,7 @@ export function ChannelCard({
onExport,
onMoveUp,
onMoveDown,
onScheduleHistory,
}: ChannelCardProps) {
const [confirmOpen, setConfirmOpen] = useState(false);
const blockCount = Object.values(channel.schedule_config.day_blocks).reduce(
@@ -185,6 +188,15 @@ export function ChannelCard({
>
<CalendarDays className="size-3.5" />
</Button>
<Button
size="icon-sm"
variant="ghost"
onClick={onScheduleHistory}
title="Schedule history"
className="text-zinc-600 hover:text-zinc-200"
>
<History className="size-3.5" />
</Button>
<Button
size="icon-sm"
asChild

View File

@@ -15,6 +15,7 @@ import { RecyclePolicyEditor } from "./recycle-policy-editor";
import { WebhookEditor } from "./webhook-editor";
import { AccessSettingsEditor } from "./access-settings-editor";
import { LogoEditor } from "./logo-editor";
import { ConfigHistorySheet } from "./config-history-sheet";
import { useChannelForm } from "@/hooks/use-channel-form";
import { channelFormSchema, extractErrors } from "@/lib/schemas";
import type { FieldErrors } from "@/lib/schemas";
@@ -748,7 +749,13 @@ export function EditChannelSheet({
</Button>
</div>
</div>
{/* TODO: ConfigHistorySheet — wired in Task 16 */}
{channel && (
<ConfigHistorySheet
channelId={channel.id}
open={configHistoryOpen}
onOpenChange={setConfigHistoryOpen}
/>
)}
</form>
</SheetContent>
</Sheet>

View File

@@ -0,0 +1,94 @@
'use client'
import { useState } from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { useScheduleHistory, useRollbackSchedule } from '@/hooks/use-channels'
interface Props {
channelId: string
open: boolean
onOpenChange: (open: boolean) => void
}
const fmtDateRange = (from: string, until: string) =>
`${new Date(from).toLocaleDateString()} ${new Date(until).toLocaleDateString()}`
export function ScheduleHistoryDialog({ channelId, open, onOpenChange }: Props) {
const { data: entries } = useScheduleHistory(channelId)
const rollback = useRollbackSchedule()
const [confirmId, setConfirmId] = useState<string | null>(null)
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Schedule history</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-2 mt-2 max-h-[60vh] overflow-y-auto">
{(entries ?? []).map((entry, i) => (
<div
key={entry.id}
className="flex items-center gap-3 p-3 rounded border border-border"
>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium">
Gen #{entry.generation}
{i === 0 && (
<span className="ml-2 text-xs text-green-400 bg-green-950 px-1.5 py-0.5 rounded">
active
</span>
)}
</div>
<div className="text-xs text-muted-foreground mt-0.5">
{fmtDateRange(entry.valid_from, entry.valid_until)}
</div>
</div>
{i > 0 && (
confirmId === entry.id ? (
<div className="flex items-center gap-1 text-xs">
<span className="text-amber-400 whitespace-nowrap">Roll back to gen #{entry.generation}?</span>
<Button
size="sm"
variant="destructive"
disabled={rollback.isPending}
onClick={() => {
rollback.mutate({ channelId, genId: entry.id })
setConfirmId(null)
onOpenChange(false)
}}
>
Confirm
</Button>
<Button size="sm" variant="ghost" onClick={() => setConfirmId(null)}>
Cancel
</Button>
</div>
) : (
<Button
size="sm"
variant="outline"
onClick={() => setConfirmId(entry.id)}
>
Rollback to here
</Button>
)
)}
</div>
))}
{(entries ?? []).length === 0 && (
<p className="text-sm text-muted-foreground text-center py-8">
No schedule history yet. Generate a schedule to get started.
</p>
)}
</div>
</DialogContent>
</Dialog>
)
}