// SettingsView - Impostazioni profilo utente function SettingsView() { const { user, updateProfile, changePassword, logout, refreshUser } = useAuth(); const toast = useToast(); const [activeTab, setActiveTab] = React.useState('profile'); // Profile form state const [profileData, setProfileData] = React.useState({ full_name: user?.full_name || '', email: user?.email || '', }); const [profileLoading, setProfileLoading] = React.useState(false); // Avatar state const [avatarFile, setAvatarFile] = React.useState(null); const [avatarPreview, setAvatarPreview] = React.useState( user?.avatar_path ? ApiUsers.getAvatarUrl(user.avatar_path) : null ); const [avatarLoading, setAvatarLoading] = React.useState(false); const [avatarDeleted, setAvatarDeleted] = React.useState(false); const fileInputRef = React.useRef(null); // Update avatar preview when user changes React.useEffect(() => { if (user?.avatar_path && !avatarFile && !avatarDeleted) { setAvatarPreview(ApiUsers.getAvatarUrl(user.avatar_path)); } }, [user?.avatar_path]); const handleAvatarChange = (e) => { const file = e.target.files[0]; if (file) { // Validate file type const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!allowedTypes.includes(file.type)) { toast('Tipo file non supportato. Usa JPG, PNG, GIF o WEBP.', 'error'); return; } // Validate file size (5MB max) if (file.size > 5 * 1024 * 1024) { toast('File troppo grande. Massimo 5MB.', 'error'); return; } setAvatarFile(file); setAvatarPreview(URL.createObjectURL(file)); setAvatarDeleted(false); } }; const handleRemoveAvatar = () => { setAvatarFile(null); if (avatarPreview && !avatarPreview.startsWith('/api/')) { URL.revokeObjectURL(avatarPreview); } setAvatarPreview(null); setAvatarDeleted(true); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleAvatarSave = async () => { setAvatarLoading(true); try { if (avatarFile) { await ApiUsers.uploadMyAvatar(avatarFile); toast('Avatar aggiornato con successo', 'success'); } else if (avatarDeleted && user?.avatar_path) { await ApiUsers.deleteMyAvatar(); toast('Avatar rimosso con successo', 'success'); } // Refresh user data in context if (refreshUser) { await refreshUser(); } setAvatarFile(null); setAvatarDeleted(false); } catch (error) { toast(error.message || 'Errore durante il salvataggio dell\'avatar', 'error'); } finally { setAvatarLoading(false); } }; // Cleanup preview URL on unmount React.useEffect(() => { return () => { if (avatarPreview && !avatarPreview.startsWith('/api/')) { URL.revokeObjectURL(avatarPreview); } }; }, [avatarPreview]); // Password form state const [passwordData, setPasswordData] = React.useState({ current_password: '', new_password: '', confirm_password: '', }); const [passwordLoading, setPasswordLoading] = React.useState(false); const [showPasswords, setShowPasswords] = React.useState({ current: false, new: true, confirm: true, }); // Calendar integration state (admin only) const [calendarStatus, setCalendarStatus] = React.useState({ connected: false, connected_at: null, shared: false }); const [calendarLoading, setCalendarLoading] = React.useState(false); const [sharingLoading, setSharingLoading] = React.useState(false); const isAdmin = user?.role === 'admin'; // Fetch calendar status on mount (admin only) React.useEffect(() => { if (isAdmin) { fetchCalendarStatus(); } }, [isAdmin]); // Handle OAuth callback React.useEffect(() => { if (isAdmin) { const hash = window.location.hash; const params = new URLSearchParams(hash.split('?')[1] || ''); if (params.get('calendar_success') === 'true') { // Tokens are now saved directly by backend callback toast('Google Calendar connesso con successo', 'success'); fetchCalendarStatus(); // Clean URL window.history.replaceState(null, '', '/#/settings'); setActiveTab('integrations'); } else if (params.get('calendar_error')) { const error = decodeURIComponent(params.get('calendar_error')); toast(`Errore: ${error}`, 'error'); // Clean URL window.history.replaceState(null, '', '/#/settings'); setActiveTab('integrations'); } } }, [isAdmin]); const fetchCalendarStatus = async () => { try { const status = await ApiUtils.getCalendarStatus(); setCalendarStatus(status); } catch (error) { console.error('Error fetching calendar status:', error); } }; const handleDisconnectCalendar = async () => { setCalendarLoading(true); try { await ApiUtils.disconnectCalendar(); setCalendarStatus({ connected: false, connected_at: null, shared: false }); toast('Google Calendar disconnesso', 'success'); } catch (error) { toast('Errore durante la disconnessione', 'error'); console.error('Error disconnecting calendar:', error); } finally { setCalendarLoading(false); } }; const handleToggleSharing = async () => { const newShared = !calendarStatus.shared; setSharingLoading(true); try { await ApiUtils.setCalendarSharing(newShared); setCalendarStatus(prev => ({ ...prev, shared: newShared })); toast(newShared ? 'Calendario condiviso con il team' : 'Condivisione calendario disattivata', 'success'); } catch (error) { toast('Errore durante l\'aggiornamento della condivisione', 'error'); console.error('Error toggling calendar sharing:', error); } finally { setSharingLoading(false); } }; const handleProfileUpdate = async (e) => { e.preventDefault(); setProfileLoading(true); const result = await updateProfile(profileData); if (result.success) { toast('Profilo aggiornato con successo', 'success'); } else { toast(result.error, 'error'); } setProfileLoading(false); }; const handlePasswordChange = async (e) => { e.preventDefault(); if (passwordData.new_password !== passwordData.confirm_password) { toast('Le password non coincidono', 'error'); return; } if (passwordData.new_password.length < 8) { toast('La password deve essere di almeno 8 caratteri', 'error'); return; } setPasswordLoading(true); const result = await changePassword(passwordData.current_password, passwordData.new_password); if (result.success) { toast('Password modificata con successo', 'success'); setPasswordData({ current_password: '', new_password: '', confirm_password: '' }); } else { toast(result.error, 'error'); } setPasswordLoading(false); }; const tabs = [ { id: 'profile', label: 'Profilo', icon: '👤' }, { id: 'security', label: 'Sicurezza', icon: '🔒' }, { id: 'notifications', label: 'Notifiche Email', icon: '📧' }, ...(isAdmin ? [{ id: 'integrations', label: 'Integrazioni', icon: '🔗' }] : []), ]; return (

Impostazioni

Gestisci il tuo account e le preferenze

{/* Tabs */}
{/* Profile Tab */} {activeTab === 'profile' && (
{/* Avatar Section */}

Immagine Profilo

{avatarPreview ? ( Avatar ) : (
{user?.full_name ? user.full_name.charAt(0).toUpperCase() : '?'}
)} {avatarPreview && ( )}
{(avatarFile || avatarDeleted) && ( )}

JPG, PNG, GIF o WEBP. Massimo 5MB.

{/* Profile Info Section */}

Informazioni Profilo

setProfileData({ ...profileData, full_name: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" />
setProfileData({ ...profileData, email: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" required />
)} {/* Security Tab */} {activeTab === 'security' && (

Modifica Password

setPasswordData({ ...passwordData, current_password: e.target.value })} className="w-full px-3 py-2 pr-10 border border-slate-200 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" required />
setPasswordData({ ...passwordData, new_password: e.target.value })} className="w-full px-3 py-2 pr-10 border border-slate-200 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" required minLength={8} />
setPasswordData({ ...passwordData, confirm_password: e.target.value })} className="w-full px-3 py-2 pr-10 border border-slate-200 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" required />

Esci

Disconnettiti dal tuo account su questo dispositivo.

)} {/* Notifications Tab */} {activeTab === 'notifications' && ( )} {/* Integrations Tab (Admin only) */} {activeTab === 'integrations' && isAdmin && (

Integrazioni Esterne

{/* Google Calendar Card */}
{/* Google Logo */}
{/* Content */}

Google Calendar

{calendarStatus.connected ? ( Connesso ) : ( Non connesso )}

Sincronizza il tuo calendario Google per visualizzare gli eventi nel calendario scadenze. Gli eventi del calendario verranno mostrati insieme alle scadenze delle pratiche.

{calendarStatus.connected && calendarStatus.connected_at && (

Connesso il {new Date(calendarStatus.connected_at).toLocaleDateString('it-IT', { day: '2-digit', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' })}

)} {/* Action Buttons */}
{calendarStatus.connected ? ( ) : ( )}
{/* Sharing Toggle (only when connected) */} {calendarStatus.connected && (
Condividi con il team

Permetti agli altri utenti di vedere gli eventi del tuo calendario Google nella dashboard.

{calendarStatus.shared && (
Il calendario e visibile a tutti gli utenti
)}
)}
)}
); } window.SettingsView = SettingsView;