design: OrganizeNotebookDialog redesign — propre et cohérent
Some checks failed
CI / Deploy production (on server) (push) Has been cancelled
CI / Lint, Unit Tests & Build (push) Has been cancelled

- Panneau latéral avec fond memento-paper (cohérent avec l'app)
- Cartes de groupes avec bordures subtiles + hover brand-accent
- Boutons primary en brand-accent (bronze) au lieu de bg-ink
- Icônes dans des pastilles arrondies bg-brand-accent/10
- Animations fluides (spring, stagger)
- Dark mode supporté (dark:bg-zinc-900)
- Footer propre avec actions contextuelles par étape
This commit is contained in:
Antigravity
2026-06-19 21:24:35 +00:00
parent 52d213da67
commit dd28b4f0bd

View File

@@ -2,7 +2,7 @@
import { useState, useCallback } from 'react' import { useState, useCallback } from 'react'
import { motion, AnimatePresence } from 'motion/react' import { motion, AnimatePresence } from 'motion/react'
import { Sparkles, X, CheckCircle2, FolderPlus, Folder, ChevronDown, ChevronUp, Loader2, AlertCircle, Check } from 'lucide-react' import { Sparkles, X, CheckCircle2, FolderPlus, Folder, ChevronDown, ChevronUp, Loader2, AlertCircle, Check, ArrowRight } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { import {
analyzeNotebookForOrganization, analyzeNotebookForOrganization,
@@ -31,7 +31,7 @@ export function OrganizeNotebookDialog({
onDone, onDone,
}: OrganizeNotebookDialogProps) { }: OrganizeNotebookDialogProps) {
const { t, language } = useLanguage() const { t, language } = useLanguage()
const organizePanelSlide = language === 'fa' || language === 'ar' ? -60 : 60 const slide = language === 'fa' || language === 'ar' ? -480 : 480
const [step, setStep] = useState<Step>('idle') const [step, setStep] = useState<Step>('idle')
const [plan, setPlan] = useState<OrganizationPlan | null>(null) const [plan, setPlan] = useState<OrganizationPlan | null>(null)
const [editableGroups, setEditableGroups] = useState<OrganizationGroup[]>([]) const [editableGroups, setEditableGroups] = useState<OrganizationGroup[]>([])
@@ -40,18 +40,9 @@ export function OrganizeNotebookDialog({
const [result, setResult] = useState<{ created: number; moved: number } | null>(null) const [result, setResult] = useState<{ created: number; moved: number } | null>(null)
const handleAnalyze = useCallback(async () => { const handleAnalyze = useCallback(async () => {
setStep('analyzing') setStep('analyzing'); setError(null); setPlan(null)
setError(null)
setPlan(null)
const res = await analyzeNotebookForOrganization(notebookId) const res = await analyzeNotebookForOrganization(notebookId)
if (!res.success || !res.plan) { setError(res.error ?? 'Erreur'); setStep('idle'); return }
if (!res.success || !res.plan) {
setError(res.error ?? t('organizeNotebook.unknownError'))
setStep('idle')
return
}
setPlan(res.plan) setPlan(res.plan)
setEditableGroups(res.plan.groups.map(g => ({ ...g, notes: [...g.notes] }))) setEditableGroups(res.plan.groups.map(g => ({ ...g, notes: [...g.notes] })))
setExpandedGroups(new Set(res.plan.groups.map((_, i) => i))) setExpandedGroups(new Set(res.plan.groups.map((_, i) => i)))
@@ -61,64 +52,39 @@ export function OrganizeNotebookDialog({
const handleRenameGroup = (idx: number, name: string) => { const handleRenameGroup = (idx: number, name: string) => {
setEditableGroups(prev => prev.map((g, i) => i === idx ? { ...g, name } : g)) setEditableGroups(prev => prev.map((g, i) => i === idx ? { ...g, name } : g))
} }
const handleToggleNote = (groupIdx: number, noteId: string) => { const handleToggleNote = (groupIdx: number, noteId: string) => {
setEditableGroups(prev => prev.map((g, i) => { setEditableGroups(prev => prev.map((g, i) => {
if (i !== groupIdx) return g if (i !== groupIdx) return g
const has = g.notes.some(n => n.id === noteId) const has = g.notes.some(n => n.id === noteId)
return { return { ...g, notes: has ? g.notes.filter(n => n.id !== noteId) : g.notes }
...g,
notes: has ? g.notes.filter(n => n.id !== noteId) : g.notes,
}
})) }))
} }
const handleRemoveGroup = (idx: number) => { const handleRemoveGroup = (idx: number) => {
setEditableGroups(prev => prev.filter((_, i) => i !== idx)) setEditableGroups(prev => prev.filter((_, i) => i !== idx))
} }
const toggleExpand = (idx: number) => { const toggleExpand = (idx: number) => {
setExpandedGroups(prev => { setExpandedGroups(prev => { const n = new Set(prev); n.has(idx) ? n.delete(idx) : n.add(idx); return n })
const next = new Set(prev)
next.has(idx) ? next.delete(idx) : next.add(idx)
return next
})
} }
const handleExecute = useCallback(async () => { const handleExecute = useCallback(async () => {
if (!plan) return if (!plan) return
setStep('executing') setStep('executing')
const finalPlan: OrganizationPlan = { const finalPlan: OrganizationPlan = {
notebookId: plan.notebookId, notebookId: plan.notebookId,
groups: editableGroups.filter(g => g.notes.length > 0 && g.name.trim()), groups: editableGroups.filter(g => g.notes.length > 0 && g.name.trim()),
} }
const res = await executeNotebookOrganization(finalPlan) const res = await executeNotebookOrganization(finalPlan)
if (!res.success) { setError(res.error ?? 'Erreur'); setStep('preview'); return }
if (!res.success) {
setError(res.error ?? t('organizeNotebook.unknownError'))
setStep('preview')
return
}
setResult({ created: res.created, moved: res.moved }) setResult({ created: res.created, moved: res.moved })
setStep('done') setStep('done')
toast.success(t('organizeNotebook.toastSuccess', { created: res.created, moved: res.moved })) toast.success(t('organizeNotebook.toastSuccess', { created: res.created, moved: res.moved }))
onDone?.() onDone?.()
}, [plan, editableGroups, onDone]) }, [plan, editableGroups, onDone, t])
const handleClose = () => { const handleClose = () => {
if (step === 'analyzing' || step === 'executing') return if (step === 'analyzing' || step === 'executing') return
onOpenChange(false) onOpenChange(false)
// Reset after close animation setTimeout(() => { setStep('idle'); setPlan(null); setEditableGroups([]); setError(null); setResult(null) }, 300)
setTimeout(() => {
setStep('idle')
setPlan(null)
setEditableGroups([])
setError(null)
setResult(null)
}, 300)
} }
if (!open) return null if (!open) return null
@@ -130,110 +96,89 @@ export function OrganizeNotebookDialog({
<AnimatePresence> <AnimatePresence>
{open && ( {open && (
<> <>
{/* Backdrop */}
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
className="fixed inset-0 z-50 bg-ink/30 backdrop-blur-sm" className="fixed inset-0 z-50 bg-black/40 backdrop-blur-sm"
onClick={handleClose} onClick={handleClose}
/> />
{/* Panel */}
<motion.div <motion.div
initial={{ opacity: 0, x: organizePanelSlide }} initial={{ opacity: 0, x: slide }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: organizePanelSlide }} exit={{ opacity: 0, x: slide }}
transition={{ type: 'spring', stiffness: 280, damping: 30 }} transition={{ type: 'spring', stiffness: 300, damping: 32 }}
className="fixed end-0 top-0 bottom-0 z-50 w-[460px] bg-card border-s border-border shadow-2xl flex flex-col" className="fixed end-0 top-0 bottom-0 z-50 w-full max-w-md bg-memento-paper dark:bg-zinc-900 border-s border-border shadow-2xl flex flex-col"
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
> >
{/* Header */} {/* Header */}
<div className="px-6 py-5 border-b border-border/60 flex items-center justify-between shrink-0"> <div className="px-5 py-4 border-b border-border/40 flex items-center justify-between shrink-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-2.5">
<div className="w-8 h-8 rounded-lg bg-brand-accent/10 flex items-center justify-center"> <div className="w-8 h-8 rounded-xl bg-brand-accent/10 flex items-center justify-center">
<Sparkles size={16} className="text-brand-accent" /> <Sparkles size={15} className="text-brand-accent" />
</div> </div>
<div> <div>
<h2 className="text-[13px] font-semibold text-ink">{t('organizeNotebook.title')}</h2> <h2 className="text-sm font-semibold text-ink dark:text-dark-ink font-memento-serif">{t('organizeNotebook.title')}</h2>
<p className="text-[11px] text-muted-ink truncate max-w-[240px]">{notebookName}</p> <p className="text-[10px] text-concrete truncate max-w-[200px]">{notebookName}</p>
</div> </div>
</div> </div>
<button <button onClick={handleClose} disabled={step === 'analyzing' || step === 'executing'}
onClick={handleClose} className="w-7 h-7 rounded-lg flex items-center justify-center text-concrete hover:text-ink hover:bg-foreground/5 transition-colors disabled:opacity-30">
disabled={step === 'analyzing' || step === 'executing'} <X size={15} />
className="w-7 h-7 rounded-md flex items-center justify-center text-muted-ink hover:text-ink hover:bg-foreground/5 transition-colors disabled:opacity-30"
>
<X size={14} />
</button> </button>
</div> </div>
{/* Content */} {/* Content */}
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto custom-scrollbar">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{/* IDLE */} {/* IDLE */}
{step === 'idle' && ( {step === 'idle' && (
<motion.div <motion.div key="idle" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }}
key="idle" className="p-5 space-y-4">
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="p-6 space-y-5"
>
{error && ( {error && (
<div className="flex items-start gap-3 p-3 rounded-xl bg-rose-50 dark:bg-rose-950/30 border border-rose-200 dark:border-rose-800/50"> <div className="flex items-start gap-2.5 p-3 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800/50">
<AlertCircle size={14} className="text-rose-500 mt-0.5 shrink-0" /> <AlertCircle size={14} className="text-red-500 mt-0.5 shrink-0" />
<p className="text-[12px] text-rose-600 dark:text-rose-400">{error}</p> <p className="text-xs text-red-600 dark:text-red-400">{error}</p>
</div> </div>
)} )}
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[13px] text-ink leading-relaxed"> <p className="text-[13px] text-ink dark:text-dark-ink leading-relaxed">{t('organizeNotebook.intro')}</p>
{t('organizeNotebook.intro')} </div>
</p> <div className="grid grid-cols-1 gap-2">
<ul className="space-y-2"> {[{ icon: FolderPlus, text: t('organizeNotebook.bulletThemes') },
{[t('organizeNotebook.bulletThemes'), t('organizeNotebook.bulletSubfolders'), t('organizeNotebook.bulletPreview')].map(item => ( { icon: Folder, text: t('organizeNotebook.bulletSubfolders') },
<li key={item} className="flex items-center gap-2 text-[12px] text-muted-ink"> { icon: Check, text: t('organizeNotebook.bulletPreview') }].map((item, i) => (
<div className="w-1.5 h-1.5 rounded-full bg-brand-accent shrink-0" /> <div key={i} className="flex items-center gap-3 p-2.5 rounded-xl bg-foreground/[0.02] border border-border/30">
{item} <div className="w-7 h-7 rounded-lg bg-brand-accent/10 flex items-center justify-center shrink-0">
</li> <item.icon size={13} className="text-brand-accent" />
))} </div>
</ul> <span className="text-[12px] text-concrete leading-tight">{item.text}</span>
</div>
))}
</div> </div>
</motion.div> </motion.div>
)} )}
{/* ANALYZING */} {/* ANALYZING */}
{step === 'analyzing' && ( {step === 'analyzing' && (
<motion.div <motion.div key="analyzing" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
key="analyzing" className="flex flex-col items-center justify-center py-20 gap-5">
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="flex flex-col items-center justify-center p-12 gap-6 min-h-[300px]"
>
<div className="relative"> <div className="relative">
<div className="w-16 h-16 rounded-2xl bg-brand-accent/10 flex items-center justify-center"> <div className="w-14 h-14 rounded-2xl bg-brand-accent/10 flex items-center justify-center">
<Sparkles size={28} className="text-brand-accent" /> <Sparkles size={24} className="text-brand-accent" />
</div> </div>
<motion.div <motion.div animate={{ rotate: 360 }} transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
animate={{ rotate: 360 }} className="absolute -inset-1.5 rounded-2xl border-2 border-transparent border-t-brand-accent/40" />
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
className="absolute -inset-2 rounded-2xl border-2 border-transparent border-t-brand-accent/40"
/>
</div> </div>
<div className="text-center space-y-1.5"> <div className="text-center space-y-1">
<p className="text-[14px] font-medium text-ink">{t('organizeNotebook.analyzingTitle')}</p> <p className="text-[13px] font-medium text-ink dark:text-dark-ink">{t('organizeNotebook.analyzingTitle')}</p>
<p className="text-[12px] text-muted-ink">{t('organizeNotebook.analyzingSubtitle')}</p> <p className="text-[11px] text-concrete">{t('organizeNotebook.analyzingSubtitle')}</p>
</div> </div>
<div className="flex gap-1.5"> <div className="flex gap-1">
{[0, 1, 2].map(i => ( {[0, 1, 2].map(i => (
<motion.div <motion.div key={i} animate={{ opacity: [0.3, 1, 0.3] }} transition={{ duration: 1.2, repeat: Infinity, delay: i * 0.2 }}
key={i} className="w-1.5 h-1.5 rounded-full bg-brand-accent" />
animate={{ opacity: [0.3, 1, 0.3] }}
transition={{ duration: 1.2, repeat: Infinity, delay: i * 0.2 }}
className="w-1.5 h-1.5 rounded-full bg-brand-accent"
/>
))} ))}
</div> </div>
</motion.div> </motion.div>
@@ -241,152 +186,94 @@ export function OrganizeNotebookDialog({
{/* PREVIEW */} {/* PREVIEW */}
{step === 'preview' && ( {step === 'preview' && (
<motion.div <motion.div key="preview" initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0 }}
key="preview" className="p-4 space-y-3">
initial={{ opacity: 0, y: 12 }} <div className="flex items-center gap-2.5 px-3 py-2.5 rounded-xl bg-brand-accent/[0.06] border border-brand-accent/15">
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
className="p-5 space-y-4"
>
{/* Summary bar */}
<div className="flex items-center gap-3 p-3 rounded-xl bg-brand-accent/5 border border-brand-accent/20">
<Sparkles size={12} className="text-brand-accent shrink-0" /> <Sparkles size={12} className="text-brand-accent shrink-0" />
<p className="text-[11px] text-brand-accent font-medium"> <p className="text-[11px] text-brand-accent font-medium leading-relaxed">
{t('organizeNotebook.previewSummary', { {t('organizeNotebook.previewSummary', { groups: editableGroups.length, notes: totalNotes, newSubs: newSubNbs })}
groups: editableGroups.length,
notes: totalNotes,
newSubs: newSubNbs,
})}
</p> </p>
</div> </div>
{error && ( {error && (
<div className="flex items-start gap-3 p-3 rounded-xl bg-rose-50 dark:bg-rose-950/30 border border-rose-200 dark:border-rose-800/50"> <div className="flex items-start gap-2.5 p-3 rounded-xl bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800/50">
<AlertCircle size={14} className="text-rose-500 mt-0.5 shrink-0" /> <AlertCircle size={14} className="text-red-500 mt-0.5 shrink-0" />
<p className="text-[12px] text-rose-600 dark:text-rose-400">{error}</p> <p className="text-xs text-red-600 dark:text-red-400">{error}</p>
</div> </div>
)} )}
{/* Groups */} {editableGroups.map((group, idx) => (
<div className="space-y-3"> <motion.div key={idx} layout initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: idx * 0.05 }}
{editableGroups.map((group, idx) => ( className="rounded-xl border border-border/40 bg-card overflow-hidden hover:border-brand-accent/20 transition-colors">
<motion.div <div className="flex items-center gap-2 px-3 py-2.5">
key={idx} <div className="w-7 h-7 rounded-lg bg-brand-accent/10 flex items-center justify-center shrink-0">
layout {group.isNew ? <FolderPlus size={13} className="text-brand-accent" /> : <Folder size={13} className="text-concrete" />}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.05 }}
className="rounded-xl border border-border bg-background overflow-hidden"
>
{/* Group header */}
<div className="flex items-center gap-2 px-3 py-2.5">
<div className="w-6 h-6 rounded-md flex items-center justify-center shrink-0">
{group.isNew
? <FolderPlus size={13} className="text-brand-accent" />
: <Folder size={13} className="text-muted-ink" />
}
</div>
<input
value={group.name}
onChange={e => handleRenameGroup(idx, e.target.value)}
className="flex-1 bg-transparent text-[12px] font-semibold text-ink outline-none focus:text-brand-accent transition-colors min-w-0"
/>
{group.isNew && (
<span className="px-1.5 py-0.5 rounded text-[9px] font-bold uppercase tracking-wider bg-brand-accent/10 text-brand-accent shrink-0">
{t('organizeNotebook.badgeNew')}
</span>
)}
<div className="flex items-center gap-1 shrink-0">
<button
onClick={() => toggleExpand(idx)}
className="w-6 h-6 rounded flex items-center justify-center text-muted-ink hover:text-ink transition-colors"
>
{expandedGroups.has(idx) ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
</button>
<button
onClick={() => handleRemoveGroup(idx)}
className="w-6 h-6 rounded flex items-center justify-center text-muted-ink hover:text-rose-500 transition-colors"
>
<X size={12} />
</button>
</div>
</div> </div>
<input value={group.name} onChange={e => handleRenameGroup(idx, e.target.value)}
{/* Notes list */} className="flex-1 bg-transparent text-[13px] font-semibold text-ink dark:text-dark-ink outline-none focus:text-brand-accent transition-colors min-w-0" />
<AnimatePresence> {group.isNew && (
{expandedGroups.has(idx) && ( <span className="px-1.5 py-0.5 rounded-md text-[9px] font-bold uppercase tracking-wider bg-brand-accent/10 text-brand-accent shrink-0">
<motion.div {t('organizeNotebook.badgeNew')}
initial={{ height: 0 }} </span>
animate={{ height: 'auto' }}
exit={{ height: 0 }}
className="overflow-hidden"
>
<div className="border-t border-border/40 px-3 pb-2 pt-1 space-y-0.5">
{group.notes.map(note => (
<div
key={note.id}
className="flex items-center gap-2 py-1.5 rounded-lg px-2 hover:bg-foreground/3 transition-colors group cursor-pointer"
onClick={() => handleToggleNote(idx, note.id)}
>
<div className="w-4 h-4 rounded border border-border flex items-center justify-center shrink-0 group-hover:border-brand-accent/50 transition-colors">
<Check size={10} className="text-brand-accent" />
</div>
<span className="text-[11px] text-muted-ink truncate group-hover:text-ink transition-colors">
{note.title || t('organizeNotebook.untitledNote')}
</span>
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
{/* Collapsed count */}
{!expandedGroups.has(idx) && (
<div className="px-4 pb-2.5 text-[11px] text-muted-ink/60">
{t('organizeNotebook.notesInGroup', { count: group.notes.length })}
</div>
)} )}
</motion.div> <div className="flex items-center gap-0.5 shrink-0">
))} <button onClick={() => toggleExpand(idx)}
</div> className="w-6 h-6 rounded-md flex items-center justify-center text-concrete hover:text-ink hover:bg-foreground/5 transition-colors">
{expandedGroups.has(idx) ? <ChevronUp size={13} /> : <ChevronDown size={13} />}
</button>
<button onClick={() => handleRemoveGroup(idx)}
className="w-6 h-6 rounded-md flex items-center justify-center text-concrete hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-950/30 transition-colors">
<X size={13} />
</button>
</div>
</div>
<AnimatePresence>
{expandedGroups.has(idx) && (
<motion.div initial={{ height: 0 }} animate={{ height: 'auto' }} exit={{ height: 0 }} className="overflow-hidden">
<div className="border-t border-border/30 px-3 pb-2 pt-1.5 space-y-0.5">
{group.notes.map(note => (
<div key={note.id} onClick={() => handleToggleNote(idx, note.id)}
className="flex items-center gap-2 py-1.5 rounded-lg px-2 hover:bg-foreground/[0.03] transition-colors group cursor-pointer">
<div className="w-4 h-4 rounded border border-border group-hover:border-brand-accent/50 flex items-center justify-center shrink-0 transition-colors">
<Check size={10} className="text-brand-accent" />
</div>
<span className="text-[11px] text-concrete truncate group-hover:text-ink transition-colors">{note.title || t('organizeNotebook.untitledNote')}</span>
</div>
))}
</div>
</motion.div>
)}
</AnimatePresence>
{!expandedGroups.has(idx) && (
<div className="px-4 pb-2 text-[10px] text-concrete/60">{t('organizeNotebook.notesInGroup', { count: group.notes.length })}</div>
)}
</motion.div>
))}
</motion.div> </motion.div>
)} )}
{/* EXECUTING */} {/* EXECUTING */}
{step === 'executing' && ( {step === 'executing' && (
<motion.div <motion.div key="executing" initial={{ opacity: 0 }} animate={{ opacity: 1 }}
key="executing" className="flex flex-col items-center justify-center py-20 gap-4">
initial={{ opacity: 0 }} <Loader2 size={28} className="text-brand-accent animate-spin" />
animate={{ opacity: 1 }}
className="flex flex-col items-center justify-center p-12 gap-5 min-h-[300px]"
>
<Loader2 size={32} className="text-brand-accent animate-spin" />
<div className="text-center space-y-1"> <div className="text-center space-y-1">
<p className="text-[14px] font-medium text-ink">{t('organizeNotebook.executingTitle')}</p> <p className="text-[13px] font-medium text-ink dark:text-dark-ink">{t('organizeNotebook.executingTitle')}</p>
<p className="text-[12px] text-muted-ink">{t('organizeNotebook.executingSubtitle')}</p> <p className="text-[11px] text-concrete">{t('organizeNotebook.executingSubtitle')}</p>
</div> </div>
</motion.div> </motion.div>
)} )}
{/* DONE */} {/* DONE */}
{step === 'done' && ( {step === 'done' && (
<motion.div <motion.div key="done" initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }}
key="done" className="flex flex-col items-center justify-center py-20 gap-5">
initial={{ opacity: 0, scale: 0.95 }} <div className="w-14 h-14 rounded-2xl bg-emerald-50 dark:bg-emerald-950/30 flex items-center justify-center">
animate={{ opacity: 1, scale: 1 }} <CheckCircle2 size={28} className="text-emerald-500" />
className="flex flex-col items-center justify-center p-12 gap-5 min-h-[300px]"
>
<div className="w-16 h-16 rounded-2xl bg-emerald-50 dark:bg-emerald-950/30 flex items-center justify-center">
<CheckCircle2 size={32} className="text-emerald-500" />
</div> </div>
<div className="text-center space-y-1.5"> <div className="text-center space-y-1.5">
<p className="text-[15px] font-semibold text-ink">{t('organizeNotebook.doneTitle')}</p> <p className="text-[14px] font-semibold text-ink dark:text-dark-ink">{t('organizeNotebook.doneTitle')}</p>
{result && ( {result && <p className="text-[12px] text-concrete">{t('organizeNotebook.doneStats', { created: result.created, moved: result.moved })}</p>}
<p className="text-[12px] text-muted-ink">
{t('organizeNotebook.doneStats', { created: result.created, moved: result.moved })}
</p>
)}
</div> </div>
</motion.div> </motion.div>
)} )}
@@ -394,47 +281,35 @@ export function OrganizeNotebookDialog({
</AnimatePresence> </AnimatePresence>
</div> </div>
{/* Footer actions */} {/* Footer */}
<div className="px-5 py-4 border-t border-border/60 shrink-0"> <div className="px-4 py-3.5 border-t border-border/40 shrink-0">
{step === 'idle' && ( {step === 'idle' && (
<button <button onClick={handleAnalyze}
onClick={handleAnalyze} className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-brand-accent text-white text-[13px] font-medium hover:bg-brand-accent/90 transition-colors">
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-ink text-paper text-[13px] font-semibold hover:opacity-85 transition-opacity"
>
<Sparkles size={14} /> <Sparkles size={14} />
{t('organizeNotebook.analyzeButton')} {t('organizeNotebook.analyzeButton')}
</button> </button>
)} )}
{step === 'preview' && ( {step === 'preview' && (
<div className="flex gap-2"> <div className="flex gap-2">
<button <button onClick={() => { setStep('idle'); setError(null) }}
onClick={() => { setStep('idle'); setError(null) }} className="flex-1 px-4 py-2.5 rounded-xl border border-border/60 text-[13px] font-medium text-concrete hover:text-ink dark:hover:text-dark-ink hover:border-border transition-colors">
className="flex-1 px-4 py-2.5 rounded-xl border border-border text-[13px] font-medium text-muted-ink hover:text-ink transition-colors"
>
{t('organizeNotebook.restart')} {t('organizeNotebook.restart')}
</button> </button>
<button <button onClick={handleExecute}
onClick={handleExecute}
disabled={editableGroups.filter(g => g.notes.length > 0).length === 0} disabled={editableGroups.filter(g => g.notes.length > 0).length === 0}
className={cn( className={cn('flex-1 flex items-center justify-center gap-1.5 px-4 py-2.5 rounded-xl text-[13px] font-medium transition-all',
'flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-[13px] font-semibold transition-all',
editableGroups.filter(g => g.notes.length > 0).length > 0 editableGroups.filter(g => g.notes.length > 0).length > 0
? 'bg-ink text-paper hover:opacity-85' ? 'bg-brand-accent text-white hover:bg-brand-accent/90'
: 'bg-foreground/10 text-muted-ink cursor-not-allowed' : 'bg-foreground/10 text-concrete cursor-not-allowed')}>
)} <ArrowRight size={14} />
>
<Check size={14} />
{t('organizeNotebook.confirm')} {t('organizeNotebook.confirm')}
</button> </button>
</div> </div>
)} )}
{step === 'done' && ( {step === 'done' && (
<button <button onClick={handleClose}
onClick={handleClose} className="w-full px-4 py-2.5 rounded-xl border border-border/60 text-[13px] font-medium text-concrete hover:text-ink dark:hover:text-dark-ink hover:border-border transition-colors">
className="w-full px-4 py-2.5 rounded-xl border border-border text-[13px] font-medium text-muted-ink hover:text-ink transition-colors"
>
{t('organizeNotebook.closeButton')} {t('organizeNotebook.closeButton')}
</button> </button>
)} )}