UI Stabilization: Global color theme updates (#75B2D6), AI Assistant styling refactor, and navigation fixes

This commit is contained in:
Antigravity
2026-05-09 12:58:16 +00:00
parent 1446463f04
commit 60a3fe5453
47 changed files with 3585 additions and 2149 deletions

View File

@@ -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" />