UI Stabilization: Global color theme updates (#75B2D6), AI Assistant styling refactor, and navigation fixes
This commit is contained in:
@@ -5,13 +5,14 @@ import { Note } from '@/lib/types'
|
||||
import { format, formatDistanceToNow } from 'date-fns'
|
||||
import { fr } from 'date-fns/locale/fr'
|
||||
import { enUS } from 'date-fns/locale/en-US'
|
||||
import { X, Info, Clock, Hash, Book, FileText, Calendar, Tag, ChevronRight } from 'lucide-react'
|
||||
import { X, Info, Clock, Hash, Book, FileText, Calendar, Tag, ChevronRight, Trash2, RotateCcw, Loader2, Check, History as HistoryIcon } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { useNotebooks } from '@/context/notebooks-context'
|
||||
import { LabelBadge } from './label-badge'
|
||||
import { NoteHistoryModal } from './note-history-modal'
|
||||
import { enableNoteHistory, commitNoteHistory } from '@/app/actions/notes'
|
||||
import { enableNoteHistory, commitNoteHistory, getNoteHistory, deleteNoteHistoryEntry, restoreNoteVersion } from '@/app/actions/notes'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
type Tab = 'info' | 'versions'
|
||||
|
||||
@@ -49,8 +50,56 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
|
||||
const [historyEnabled, setHistoryEnabled] = useState(note.historyEnabled ?? false)
|
||||
const [isSavingVersion, setIsSavingVersion] = useState(false)
|
||||
const [versionSaved, setVersionSaved] = useState(false)
|
||||
const [historyEntries, setHistoryEntries] = useState<any[]>([])
|
||||
const [isLoadingHistory, setIsLoadingHistory] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState<string | null>(null)
|
||||
const [isRestoring, setIsRestoring] = useState<string | null>(null)
|
||||
const locale = getLocale(language)
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'versions' && historyEnabled) {
|
||||
loadHistory()
|
||||
}
|
||||
}, [activeTab, historyEnabled, note.id])
|
||||
|
||||
const loadHistory = async () => {
|
||||
setIsLoadingHistory(true)
|
||||
try {
|
||||
const entries = await getNoteHistory(note.id, 50)
|
||||
setHistoryEntries(entries)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
setIsLoadingHistory(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteVersion = async (entryId: string) => {
|
||||
if (!confirm('Supprimer cette version ?')) return
|
||||
setIsDeleting(entryId)
|
||||
try {
|
||||
await deleteNoteHistoryEntry(note.id, entryId)
|
||||
setHistoryEntries(prev => prev.filter(e => e.id !== entryId))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
setIsDeleting(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRestoreVersion = async (entryId: string) => {
|
||||
setIsRestoring(entryId)
|
||||
try {
|
||||
const restored = await restoreNoteVersion(note.id, entryId)
|
||||
onNoteRestored?.(restored)
|
||||
loadHistory()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
setIsRestoring(null)
|
||||
}
|
||||
}
|
||||
|
||||
const notebook = useMemo(
|
||||
() => notebooks.find(nb => nb.id === note.notebookId),
|
||||
[notebooks, note.notebookId]
|
||||
@@ -201,17 +250,17 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<p className="text-[10px] uppercase tracking-widest text-muted-foreground">Versions sauvegardées</p>
|
||||
<div className="space-y-4">
|
||||
<p className="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">Versions sauvegardées</p>
|
||||
|
||||
{/* Save version button */}
|
||||
<button
|
||||
disabled={isSavingVersion}
|
||||
className={cn(
|
||||
'w-full flex items-center justify-center gap-2 p-3 rounded-xl border transition-colors text-sm font-medium',
|
||||
'w-full flex items-center justify-center gap-2 p-3 rounded-xl border transition-all text-xs font-bold uppercase tracking-widest',
|
||||
versionSaved
|
||||
? 'border-emerald-500/40 bg-emerald-50 dark:bg-emerald-950/30 text-emerald-700 dark:text-emerald-400'
|
||||
: 'border-foreground/20 bg-foreground text-background hover:opacity-80',
|
||||
: 'border-foreground/10 bg-foreground text-background hover:opacity-90 shadow-sm',
|
||||
isSavingVersion && 'opacity-50 cursor-not-allowed'
|
||||
)}
|
||||
onClick={async () => {
|
||||
@@ -219,6 +268,7 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
|
||||
try {
|
||||
await commitNoteHistory(note.id)
|
||||
setVersionSaved(true)
|
||||
loadHistory()
|
||||
setTimeout(() => setVersionSaved(false), 3000)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -228,24 +278,95 @@ export function NoteDocumentInfoPanel({ note, content, onClose, onNoteRestored }
|
||||
}}
|
||||
>
|
||||
{isSavingVersion ? (
|
||||
<><span className="h-3.5 w-3.5 rounded-full border-2 border-current border-t-transparent animate-spin" />Sauvegarde…</>
|
||||
<><Loader2 className="h-3.5 w-3.5 animate-spin" />Sauvegarde…</>
|
||||
) : versionSaved ? (
|
||||
<><span className="text-base">✓</span> Version sauvegardée !</>
|
||||
<><Check className="h-3.5 w-3.5" /> Version sauvegardée !</>
|
||||
) : (
|
||||
<><span className="text-base">⎘</span> Sauvegarder cette version</>
|
||||
<>Sauvegarder cette version</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* View history */}
|
||||
<div className="h-px bg-border/30 my-2" />
|
||||
|
||||
{/* Timeline */}
|
||||
{isLoadingHistory && historyEntries.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-10 opacity-40">
|
||||
<Loader2 className="h-6 w-6 animate-spin mb-2" />
|
||||
<p className="text-[10px] uppercase tracking-widest">Chargement...</p>
|
||||
</div>
|
||||
) : historyEntries.length === 0 ? (
|
||||
<div className="text-center py-8 opacity-40 border border-dashed rounded-xl">
|
||||
<Clock className="h-6 w-6 mx-auto mb-2" />
|
||||
<p className="text-[10px] uppercase tracking-widest">Aucune version</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative pl-6 space-y-6 before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-px before:bg-border/40">
|
||||
{historyEntries.map((entry, idx) => {
|
||||
const colors = ['#E2E8F0', '#ACB995', '#E9ECEF']
|
||||
const dotColor = colors[idx % colors.length]
|
||||
const isLatest = idx === 0
|
||||
|
||||
return (
|
||||
<div key={entry.id} className="relative group">
|
||||
{/* Dot */}
|
||||
<div
|
||||
className="absolute -left-[19px] top-1.5 h-3 w-3 rounded-full border-2 border-background z-10 shadow-sm"
|
||||
style={{ backgroundColor: dotColor }}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs font-bold font-mono">v{entry.version}</span>
|
||||
{isLatest && (
|
||||
<span className="text-[9px] px-1.5 py-0.5 rounded-md bg-primary/10 text-primary font-bold uppercase tracking-widest">Latest</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={() => handleRestoreVersion(entry.id)}
|
||||
disabled={!!isRestoring || !!isDeleting}
|
||||
className="p-1.5 rounded-lg hover:bg-primary/10 text-muted-foreground hover:text-primary transition-colors"
|
||||
title="Restaurer"
|
||||
>
|
||||
{isRestoring === entry.id ? <Loader2 className="h-3 w-3 animate-spin" /> : <RotateCcw className="h-3 w-3" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteVersion(entry.id)}
|
||||
disabled={!!isRestoring || !!isDeleting}
|
||||
className="p-1.5 rounded-lg hover:bg-red-500/10 text-muted-foreground hover:text-red-500 transition-colors"
|
||||
title="Supprimer"
|
||||
>
|
||||
{isDeleting === entry.id ? <Loader2 className="h-3 w-3 animate-spin" /> : <Trash2 className="h-3 w-3" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[10px] text-muted-foreground font-medium">
|
||||
{format(new Date(entry.createdAt), 'd MMM · HH:mm', { locale })}
|
||||
<span className="mx-1.5 opacity-30">·</span>
|
||||
{formatDistanceToNow(new Date(entry.createdAt), { addSuffix: true, locale })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Button to open the full modal (optional, but good to keep if user wants diff) */}
|
||||
<button
|
||||
className="w-full flex items-center justify-between p-3 rounded-xl border border-border hover:bg-muted transition-colors text-left"
|
||||
className="w-full flex items-center justify-between p-3 rounded-xl border border-border/40 hover:bg-muted/50 transition-colors text-left group mt-4"
|
||||
onClick={() => setShowHistory(true)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-2 h-2 rounded-full bg-emerald-500 shrink-0" />
|
||||
<div className="w-8 h-8 rounded-full bg-primary/5 flex items-center justify-center text-primary group-hover:bg-primary/10 transition-colors">
|
||||
<HistoryIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium">Voir l'historique</p>
|
||||
<p className="text-[11px] text-muted-foreground">Comparer et restaurer des versions</p>
|
||||
<p className="text-xs font-bold uppercase tracking-wider">Mode Comparaison</p>
|
||||
<p className="text-[10px] text-muted-foreground">Comparer les versions côte à côte</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
|
||||
Reference in New Issue
Block a user