import React, { useCallback, useEffect, useRef, useState } from 'react'; import LoadingCard from '@/components/LoadingCard'; import MainCard from '@/components/MainCard'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { AlertCircle, CheckCircle2, FileText, FolderOpen, KeyRound, Loader2, RefreshCw, User, } from 'lucide-react'; import { useAuth } from '../context/useAuth'; import api from '../api/axios'; const FILES_ENDPOINT = '/file/files/'; const getErrorText = (value) => { if (typeof value === 'string') return value; if (Array.isArray(value)) return value.map(getErrorText).filter(Boolean).join(' '); return ''; }; const getRequestError = (error, fallback) => { const data = error.response?.data; return ( getErrorText(data?.current_password) || getErrorText(data?.new_password) || getErrorText(data?.confirm_password) || getErrorText(data?.non_field_errors) || getErrorText(data?.detail) || getErrorText(data?.error) || getErrorText(Array.isArray(data) ? data : null) || fallback ); }; const parseFilesResponse = (data) => { const items = Array.isArray(data) ? data : data?.results; if (!Array.isArray(items)) { throw new Error('Réponse de fichiers invalide.'); } // La pagination reste sur le même endpoint Axios, même si Django // renvoie une URL absolue dans "next". const next = typeof data?.next === 'string' && data.next ? `${FILES_ENDPOINT}${new URL(data.next, 'https://localhost').search}` : null; return { items, next, total: Array.isArray(data) ? data.length : Number.isInteger(data?.count) && data.count >= 0 ? data.count : null, }; }; const formatDate = (value) => { if (!value) return 'Non renseignée'; const date = new Date(value); if (Number.isNaN(date.getTime())) return 'Non renseignée'; return date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric', }); }; const ProfileField = ({ label, children }) => (
{label}
{children || 'Non renseigné'}
); const ProfileContent = ({ user }) => { const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [passwordError, setPasswordError] = useState(''); const [passwordSuccess, setPasswordSuccess] = useState(''); const [savingPassword, setSavingPassword] = useState(false); const passwordRequestPending = useRef(false); const [filesState, setFilesState] = useState({ items: [], next: null, total: null, loading: true, loadingMore: false, error: '', }); const filesRequest = useRef(null); const loadFiles = useCallback(async (nextPage = null) => { filesRequest.current?.abort(); const controller = new AbortController(); filesRequest.current = controller; const append = Boolean(nextPage); setFilesState((previous) => ({ ...previous, loading: !append, loadingMore: append, error: '', })); try { const response = await api.get(nextPage || FILES_ENDPOINT, { signal: controller.signal, }); const page = parseFilesResponse(response.data); if (controller.signal.aborted) return; setFilesState((previous) => ({ items: append ? Array.from(new Map( [...previous.items, ...page.items].map((file) => [file.id, file]), ).values()) : page.items, next: page.next, total: page.total, loading: false, loadingMore: false, error: '', })); } catch (error) { if (controller.signal.aborted) return; setFilesState((previous) => ({ ...previous, loading: false, loadingMore: false, error: getRequestError(error, 'Impossible de charger tes fichiers. Réessaie dans un instant.'), })); } }, []); useEffect(() => { loadFiles(); return () => filesRequest.current?.abort(); }, [loadFiles]); const resetPasswordForm = () => { setCurrentPassword(''); setNewPassword(''); setConfirmPassword(''); setPasswordError(''); }; const handlePasswordDialogChange = (open) => { if (passwordRequestPending.current) return; resetPasswordForm(); if (open) setPasswordSuccess(''); setPasswordDialogOpen(open); }; const handleChangePassword = async (event) => { event.preventDefault(); if (passwordRequestPending.current) return; setPasswordError(''); setPasswordSuccess(''); if (!currentPassword || !newPassword || !confirmPassword) { setPasswordError('Remplis tous les champs.'); return; } if (newPassword !== confirmPassword) { setPasswordError('Les nouveaux mots de passe ne correspondent pas.'); return; } passwordRequestPending.current = true; setSavingPassword(true); try { await api.post('/users/change-password/', { current_password: currentPassword, new_password: newPassword, confirm_password: confirmPassword, }); resetPasswordForm(); setPasswordDialogOpen(false); setPasswordSuccess('Ton mot de passe a bien été modifié.'); } catch (error) { setPasswordError(getRequestError(error, 'Impossible de modifier le mot de passe.')); } finally { passwordRequestPending.current = false; setSavingPassword(false); } }; const fullName = [user.first_name, user.last_name].filter(Boolean).join(' ').trim(); const displayName = fullName || user.username || 'Mon profil'; const initials = [user.first_name?.trim()?.[0], user.last_name?.trim()?.[0]] .filter(Boolean).join('').toUpperCase() || user.username?.[0]?.toUpperCase() || '?'; const filesBusy = filesState.loading || filesState.loadingMore; return (

{displayName}

{user.is_admin ? 'Administrateur' : 'Utilisateur'}

Informations personnelles

Les informations associées à ton compte.

{user.first_name} {user.last_name} {user.username} {formatDate(user.date_joined)}

Mot de passe

Modifie le mot de passe utilisé pour te connecter.

{passwordSuccess && (
)}

Mes fichiers

Les documents mis à ta disposition.

{filesState.error && (
)} {filesState.loading && filesState.items.length === 0 && (
)} {!filesState.loading && !filesState.error && filesState.items.length === 0 && (
)} {filesState.items.length > 0 && ( <>
    {filesState.items.map((file) => (
  • {file.name || 'Fichier sans nom'}

    {typeof file.type === 'string' && file.type.trim() ? file.type : 'Sans catégorie'}
  • ))}

{filesState.items.length} fichier{filesState.items.length > 1 ? 's' : ''} affiché{filesState.items.length > 1 ? 's' : ''} {filesState.total !== null && filesState.total > filesState.items.length ? ` sur ${filesState.total}` : ''}

{filesState.next && ( )}
)}
Modifier le mot de passe Saisis ton mot de passe actuel, puis le nouveau deux fois.
setCurrentPassword(event.target.value)} disabled={savingPassword} required />
setNewPassword(event.target.value)} disabled={savingPassword} required />
setConfirmPassword(event.target.value)} disabled={savingPassword} required />
{passwordError &&

{passwordError}

}
); }; const MyspacePage = () => { const { user } = useAuth(); if (!user) { return ; } // Réinitialise les formulaires et les fichiers si le compte change. return ; }; export default MyspacePage;