redesign de la page /admin

This commit is contained in:
Yannick Le Duc
2025-08-26 23:39:58 +02:00
parent caf0478e02
commit 4ce52f300f
12 changed files with 577 additions and 685 deletions

View File

@@ -96,6 +96,8 @@ export default function AuthGuard({ children, requireSuperAdmin = false }: AuthG
}
};
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
@@ -218,21 +220,6 @@ export default function AuthGuard({ children, requireSuperAdmin = false }: AuthG
return (
<div>
{/* Barre de navigation admin */}
<div className="bg-white border-b px-4 py-2 flex justify-between items-center">
<div className="flex items-center space-x-2">
<Lock className="h-4 w-4 text-blue-600" />
<span className="font-medium text-sm">Administration</span>
</div>
<Button
variant="outline"
size="sm"
onClick={handleLogout}
>
Déconnexion
</Button>
</div>
{children}
</div>
);

View File

@@ -1,9 +1,9 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Home, Settings, Users, FileText, ArrowLeft } from 'lucide-react';
import { Settings, ArrowLeft } from 'lucide-react';
interface NavigationProps {
showBackButton?: boolean;
@@ -11,11 +11,6 @@ interface NavigationProps {
}
export default function Navigation({ showBackButton = false, backUrl = '/' }: NavigationProps) {
const pathname = usePathname();
const isActive = (path: string) => {
return pathname === path;
};
return (
<Card className="mb-6 border-0 shadow-sm">
@@ -30,36 +25,23 @@ export default function Navigation({ showBackButton = false, backUrl = '/' }: Na
</Link>
</Button>
)}
<div className="flex items-center space-x-1">
<Button
asChild
variant={isActive('/') ? 'default' : 'ghost'}
size="sm"
>
<Link href="/">
<Home className="w-4 h-4 mr-2" />
Accueil
</Link>
</Button>
<Button
asChild
variant={isActive('/admin') ? 'default' : 'ghost'}
size="sm"
>
<Link href="/admin">
<Settings className="w-4 h-4 mr-2" />
Administration
</Link>
</Button>
</div>
<h1 className="text-xl font-semibold text-slate-900 dark:text-slate-100">
Mes Budgets Participatifs - Admin
</h1>
</div>
<div className="flex items-center space-x-2">
<div className="text-sm text-slate-600 dark:text-slate-300">
Mes Budgets Participatifs
</div>
<Button asChild variant="ghost" size="sm">
<Link href="/admin/settings">
<Settings className="w-4 h-4 mr-2" />
Paramètres
</Link>
</Button>
<Button asChild variant="ghost" size="sm">
<Link href="/api/auth/signout">
Déconnexion
</Link>
</Button>
</div>
</div>
</div>

View File

@@ -0,0 +1,133 @@
'use client';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { FileText, Vote, CheckCircle, Check } from 'lucide-react';
interface StatusSwitchProps {
currentStatus: 'deposit' | 'voting' | 'closed';
onStatusChange: (newStatus: 'deposit' | 'voting' | 'closed') => Promise<void>;
disabled?: boolean;
}
const statusConfig = {
deposit: {
label: 'Dépôt',
icon: FileText,
color: 'bg-blue-500',
hoverColor: 'hover:bg-blue-600',
activeColor: 'bg-blue-600',
textColor: 'text-blue-600',
bgColor: 'bg-blue-50',
borderColor: 'border-blue-200'
},
voting: {
label: 'Vote',
icon: Vote,
color: 'bg-orange-500',
hoverColor: 'hover:bg-orange-600',
activeColor: 'bg-orange-600',
textColor: 'text-orange-600',
bgColor: 'bg-orange-50',
borderColor: 'border-orange-200'
},
closed: {
label: 'Terminée',
icon: CheckCircle,
color: 'bg-green-500',
hoverColor: 'hover:bg-green-600',
activeColor: 'bg-green-600',
textColor: 'text-green-600',
bgColor: 'bg-green-50',
borderColor: 'border-green-200'
}
};
export default function StatusSwitch({ currentStatus, onStatusChange, disabled = false }: StatusSwitchProps) {
const [localStatus, setLocalStatus] = useState(currentStatus);
const [isChanging, setIsChanging] = useState(false);
const [showSuccess, setShowSuccess] = useState(false);
// Synchroniser l'état local avec les props
useEffect(() => {
setLocalStatus(currentStatus);
}, [currentStatus]);
const handleStatusChange = async (newStatus: 'deposit' | 'voting' | 'closed') => {
if (disabled || isChanging || newStatus === localStatus) return;
setIsChanging(true);
try {
// Mettre à jour l'état local immédiatement pour un feedback visuel instantané
setLocalStatus(newStatus);
// Appeler la fonction de mise à jour
await onStatusChange(newStatus);
// Afficher la notification de succès
setShowSuccess(true);
setTimeout(() => setShowSuccess(false), 2000);
} catch (error) {
// En cas d'erreur, revenir à l'état précédent
setLocalStatus(currentStatus);
console.error('Erreur lors du changement de statut:', error);
} finally {
setIsChanging(false);
}
};
return (
<div className="relative">
{/* Notification de succès */}
{showSuccess && (
<div className="absolute -top-12 left-1/2 transform -translate-x-1/2 z-10">
<div className="bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg flex items-center gap-2 animate-in slide-in-from-top-2 duration-300">
<Check className="w-4 h-4" />
<span className="text-sm font-medium">Statut mis à jour !</span>
</div>
</div>
)}
<div className="flex items-center bg-slate-100 dark:bg-slate-800 rounded-xl p-1 shadow-inner">
{(['deposit', 'voting', 'closed'] as const).map((status, index) => {
const config = statusConfig[status];
const Icon = config.icon;
const isActive = localStatus === status;
return (
<Button
key={status}
variant="ghost"
size="sm"
disabled={disabled || isChanging}
onClick={() => handleStatusChange(status)}
className={`
relative flex-1 h-10 px-3 rounded-lg transition-all duration-300 ease-out
${isActive
? `${config.activeColor} text-white shadow-lg transform scale-105`
: `${config.hoverColor} ${config.textColor} hover:text-white`
}
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
${isChanging ? 'animate-pulse' : ''}
`}
>
<div className="flex items-center gap-2">
<Icon className={`w-4 h-4 transition-transform duration-300 ${isActive ? 'scale-110' : ''}`} />
<span className="text-sm font-medium">{config.label}</span>
</div>
{/* Indicateur de progression */}
{isActive && (
<div className="absolute inset-0 rounded-lg bg-gradient-to-r from-transparent via-white/20 to-transparent animate-pulse" />
)}
</Button>
);
})}
</div>
{/* Effet de brillance au survol */}
<div className="absolute inset-0 rounded-xl bg-gradient-to-r from-transparent via-white/10 to-transparent opacity-0 hover:opacity-100 transition-opacity duration-300 pointer-events-none" />
</div>
);
}