'use client'; import { useSession } from 'next-auth/react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { Button } from '../../components/ui/button'; import { Input } from '../../components/ui/input'; import { Label } from '../../components/ui/label'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '../../components/ui/table'; import { Card, CardContent, CardHeader, CardTitle, } from '../../components/ui/card'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../../components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../components/ui/select'; import { Checkbox } from '../../components/ui/checkbox'; import { toast } from 'sonner'; import { DatePicker } from '../../components/ui/date-picker'; import { format } from 'date-fns'; import { useSettings } from '../../context/SettingsContext'; interface Hour { id: string; date: string; duration: number; reason: string; status: string; userId: string; createdAt?: string; user: { email: string; firstName?: string; lastName?: string; role: string }; validatedBy?: { firstName?: string; lastName?: string; email: string }; } interface Settings { name: string; logo: string; } interface User { id: string; email: string; firstName?: string; lastName?: string; role: string; } export default function AdminPage() { const { data: session, status } = useSession(); const router = useRouter(); const [hours, setHours] = useState([]); const [settings, setSettings] = useState({ name: '', logo: '' }); const [newEmail, setNewEmail] = useState(''); const [newPassword, setNewPassword] = useState(''); const [newRole, setNewRole] = useState('MEMBER'); const [logoFile, setLogoFile] = useState(null); const [date, setDate] = useState(); const [duration, setDuration] = useState(''); const [reason, setReason] = useState(''); const [hoursInput, setHoursInput] = useState(''); const [minutesInput, setMinutesInput] = useState(''); const [newFirstName, setNewFirstName] = useState(''); const [newLastName, setNewLastName] = useState(''); const [showAll, setShowAll] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [selectedUser, setSelectedUser] = useState<{ id: string; name: string; } | null>(null); const [currentPassword, setCurrentPassword] = useState(''); const [changeNewPassword, setChangeNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [forceDelete, setForceDelete] = useState(false); const [showForceModal, setShowForceModal] = useState(false); const [selectedUserForReset, setSelectedUserForReset] = useState<{ id: string; name: string; } | null>(null); const [newPasswordForReset, setNewPasswordForReset] = useState(''); const [resetPasswordDialog, setResetPasswordDialog] = useState(false); const [confirmReset, setConfirmReset] = useState(false); const [confirmResetPassword, setConfirmResetPassword] = useState(false); const [users, setUsers] = useState([]); const [confirmPasswordChange, setConfirmPasswordChange] = useState(false); const [selectedUserIds, setSelectedUserIds] = useState([]); const [sortBy, setSortBy] = useState<'date' | 'createdAt'>('date'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); const [showPendingFirst, setShowPendingFirst] = useState(false); const { refetchSettings } = useSettings(); useEffect(() => { if (status === 'loading') return; if ( !session || (session.user.role !== 'ADMIN' && session.user.role !== 'SUPER_ADMIN') ) { router.push('/dashboard'); return; } fetchHours(); fetchSettings(); }, [session, status, router]); useEffect(() => { if ( session?.user?.role === 'SUPER_ADMIN' || session?.user?.role === 'ADMIN' ) { fetchUsers(); } }, [session]); const fetchHours = async () => { const res = await fetch('/api/hours'); if (res.ok) { const data = await res.json(); setHours(data); } }; const fetchSettings = async () => { const res = await fetch('/api/settings'); if (res.ok) { const data = await res.json(); setSettings(data); } }; const fetchUsers = async () => { const res = await fetch('/api/users'); if (res.ok) { const data = await res.json(); setUsers(data); } }; const handleValidate = async (id: string, status: string) => { await fetch(`/api/hours/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }), }); fetchHours(); toast.success(`Heure ${status === 'VALIDATED' ? 'validée' : 'rejetée'}`); }; const handleUpdateSettings = async (e: React.FormEvent) => { e.preventDefault(); let logoPath = settings.logo; if (logoFile) { const formData = new FormData(); formData.append('file', logoFile); const uploadRes = await fetch('/api/upload', { method: 'POST', body: formData, }); if (uploadRes.ok) { const uploadData = await uploadRes.json(); logoPath = uploadData.path; } else { alert('Erreur upload'); return; } } const res = await fetch('/api/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: settings.name, logo: logoPath }), }); if (res.ok) { setLogoFile(null); fetchSettings(); refetchSettings(); toast.success('Paramètres mis à jour'); } else { toast.error('Erreur lors de la mise à jour des paramètres'); } }; const handleCreateUser = async (e: React.FormEvent) => { e.preventDefault(); const res = await fetch('/api/auth/signup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: newEmail, password: newPassword, role: newRole, firstName: newFirstName, lastName: newLastName, }), }); if (res.ok) { setNewEmail(''); setNewPassword(''); setNewRole('MEMBER'); setNewFirstName(''); setNewLastName(''); toast.success('Utilisateur créé'); } }; const handleExport = (format: string) => { window.open(`/api/export?format=${format}`, '_blank'); }; const handleAddHour = async (e: React.FormEvent) => { e.preventDefault(); const totalMinutes = parseInt(hoursInput) * 60 + parseInt(minutesInput); const dateString = date ? format(date, 'yyyy-MM-dd') : ''; const res = await fetch('/api/hours', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: dateString, duration: totalMinutes, reason, userIds: selectedUserIds.length > 0 ? selectedUserIds : undefined, }), }); if (res.ok) { setDate(undefined); setHoursInput(''); setMinutesInput(''); setReason(''); setSelectedUserIds([]); fetchHours(); toast.success('Heure ajoutée avec succès'); } else { toast.error("Erreur lors de l'ajout de l'heure"); } }; const handleDelete = async (id: string) => { // Reject the hour entry then delete await handleValidate(id, 'REJECTED'); await fetch(`/api/hours/${id}`, { method: 'DELETE', }); fetchHours(); toast.success('Heure supprimée'); }; const userMap = users.reduce( (acc, user) => { const name = `${user.firstName || ''} ${user.lastName || ''}`.trim() || user.email; acc[user.id] = { name, email: user.email, role: user.role }; return acc; }, {} as Record, ); const userTotals = users.reduce( (acc, user) => { acc[user.id] = hours .filter((h) => h.userId === user.id && h.status === 'VALIDATED') .reduce((sum, h) => sum + h.duration, 0); return acc; }, {} as Record, ); const handleDeleteUser = async () => { if (!selectedUser) return; const res = await fetch(`/api/users/${selectedUser.id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: forceDelete }), }); if (res.ok) { setDialogOpen(false); setSelectedUser(null); fetchHours(); toast.success('Utilisateur supprimé'); } else if (res.status === 400) { setDialogOpen(false); setShowForceModal(true); } else { const data = await res.json(); toast.error(data.error || 'Erreur lors de la suppression'); } }; const handleForceDelete = async () => { if (!selectedUser) return; const res = await fetch(`/api/users/${selectedUser.id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: true }), }); if (res.ok) { setShowForceModal(false); setSelectedUser(null); fetchHours(); toast.success('Utilisateur supprimé'); } else { const data = await res.json(); toast.error(data.error || 'Erreur lors de la suppression'); } }; const handleConfirmChangePassword = async () => { const res = await fetch('/api/auth/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ currentPassword, newPassword: changeNewPassword }), }); if (res.ok) { toast.success('Mot de passe changé avec succès'); setCurrentPassword(''); setChangeNewPassword(''); setConfirmPassword(''); setConfirmPasswordChange(false); } else { const data = await res.json(); toast.error(data.error || 'Erreur lors du changement de mot de passe'); } }; const handleChangePassword = async (e: React.FormEvent) => { e.preventDefault(); if (changeNewPassword !== confirmPassword) { toast.error('Les mots de passe ne correspondent pas'); return; } await handleConfirmChangePassword(); }; const handleResetPassword = () => { setConfirmResetPassword(true); }; const handleConfirmResetPassword = async () => { if (!selectedUserForReset || !newPasswordForReset) return; const res = await fetch(`/api/users/${selectedUserForReset.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: newPasswordForReset }), }); if (res.ok) { setResetPasswordDialog(false); setConfirmResetPassword(false); setSelectedUserForReset(null); setNewPasswordForReset(''); toast.success('Mot de passe réinitialisé'); } else { const data = await res.json(); toast.error(data.error || 'Erreur lors de la réinitialisation'); } }; if (status === 'loading') return
Chargement...
; const isSuperAdmin = session?.user?.role === 'SUPER_ADMIN'; const sortedHours = [...hours].sort((a, b) => { if (showPendingFirst) { if (a.status === 'PENDING' && b.status !== 'PENDING') return -1; if (a.status !== 'PENDING' && b.status === 'PENDING') return 1; } let dateA, dateB; if (sortBy === 'createdAt') { dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0; dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0; } else { dateA = new Date(a.date).getTime(); dateB = new Date(b.date).getTime(); } if (sortOrder === 'asc') { return dateA - dateB; } else { return dateB - dateA; } }); const displayedHours = showAll ? sortedHours : sortedHours.slice(0, 10); const formatHours = (minutes: number) => { const h = Math.floor(minutes / 60); const m = minutes % 60; return `${h}h ${m}min`; }; return (

Administration

Ajouter des heures
{users.map((user) => (
{ if (checked) { setSelectedUserIds([...selectedUserIds, user.id]); } else { setSelectedUserIds( selectedUserIds.filter((id) => id !== user.id), ); } }} />
))}
setHoursInput(e.target.value)} min="0" required />
setMinutesInput(e.target.value)} min="0" max="59" required />
setReason(e.target.value)} required />
Gestion des heures
setShowPendingFirst(checked as boolean) } />
Date Durée Raison Statut Utilisateur Actions {displayedHours.map((hour) => ( {new Date(hour.date).toLocaleDateString()} {hour.duration} min {hour.reason} {hour.status === 'VALIDATED' && hour.validatedBy ? `Validé par ${hour.validatedBy.firstName || ''} ${hour.validatedBy.lastName || ''}`.trim() || hour.validatedBy.email : hour.status === 'REJECTED' && hour.validatedBy ? `Rejeté par ${hour.validatedBy.firstName || ''} ${hour.validatedBy.lastName || ''}`.trim() || hour.validatedBy.email : hour.status === 'PENDING' ? 'En attente' : hour.status} {userMap[hour.userId]?.name} {hour.status === 'VALIDATED' || hour.status === 'REJECTED' ? ( ) : ( <> )} ))}
{hours.length > 10 && !showAll && (
)}
Totaux par utilisateur
Utilisateur Heures Validées Actions {Object.entries(userTotals).map(([userId, total]) => ( {userMap[userId]?.name} {formatHours(total)} {isSuperAdmin ? ( userMap[userId]?.role === 'SUPER_ADMIN' ? ( 'Super Admin' ) : ( <> ) ) : userMap[userId]?.role === 'SUPER_ADMIN' ? ( 'Gestionnaire' ) : userMap[userId]?.role === 'ADMIN' ? ( 'Bureau' ) : ( 'Membre' )} ))}
Changer mot de passe
setCurrentPassword(e.target.value)} required />
setChangeNewPassword(e.target.value)} required />
setConfirmPassword(e.target.value)} required />
{isSuperAdmin && ( Créer un compte
setNewEmail(e.target.value)} required />
setNewPassword(e.target.value)} required />
setNewFirstName(e.target.value)} required />
setNewLastName(e.target.value)} required />
)} Paramètres du Club
setSettings({ ...settings, name: e.target.value }) } />
setLogoFile(e.target.files?.[0] || null)} /> {settings.logo &&

Actuel : {settings.logo}

}
{selectedUser && ( Confirmation Êtes-vous sûr de vouloir supprimer cet utilisateur ?
setForceDelete(checked as boolean)} />
)} {selectedUser && showForceModal && ( Confirmation de suppression forcée Cette action supprimera l'utilisateur sans tenir compte de ses heures. Êtes-vous sûr ? )} {selectedUserForReset && ( Réinitialiser le mot de passe Entrez un nouveau mot de passe pour {selectedUserForReset.name}.
setNewPasswordForReset(e.target.value)} required />
)} {selectedUserForReset && confirmResetPassword && ( Confirmation de réinitialisation Êtes-vous sûr de vouloir réinitialiser le mot de passe de{' '} {selectedUserForReset.name} ? )}
); }