feat: Implement a snapshots feature with a list component and a dedicated page to manage and restore saved sessions.

This commit is contained in:
Michael committed 2026-02-22 14:37:19 +01:00
1 parent afa7e59c34
commit d6e313f945
4 files changed
+37 -37

No files matched your search

+4 -4
View File
@@ -7,12 +7,12 @@ export default function ExportsPage() {
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div> <div>
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-[var(--accent-bg)] border border-[var(--accent-border)] flex items-center justify-center">
<FileDown className="w-5 h-5 text-emerald-400" /> <FileDown className="w-5 h-5 text-[var(--accent)]" />
</div> </div>
<h1 className="text-3xl font-black tracking-tight text-slate-100 italic uppercase">Historique d&apos;Arbitrage</h1> <h1 className="text-2xl font-black tracking-tight text-[var(--text-primary)] uppercase">Historique d&apos;Arbitrage</h1>
</div> </div>
<p className="text-sm text-slate-400 max-w-xl"> <p className="text-sm text-[var(--text-secondary)] max-w-xl">
Retrouvez ici toutes vos sessions sauvegardées et exports réalisés. Vous pouvez restaurer une session pour reprendre votre travail ou supprimer les anciens enregistrements. Retrouvez ici toutes vos sessions sauvegardées et exports réalisés. Vous pouvez restaurer une session pour reprendre votre travail ou supprimer les anciens enregistrements.
</p> </p>
</div> </div>
+4 -4
View File
@@ -7,12 +7,12 @@ export default function SnapshotsPage() {
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div> <div>
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-[var(--accent-bg)] border border-[var(--accent-border)] flex items-center justify-center">
<Camera className="w-5 h-5 text-indigo-400" /> <Camera className="w-5 h-5 text-[var(--accent)]" />
</div> </div>
<h1 className="text-3xl font-black tracking-tight text-slate-100 italic uppercase">Snapshots</h1> <h1 className="text-2xl font-black tracking-tight text-[var(--text-primary)] uppercase">Snapshots</h1>
</div> </div>
<p className="text-sm text-slate-400 max-w-xl"> <p className="text-sm text-[var(--text-secondary)] max-w-xl">
Historique de vos sessions d&apos;arbitrage. Restaurez une sauvegarde pour retrouver vos modifications en cours sur une gamme spécifique. Historique de vos sessions d&apos;arbitrage. Restaurez une sauvegarde pour retrouver vos modifications en cours sur une gamme spécifique.
</p> </p>
</div> </div>
+8 -8
View File
@@ -37,10 +37,10 @@ export function Sidebar() {
{/* Logo */} {/* Logo */}
<div className="px-5 py-[18px]" style={{ borderBottom: "1px solid var(--border)" }}> <div className="px-5 py-[18px]" style={{ borderBottom: "1px solid var(--border)" }}>
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<div className="w-7 h-7 rounded-[8px] bg-emerald-500 dark:bg-emerald-600 flex items-center justify-center shadow-sm border border-white/10"> <div className="w-7 h-7 rounded-[8px] bg-[var(--accent)] flex items-center justify-center shadow-sm border border-white/10">
<Package className="w-[15px] h-[15px] text-white" strokeWidth={2.2} /> <Package className="w-[15px] h-[15px] text-white" strokeWidth={2.2} />
</div> </div>
<span className="text-[15px] font-semibold tracking-[-0.3px]" style={{ color: "var(--text-primary)" }}> <span className="text-[15px] font-semibold tracking-[-0.3px] text-[var(--text-primary)]">
CollectFlow CollectFlow
</span> </span>
</div> </div>
@@ -78,13 +78,13 @@ export function Sidebar() {
{/* User Profile & Logout */} {/* User Profile & Logout */}
<div className="px-3 pb-3 space-y-2"> <div className="px-3 pb-3 space-y-2">
<div className="flex items-center gap-3 px-3 py-3 rounded-xl bg-slate-800/20 border border-slate-700/30"> <div className="flex items-center gap-3 px-3 py-3 rounded-xl bg-[var(--bg-elevated)] border border-[var(--border)]">
<div className="w-8 h-8 rounded-lg bg-indigo-500/10 flex items-center justify-center border border-indigo-500/20"> <div className="w-8 h-8 rounded-lg bg-[var(--accent-bg)] flex items-center justify-center border border-[var(--accent-border)]">
<UserIcon className="w-4 h-4 text-indigo-400" /> <UserIcon className="w-4 h-4 text-[var(--accent)]" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[12px] font-bold text-slate-100 truncate">{session?.user?.name || "Invité"}</p> <p className="text-[12px] font-bold text-[var(--text-primary)] truncate">{session?.user?.name || "Invité"}</p>
<p className="text-[10px] uppercase font-black text-slate-500 tracking-wider"> <p className="text-[10px] uppercase font-black text-[var(--text-muted)] tracking-wider">
{userRole === "admin" ? "Administrateur" : "Utilisateur"} {userRole === "admin" ? "Administrateur" : "Utilisateur"}
</p> </p>
</div> </div>
@@ -92,7 +92,7 @@ export function Sidebar() {
<button <button
onClick={() => signOut({ callbackUrl: "/login" })} onClick={() => signOut({ callbackUrl: "/login" })}
className="w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-[13px] font-medium text-rose-500 hover:bg-rose-500/10 transition-colors" className="w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-[13px] font-medium text-[var(--accent-error)] hover:bg-[var(--accent-error-bg)] transition-colors"
> >
<LogOut className="w-[15px] h-[15px]" strokeWidth={1.8} /> <LogOut className="w-[15px] h-[15px]" strokeWidth={1.8} />
Déconnexion Déconnexion
@@ -110,22 +110,22 @@ export function SnapshotList({ type }: SnapshotListProps) {
if (loading) { if (loading) {
return ( return (
<div className="flex flex-col items-center justify-center p-20 space-y-4"> <div className="flex flex-col items-center justify-center p-20 space-y-4">
<Loader2 className="w-8 h-8 text-indigo-500 animate-spin" /> <Loader2 className="w-8 h-8 text-[var(--accent)] animate-spin" />
<p className="text-slate-400 text-sm">Récupération de l&apos;historique...</p> <p className="text-[var(--text-muted)] text-sm">Récupération de l&apos;historique...</p>
</div> </div>
); );
} }
if (snapshots.length === 0) { if (snapshots.length === 0) {
return ( return (
<div className="border border-dashed border-slate-700 rounded-2xl p-16 text-center bg-slate-800/20"> <div className="border border-dashed border-[var(--border-strong)] rounded-2xl p-16 text-center bg-[var(--bg-elevated)]/30">
<div className="w-16 h-16 rounded-full bg-slate-800 flex items-center justify-center mx-auto mb-4 border border-slate-700"> <div className="w-16 h-16 rounded-full bg-[var(--bg-elevated)] flex items-center justify-center mx-auto mb-4 border border-[var(--border)]">
<History className="w-8 h-8 text-slate-500" /> <History className="w-8 h-8 text-[var(--text-muted)]" />
</div> </div>
<h3 className="text-slate-200 font-bold mb-1"> <h3 className="text-[var(--text-primary)] font-bold mb-1">
{type === "export" ? "Aucun export pour le moment" : "Aucun snapshot pour le moment"} {type === "export" ? "Aucun export pour le moment" : "Aucun snapshot pour le moment"}
</h3> </h3>
<p className="text-slate-500 text-sm max-w-xs mx-auto"> <p className="text-[var(--text-secondary)] text-sm max-w-xs mx-auto">
{type === "export" {type === "export"
? "Vos exports Excel apparaîtront ici pour historique." ? "Vos exports Excel apparaîtront ici pour historique."
: "Capturez l'état de votre travail depuis la grille pour le retrouver ici ultérieurement."} : "Capturez l'état de votre travail depuis la grille pour le retrouver ici ultérieurement."}
@@ -135,25 +135,25 @@ export function SnapshotList({ type }: SnapshotListProps) {
} }
return ( return (
<div className="grid gap-4"> <div className="grid gap-3">
{snapshots.map((s) => ( {snapshots.map((s) => (
<div <div
key={s.id} key={s.id}
className="group relative flex items-center gap-6 p-5 rounded-2xl border transition-all hover:shadow-xl bg-slate-800/40 border-slate-700/50 hover:border-indigo-500/50 hover:bg-slate-800/60" className="group relative flex items-center gap-6 p-5 rounded-xl border transition-all hover:shadow-lg bg-[var(--bg-surface)] border-[var(--border)] hover:border-[var(--accent-border)]"
> >
<div className="w-12 h-12 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center shrink-0"> <div className="w-11 h-11 rounded-xl bg-[var(--accent-bg)] border border-[var(--accent-border)] flex items-center justify-center shrink-0">
<Store className="w-6 h-6 text-indigo-400" /> <Store className="w-5 h-5 text-[var(--accent)]" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<h3 className="text-sm font-bold text-slate-100 truncate">{s.label}</h3> <h3 className="text-sm font-bold text-[var(--text-primary)] truncate">{s.label}</h3>
<span className="px-2 py-0.5 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-[10px] font-black uppercase text-indigo-400 tracking-wider"> <span className="px-2 py-0.5 rounded-full bg-[var(--accent-bg)] border border-[var(--accent-border)] text-[10px] font-black uppercase text-[var(--accent)] tracking-wider">
{s.magasin || "Magasin Inconnu"} {s.magasin || "Magasin Inconnu"}
</span> </span>
</div> </div>
<div className="flex items-center gap-4 text-[11px] text-slate-400 font-medium"> <div className="flex items-center gap-4 text-[11px] text-[var(--text-muted)] font-medium">
<span className="flex items-center gap-1.5"> <span className="flex items-center gap-1.5">
<Calendar className="w-3.5 h-3.5 opacity-60" /> <Calendar className="w-3.5 h-3.5 opacity-60" />
{formatDate(new Date(s.createdAt))} {formatDate(new Date(s.createdAt))}
@@ -166,16 +166,16 @@ export function SnapshotList({ type }: SnapshotListProps) {
</div> </div>
{s.summaryJson && ( {s.summaryJson && (
<div className="hidden xl:flex items-center gap-8 border-l border-slate-700/50 pl-8 mr-4"> <div className="hidden xl:flex items-center gap-8 border-l border-[var(--border)] pl-8 mr-4">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-[9px] uppercase font-black text-slate-500 tracking-wider">CA</span> <span className="text-[9px] uppercase font-black text-[var(--text-muted)] tracking-wider">CA</span>
<span className="text-xs font-bold text-emerald-400"> <span className="text-xs font-bold text-[var(--accent-success)]">
{(s.summaryJson.totalCa || 0).toLocaleString("fr-FR", { style: "currency", currency: "EUR", maximumFractionDigits: 0 })} {(s.summaryJson.totalCa || 0).toLocaleString("fr-FR", { style: "currency", currency: "EUR", maximumFractionDigits: 0 })}
</span> </span>
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-[9px] uppercase font-black text-slate-500 tracking-wider">Marge</span> <span className="text-[9px] uppercase font-black text-[var(--text-muted)] tracking-wider">Marge</span>
<span className="text-xs font-bold text-slate-200"> <span className="text-xs font-bold text-[var(--text-secondary)]">
{s.summaryJson.tauxMargeGlobal?.toFixed(1)}% {s.summaryJson.tauxMargeGlobal?.toFixed(1)}%
</span> </span>
</div> </div>
@@ -185,7 +185,7 @@ export function SnapshotList({ type }: SnapshotListProps) {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => handleLoad(s)} onClick={() => handleLoad(s)}
className="flex items-center gap-2 px-4 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-[12px] font-black transition-all active:scale-95 shadow-lg shadow-indigo-900/20" className="apple-btn-primary flex items-center gap-2"
> >
Restaurer Restaurer
<ChevronRight className="w-4 h-4" /> <ChevronRight className="w-4 h-4" />
@@ -194,7 +194,7 @@ export function SnapshotList({ type }: SnapshotListProps) {
<button <button
onClick={() => handleDelete(s.id)} onClick={() => handleDelete(s.id)}
disabled={isDeleting === s.id} disabled={isDeleting === s.id}
className="p-2.5 rounded-xl border border-slate-700 text-slate-500 hover:text-rose-500 hover:bg-rose-500/10 hover:border-rose-500/30 transition-all active:scale-95" className="p-2.5 rounded-xl border border-[var(--border)] text-[var(--text-muted)] hover:text-[var(--accent-error)] hover:bg-[var(--accent-error-bg)] hover:border-[var(--accent-error)] transition-all active:scale-95"
title="Supprimer" title="Supprimer"
> >
{isDeleting === s.id ? <Loader2 className="w-4 h-4 animate-spin" /> : <Trash2 className="w-4 h-4" />} {isDeleting === s.id ? <Loader2 className="w-4 h-4 animate-spin" /> : <Trash2 className="w-4 h-4" />}