- Add NoteVersion model for storing note snapshots - Add versions API (list, create, get, restore) - Add version history UI dialog in note detail page - Create version snapshot before each note update
159 lines
5.1 KiB
TypeScript
159 lines
5.1 KiB
TypeScript
'use client'
|
|
import { useState } from 'react'
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription, DialogFooter } from '@/components/ui/dialog'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { History, RotateCcw } from 'lucide-react'
|
|
import { toast } from 'sonner'
|
|
|
|
interface Version {
|
|
id: string
|
|
title: string
|
|
content: string
|
|
createdAt: string
|
|
}
|
|
|
|
interface VersionHistoryProps {
|
|
noteId: string
|
|
}
|
|
|
|
export function VersionHistory({ noteId }: VersionHistoryProps) {
|
|
const [open, setOpen] = useState(false)
|
|
const [versions, setVersions] = useState<Version[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
const [restoring, setRestoring] = useState<string | null>(null)
|
|
const [confirmRestore, setConfirmRestore] = useState<string | null>(null)
|
|
|
|
const fetchVersions = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const res = await fetch(`/api/notes/${noteId}/versions`)
|
|
const data = await res.json()
|
|
if (data.success) {
|
|
setVersions(data.data)
|
|
}
|
|
} catch {
|
|
toast.error('Error al cargar las versiones')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleOpenChange = (newOpen: boolean) => {
|
|
setOpen(newOpen)
|
|
if (newOpen && versions.length === 0) {
|
|
fetchVersions()
|
|
}
|
|
}
|
|
|
|
const handleRestore = async (versionId: string) => {
|
|
setRestoring(versionId)
|
|
try {
|
|
const res = await fetch(`/api/notes/${noteId}/versions/${versionId}`, {
|
|
method: 'PUT',
|
|
})
|
|
const data = await res.json()
|
|
if (data.success) {
|
|
toast.success('Versión restaurada correctamente')
|
|
setOpen(false)
|
|
window.location.reload()
|
|
} else {
|
|
toast.error(data.error || 'Error al restaurar la versión')
|
|
}
|
|
} catch {
|
|
toast.error('Error al restaurar la versión')
|
|
} finally {
|
|
setRestoring(null)
|
|
setConfirmRestore(null)
|
|
}
|
|
}
|
|
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString)
|
|
return date.toLocaleString('es-ES', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
<DialogTrigger asChild>
|
|
<Button variant="outline" size="sm">
|
|
<History className="h-4 w-4 mr-1" /> Historial
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-hidden flex flex-col">
|
|
<DialogHeader>
|
|
<DialogTitle>Historial de versiones</DialogTitle>
|
|
<DialogDescription>
|
|
Revisa y restaura versiones anteriores de esta nota
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="flex-1 overflow-y-auto py-4">
|
|
{loading ? (
|
|
<div className="text-center py-8 text-gray-500">Cargando versiones...</div>
|
|
) : versions.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-500">No hay versiones guardadas</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{versions.map((version) => (
|
|
<div
|
|
key={version.id}
|
|
className="flex items-start justify-between gap-4 p-3 border rounded-lg hover:bg-gray-50"
|
|
>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="font-medium text-sm mb-1">
|
|
{formatDate(version.createdAt)}
|
|
</div>
|
|
<div className="text-gray-600 text-sm truncate">
|
|
{version.title}
|
|
</div>
|
|
<div className="text-gray-400 text-xs mt-1 truncate">
|
|
{version.content.substring(0, 50)}...
|
|
</div>
|
|
</div>
|
|
|
|
{confirmRestore === version.id ? (
|
|
<div className="flex gap-2 items-center">
|
|
<span className="text-xs text-gray-500">¿Restaurar?</span>
|
|
<Button
|
|
size="sm"
|
|
variant="destructive"
|
|
onClick={() => handleRestore(version.id)}
|
|
disabled={restoring === version.id}
|
|
>
|
|
{restoring === version.id ? '...' : 'Sí'}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => setConfirmRestore(null)}
|
|
>
|
|
No
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => setConfirmRestore(version.id)}
|
|
title="Restaurar esta versión"
|
|
>
|
|
<RotateCcw className="h-4 w-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|