120 lines
4.2 KiB
TypeScript
120 lines
4.2 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { useConfigHistory, usePinSnapshot, useRestoreConfig } from '@/hooks/use-channels'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface Props {
|
|
channelId: string
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
}
|
|
|
|
export function ConfigHistorySheet({ channelId, open, onOpenChange }: Props) {
|
|
const { data: snapshots } = useConfigHistory(channelId)
|
|
const pin = usePinSnapshot()
|
|
const restore = useRestoreConfig()
|
|
const [pinningId, setPinningId] = useState<string | null>(null)
|
|
const [pinLabel, setPinLabel] = useState('')
|
|
|
|
return (
|
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
<SheetContent>
|
|
<SheetHeader>
|
|
<SheetTitle>Config history</SheetTitle>
|
|
</SheetHeader>
|
|
<div className="flex flex-col gap-2 mt-4 overflow-y-auto px-4 pb-4">
|
|
{(snapshots ?? []).map((snap, i) => (
|
|
<div
|
|
key={snap.id}
|
|
className={cn(
|
|
'flex items-center gap-3 p-3 rounded border',
|
|
i === 0 ? 'border-green-700 bg-green-950/30' : 'border-border'
|
|
)}
|
|
>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="text-sm font-medium">
|
|
v{snap.version_num} —{' '}
|
|
{new Date(snap.created_at).toLocaleString()}
|
|
{i === 0 && (
|
|
<span className="ml-2 text-xs text-green-400 bg-green-950 px-1.5 py-0.5 rounded">
|
|
current
|
|
</span>
|
|
)}
|
|
</div>
|
|
{snap.label ? (
|
|
<div className="text-xs text-amber-400 mt-0.5">📌 {snap.label}</div>
|
|
) : (
|
|
<div className="text-xs text-muted-foreground">Auto-saved</div>
|
|
)}
|
|
</div>
|
|
|
|
{i === 0 && (
|
|
pinningId === snap.id ? (
|
|
<div className="flex gap-1 items-center">
|
|
<Input
|
|
value={pinLabel}
|
|
onChange={e => setPinLabel(e.target.value)}
|
|
className="h-7 text-xs w-32"
|
|
placeholder="label…"
|
|
onKeyDown={e => {
|
|
if (e.key === 'Enter') {
|
|
pin.mutate({ channelId, snapId: snap.id, label: pinLabel })
|
|
setPinningId(null)
|
|
}
|
|
if (e.key === 'Escape') setPinningId(null)
|
|
}}
|
|
/>
|
|
<Button
|
|
size="sm"
|
|
onClick={() => {
|
|
pin.mutate({ channelId, snapId: snap.id, label: pinLabel })
|
|
setPinningId(null)
|
|
}}
|
|
>
|
|
Save
|
|
</Button>
|
|
<Button size="sm" variant="ghost" onClick={() => setPinningId(null)}>
|
|
✕
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => {
|
|
setPinningId(snap.id)
|
|
setPinLabel(snap.label ?? '')
|
|
}}
|
|
>
|
|
Pin
|
|
</Button>
|
|
)
|
|
)}
|
|
|
|
{i > 0 && (
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => restore.mutate({ channelId, snapId: snap.id })}
|
|
disabled={restore.isPending}
|
|
>
|
|
Restore
|
|
</Button>
|
|
)}
|
|
</div>
|
|
))}
|
|
{(snapshots ?? []).length === 0 && (
|
|
<p className="text-sm text-muted-foreground text-center py-8">
|
|
No history yet. History is created automatically when you save changes.
|
|
</p>
|
|
)}
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
)
|
|
}
|