diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 9b818a6..1e24b04 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -31,6 +31,7 @@ import { 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; @@ -40,6 +41,7 @@ interface Hour { status: string; userId: string; user: { email: string; firstName?: string; lastName?: string; role: string }; + validatedBy?: { firstName?: string; lastName?: string; email: string }; } interface Settings { @@ -47,6 +49,14 @@ interface Settings { 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(); @@ -74,6 +84,17 @@ export default function AdminPage() { 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 { refetchSettings } = useSettings(); useEffect(() => { if (status === 'loading') return; @@ -88,6 +109,12 @@ export default function AdminPage() { fetchSettings(); }, [session, status, router]); + useEffect(() => { + if (session?.user?.role === 'SUPER_ADMIN') { + fetchUsers(); + } + }, [session]); + const fetchHours = async () => { const res = await fetch('/api/hours'); if (res.ok) { @@ -104,6 +131,14 @@ export default function AdminPage() { } }; + 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', @@ -131,13 +166,19 @@ export default function AdminPage() { return; } } - await fetch('/api/settings', { + const res = await fetch('/api/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: settings.name, logo: logoPath }), }); - setLogoFile(null); - fetchSettings(); + 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) => { @@ -196,36 +237,23 @@ export default function AdminPage() { method: 'DELETE', }); fetchHours(); + toast.success('Heure supprimée'); }; - const userDisplayNames = hours.reduce( - (acc, hour) => { - const name = - `${hour.user.firstName || ''} ${hour.user.lastName || ''}`.trim() || - hour.user.email; - acc[hour.user.email] = name; + 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, + {} as Record, ); - const userMap = {} as Record< - string, - { name: string; email: string; role: string } - >; - hours.forEach((hour) => { - userMap[hour.userId] = { - name: userDisplayNames[hour.user.email], - email: hour.user.email, - role: hour.user.role, - }; - }); - - const userTotals = hours.reduce( - (acc, hour) => { - if (hour.status === 'VALIDATED') { - acc[hour.userId] = (acc[hour.userId] || 0) + hour.duration; - } + 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, @@ -270,12 +298,7 @@ export default function AdminPage() { } }; - const handleChangePassword = async (e: React.FormEvent) => { - e.preventDefault(); - if (changeNewPassword !== confirmPassword) { - toast.error('Les mots de passe ne correspondent pas'); - return; - } + const handleConfirmChangePassword = async () => { const res = await fetch('/api/auth/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -286,12 +309,45 @@ export default function AdminPage() { 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'; @@ -382,8 +438,17 @@ export default function AdminPage() { {hour.duration} min {hour.reason} - {hour.status} - {userDisplayNames[hour.user.email]} + + {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' ? ( @@ -440,19 +505,34 @@ export default function AdminPage() { {userMap[userId]?.role === 'SUPER_ADMIN' ? ( 'Super Admin' ) : ( - + <> + + + )} @@ -558,7 +638,7 @@ export default function AdminPage() { className="w-full p-2 border rounded bg-white text-black dark:bg-stone-800 dark:text-white dark:border-stone-600" > - + @@ -644,6 +724,50 @@ export default function AdminPage() { )} + {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} ? + + + + + + + + )}