Update Settings.tsx
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
|||||||
AlertTriangle, User as UserIcon, Server, Bell, Clock, FileText,
|
AlertTriangle, User as UserIcon, Server, Bell, Clock, FileText,
|
||||||
Lock, Megaphone, CheckCircle2, Info, Hammer, Link as LinkIcon,
|
Lock, Megaphone, CheckCircle2, Info, Hammer, Link as LinkIcon,
|
||||||
Eye, Calendar, List, UserCog, Mail, Power, MapPin, CreditCard,
|
Eye, Calendar, List, UserCog, Mail, Power, MapPin, CreditCard,
|
||||||
ToggleLeft, ToggleRight, LayoutGrid, PieChart, Users, Send, Cloud, HardDrive
|
ToggleLeft, ToggleRight, LayoutGrid, PieChart, Users, Send, Cloud, HardDrive, ChevronRight
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
export const SettingsPage: React.FC = () => {
|
export const SettingsPage: React.FC = () => {
|
||||||
@@ -203,359 +203,62 @@ export const SettingsPage: React.FC = () => {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, [isPrivileged]);
|
}, [isPrivileged]);
|
||||||
|
|
||||||
// --- HANDLERS ---
|
// --- HANDLERS (Omitted details for brevity as they are unchanged) ---
|
||||||
|
|
||||||
const handleProfileSubmit = async (e: React.FormEvent) => {
|
const handleProfileSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); setProfileSaving(true); setProfileMsg('');
|
||||||
setProfileSaving(true);
|
try { await CondoService.updateProfile(profileForm); setProfileMsg('Profilo aggiornato con successo!'); setTimeout(() => setProfileMsg(''), 3000); setProfileForm(prev => ({ ...prev, password: '' })); } catch (e) { setProfileMsg('Errore aggiornamento profilo'); } finally { setProfileSaving(false); }
|
||||||
setProfileMsg('');
|
|
||||||
try {
|
|
||||||
await CondoService.updateProfile(profileForm);
|
|
||||||
setProfileMsg('Profilo aggiornato con successo!');
|
|
||||||
setTimeout(() => setProfileMsg(''), 3000);
|
|
||||||
setProfileForm(prev => ({ ...prev, password: '' }));
|
|
||||||
} catch (e) {
|
|
||||||
setProfileMsg('Errore aggiornamento profilo');
|
|
||||||
} finally {
|
|
||||||
setProfileSaving(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGeneralSubmit = async (e: React.FormEvent) => {
|
const handleGeneralSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); if (!activeCondo) return; setSaving(true); setSuccessMsg('');
|
||||||
if (!activeCondo) return;
|
try { await CondoService.saveCondo(activeCondo); setSuccessMsg('Dati condominio aggiornati!'); setTimeout(() => setSuccessMsg(''), 3000); const list = await CondoService.getCondos(); setCondos(list); } catch (e) { console.error(e); } finally { setSaving(false); }
|
||||||
setSaving(true);
|
|
||||||
setSuccessMsg('');
|
|
||||||
try {
|
|
||||||
await CondoService.saveCondo(activeCondo);
|
|
||||||
setSuccessMsg('Dati condominio aggiornati!');
|
|
||||||
setTimeout(() => setSuccessMsg(''), 3000);
|
|
||||||
const list = await CondoService.getCondos();
|
|
||||||
setCondos(list);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFeaturesSubmit = async (e: React.FormEvent) => {
|
const handleFeaturesSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); if (!globalSettings) return; setSaving(true);
|
||||||
if (!globalSettings) return;
|
try { await CondoService.updateSettings(globalSettings); setSuccessMsg('Configurazione salvata!'); setTimeout(() => setSuccessMsg(''), 3000); window.location.reload(); } catch(e) { console.error(e); } finally { setSaving(false); }
|
||||||
setSaving(true);
|
|
||||||
try {
|
|
||||||
await CondoService.updateSettings(globalSettings);
|
|
||||||
setSuccessMsg('Configurazione salvata!');
|
|
||||||
setTimeout(() => setSuccessMsg(''), 3000);
|
|
||||||
window.location.reload();
|
|
||||||
} catch(e) {
|
|
||||||
console.error(e);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSmtpSubmit = async (e: React.FormEvent) => {
|
const handleSmtpSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); if (!globalSettings) return; setSaving(true);
|
||||||
if (!globalSettings) return;
|
try { await CondoService.updateSettings(globalSettings); setSuccessMsg('Configurazione SMTP salvata!'); setTimeout(() => { setSuccessMsg(''); setShowSmtpModal(false); }, 2000); } catch (e) { console.error(e); } finally { setSaving(false); }
|
||||||
setSaving(true);
|
|
||||||
try {
|
|
||||||
await CondoService.updateSettings(globalSettings);
|
|
||||||
setSuccessMsg('Configurazione SMTP salvata!');
|
|
||||||
setTimeout(() => {
|
|
||||||
setSuccessMsg('');
|
|
||||||
setShowSmtpModal(false);
|
|
||||||
}, 2000);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSmtpTest = async () => {
|
const handleSmtpTest = async () => {
|
||||||
if (!globalSettings?.smtpConfig) return;
|
if (!globalSettings?.smtpConfig) return; setTestingSmtp(true); setTestSmtpMsg('');
|
||||||
setTestingSmtp(true);
|
try { await CondoService.testSmtpConfig(globalSettings.smtpConfig); setTestSmtpMsg('Successo! Email di prova inviata.'); } catch(e: any) { setTestSmtpMsg('Errore: ' + (e.message || "Impossibile connettersi")); } finally { setTestingSmtp(false); }
|
||||||
setTestSmtpMsg('');
|
|
||||||
try {
|
|
||||||
await CondoService.testSmtpConfig(globalSettings.smtpConfig);
|
|
||||||
setTestSmtpMsg('Successo! Email di prova inviata.');
|
|
||||||
} catch(e: any) {
|
|
||||||
setTestSmtpMsg('Errore: ' + (e.message || "Impossibile connettersi"));
|
|
||||||
} finally {
|
|
||||||
setTestingSmtp(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStorageSubmit = async (e: React.FormEvent) => {
|
const handleStorageSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); if (!globalSettings) return; setSaving(true);
|
||||||
if (!globalSettings) return;
|
try { await CondoService.updateSettings(globalSettings); setSuccessMsg('Configurazione Storage salvata!'); setTimeout(() => setSuccessMsg(''), 3000); } catch (e) { console.error(e); } finally { setSaving(false); }
|
||||||
setSaving(true);
|
|
||||||
try {
|
|
||||||
await CondoService.updateSettings(globalSettings);
|
|
||||||
setSuccessMsg('Configurazione Storage salvata!');
|
|
||||||
setTimeout(() => setSuccessMsg(''), 3000);
|
|
||||||
} catch (e) { console.error(e); }
|
|
||||||
finally { setSaving(false); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNewYear = async () => {
|
const handleNewYear = async () => {
|
||||||
if (!globalSettings) return;
|
if (!globalSettings) return; const nextYear = globalSettings.currentYear + 1;
|
||||||
const nextYear = globalSettings.currentYear + 1;
|
if (window.confirm(`Sei sicuro di voler chiudere l'anno ${globalSettings.currentYear} e aprire il ${nextYear}?`)) { setSaving(true); try { const newSettings = { ...globalSettings, currentYear: nextYear }; await CondoService.updateSettings(newSettings); setGlobalSettings(newSettings); setSuccessMsg(`Anno ${nextYear} aperto!`); } catch(e) { console.error(e); } finally { setSaving(false); } }
|
||||||
if (window.confirm(`Sei sicuro di voler chiudere l'anno ${globalSettings.currentYear} e aprire il ${nextYear}?`)) {
|
|
||||||
setSaving(true);
|
|
||||||
try {
|
|
||||||
const newSettings = { ...globalSettings, currentYear: nextYear };
|
|
||||||
await CondoService.updateSettings(newSettings);
|
|
||||||
setGlobalSettings(newSettings);
|
|
||||||
setSuccessMsg(`Anno ${nextYear} aperto!`);
|
|
||||||
} catch(e) {
|
|
||||||
console.error(e);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// CRUD Handlers (omitted repeated code for brevity, logic is same as before)
|
|
||||||
const openAddCondoModal = () => {
|
|
||||||
setEditingCondo(null);
|
|
||||||
setCondoForm({ name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', paypalClientId: '', defaultMonthlyQuota: 100, dueDay: 10 });
|
|
||||||
setShowCondoModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openEditCondoModal = (c: Condo) => {
|
|
||||||
setEditingCondo(c);
|
|
||||||
setCondoForm({ name: c.name, address: c.address || '', streetNumber: c.streetNumber || '', city: c.city || '', province: c.province || '', zipCode: c.zipCode || '', notes: c.notes || '', paypalClientId: c.paypalClientId || '', defaultMonthlyQuota: c.defaultMonthlyQuota, dueDay: c.dueDay || 10 });
|
|
||||||
setShowCondoModal(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CRUD Handlers
|
||||||
|
const openAddCondoModal = () => { setEditingCondo(null); setCondoForm({ name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', paypalClientId: '', defaultMonthlyQuota: 100, dueDay: 10 }); setShowCondoModal(true); };
|
||||||
|
const openEditCondoModal = (c: Condo) => { setEditingCondo(c); setCondoForm({ name: c.name, address: c.address || '', streetNumber: c.streetNumber || '', city: c.city || '', province: c.province || '', zipCode: c.zipCode || '', notes: c.notes || '', paypalClientId: c.paypalClientId || '', defaultMonthlyQuota: c.defaultMonthlyQuota, dueDay: c.dueDay || 10 }); setShowCondoModal(true); };
|
||||||
const handleCondoSubmit = async (e: React.FormEvent) => {
|
const handleCondoSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault(); try { const payload: Condo = { id: editingCondo ? editingCondo.id : '', name: condoForm.name, address: condoForm.address, streetNumber: condoForm.streetNumber, city: condoForm.city, province: condoForm.province, zipCode: condoForm.zipCode, notes: condoForm.notes, paypalClientId: condoForm.paypalClientId, defaultMonthlyQuota: condoForm.defaultMonthlyQuota, dueDay: condoForm.dueDay }; const savedCondo = await CondoService.saveCondo(payload); const list = await CondoService.getCondos(); setCondos(list); if (activeCondo?.id === savedCondo.id) { setActiveCondo(savedCondo); } if (!activeCondo && list.length === 1) { CondoService.setActiveCondo(savedCondo.id); } setShowCondoModal(false); window.dispatchEvent(new Event('condo-updated')); } catch (e) { console.error(e); alert("Errore nel salvataggio del condominio. Assicurati di essere amministratore."); }
|
||||||
try {
|
|
||||||
const payload: Condo = {
|
|
||||||
id: editingCondo ? editingCondo.id : '',
|
|
||||||
name: condoForm.name,
|
|
||||||
address: condoForm.address,
|
|
||||||
streetNumber: condoForm.streetNumber,
|
|
||||||
city: condoForm.city,
|
|
||||||
province: condoForm.province,
|
|
||||||
zipCode: condoForm.zipCode,
|
|
||||||
notes: condoForm.notes,
|
|
||||||
paypalClientId: condoForm.paypalClientId,
|
|
||||||
defaultMonthlyQuota: condoForm.defaultMonthlyQuota,
|
|
||||||
dueDay: condoForm.dueDay
|
|
||||||
};
|
};
|
||||||
const savedCondo = await CondoService.saveCondo(payload);
|
const handleDeleteCondo = async (id: string) => { if(!window.confirm("Eliminare questo condominio? Attenzione: operazione irreversibile.")) return; try { await CondoService.deleteCondo(id); setCondos(await CondoService.getCondos()); window.dispatchEvent(new Event('condo-updated')); } catch (e) { console.error(e); } };
|
||||||
const list = await CondoService.getCondos();
|
const openAddFamilyModal = () => { setEditingFamily(null); setFamilyForm({ name: '', unitNumber: '', stair: '', floor: '', notes: '', contactEmail: '', customMonthlyQuota: '' }); setShowFamilyModal(true); };
|
||||||
setCondos(list);
|
const openEditFamilyModal = (family: Family) => { setEditingFamily(family); setFamilyForm({ name: family.name, unitNumber: family.unitNumber, stair: family.stair || '', floor: family.floor || '', notes: family.notes || '', contactEmail: family.contactEmail || '', customMonthlyQuota: family.customMonthlyQuota ? family.customMonthlyQuota.toString() : '' }); setShowFamilyModal(true); };
|
||||||
if (activeCondo?.id === savedCondo.id) { setActiveCondo(savedCondo); }
|
const handleDeleteFamily = async (id: string) => { if (!window.confirm('Eliminare questa famiglia?')) return; try { await CondoService.deleteFamily(id); setFamilies(families.filter(f => f.id !== id)); } catch (e) { console.error(e); } };
|
||||||
if (!activeCondo && list.length === 1) { CondoService.setActiveCondo(savedCondo.id); }
|
const handleFamilySubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const quota = familyForm.customMonthlyQuota && familyForm.customMonthlyQuota.trim() !== '' ? parseFloat(familyForm.customMonthlyQuota) : undefined; const payload: any = { name: familyForm.name, unitNumber: familyForm.unitNumber, stair: familyForm.stair, floor: familyForm.floor, notes: familyForm.notes, contactEmail: familyForm.contactEmail, customMonthlyQuota: quota }; if (editingFamily) { const updatedFamily = { ...editingFamily, ...payload }; await CondoService.updateFamily(updatedFamily); setFamilies(families.map(f => f.id === updatedFamily.id ? updatedFamily : f)); } else { const newFamily = await CondoService.addFamily(payload); setFamilies([...families, newFamily]); } setShowFamilyModal(false); } catch (e: any) { console.error(e); alert(`Errore: ${e.message || "Impossibile salvare la famiglia."}`); } };
|
||||||
setShowCondoModal(false);
|
const openAddUserModal = () => { setEditingUser(null); setUserForm({ name: '', email: '', password: '', phone: '', role: 'user', familyId: '', receiveAlerts: true }); setShowUserModal(true); };
|
||||||
window.dispatchEvent(new Event('condo-updated'));
|
const openEditUserModal = (user: User) => { setEditingUser(user); setUserForm({ name: user.name || '', email: user.email, password: '', phone: user.phone || '', role: user.role || 'user', familyId: user.familyId || '', receiveAlerts: user.receiveAlerts ?? true }); setShowUserModal(true); };
|
||||||
} catch (e) {
|
const handleUserSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { if (editingUser) { await CondoService.updateUser(editingUser.id, userForm); } else { await CondoService.createUser(userForm); } setUsers(await CondoService.getUsers(activeCondo?.id)); setShowUserModal(false); } catch (e) { alert("Errore nel salvataggio utente"); } };
|
||||||
console.error(e);
|
const handleDeleteUser = async (id: string) => { if(!window.confirm("Eliminare utente?")) return; await CondoService.deleteUser(id); setUsers(users.filter(u => u.id !== id)); };
|
||||||
alert("Errore nel salvataggio del condominio. Assicurati di essere amministratore.");
|
const openAddNoticeModal = () => { setEditingNotice(null); setNoticeTargetMode('all'); setNoticeForm({ title: '', content: '', type: 'info', link: '', condoId: activeCondo?.id || '', active: true, targetFamilyIds: [] }); setShowNoticeModal(true); };
|
||||||
}
|
const openEditNoticeModal = (n: Notice) => { setEditingNotice(n); const isTargeted = n.targetFamilyIds && n.targetFamilyIds.length > 0; setNoticeTargetMode(isTargeted ? 'specific' : 'all'); setNoticeForm({ title: n.title, content: n.content, type: n.type, link: n.link || '', condoId: n.condoId, active: n.active, targetFamilyIds: n.targetFamilyIds || [] }); setShowNoticeModal(true); };
|
||||||
};
|
const handleNoticeSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const payload: Notice = { id: editingNotice ? editingNotice.id : '', ...noticeForm, targetFamilyIds: noticeTargetMode === 'all' ? [] : noticeForm.targetFamilyIds, date: editingNotice ? editingNotice.date : new Date().toISOString() }; await CondoService.saveNotice(payload); setNotices(await CondoService.getNotices(activeCondo?.id)); setShowNoticeModal(false); } catch (e) { console.error(e); } };
|
||||||
|
const handleDeleteNotice = async (id: string) => { if(!window.confirm("Eliminare annuncio?")) return; await CondoService.deleteNotice(id); setNotices(notices.filter(n => n.id !== id)); };
|
||||||
const handleDeleteCondo = async (id: string) => {
|
const toggleNoticeActive = async (notice: Notice) => { try { const updated = { ...notice, active: !notice.active }; await CondoService.saveNotice(updated); setNotices(notices.map(n => n.id === notice.id ? updated : n)); } catch (e) { console.error(e); } };
|
||||||
if(!window.confirm("Eliminare questo condominio? Attenzione: operazione irreversibile.")) return;
|
const toggleNoticeFamilyTarget = (familyId: string) => { setNoticeForm(prev => { const current = prev.targetFamilyIds; if (current.includes(familyId)) { return { ...prev, targetFamilyIds: current.filter(id => id !== familyId) }; } else { return { ...prev, targetFamilyIds: [...current, familyId] }; } }); };
|
||||||
try {
|
const openReadDetails = (noticeId: string) => { setSelectedNoticeId(noticeId); setShowReadDetailsModal(true); };
|
||||||
await CondoService.deleteCondo(id);
|
const openAddAlertModal = () => { setEditingAlert(null); setAlertForm({ subject: '', body: '', daysOffset: 1, offsetType: 'before_next_month', sendHour: 9, active: true }); setShowAlertModal(true); };
|
||||||
setCondos(await CondoService.getCondos());
|
const openEditAlertModal = (alert: AlertDefinition) => { setEditingAlert(alert); setAlertForm(alert); setShowAlertModal(true); };
|
||||||
window.dispatchEvent(new Event('condo-updated'));
|
const handleAlertSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const payload: AlertDefinition = { id: editingAlert ? editingAlert.id : '', subject: alertForm.subject!, body: alertForm.body!, daysOffset: Number(alertForm.daysOffset), offsetType: alertForm.offsetType as any, sendHour: Number(alertForm.sendHour), active: alertForm.active! }; const saved = await CondoService.saveAlert(payload); setAlerts(editingAlert ? alerts.map(a => a.id === saved.id ? saved : a) : [...alerts, saved]); setShowAlertModal(false); } catch (e) { console.error(e); } };
|
||||||
} catch (e) {
|
const handleDeleteAlert = async (id: string) => { if(!window.confirm("Eliminare avviso?")) return; await CondoService.deleteAlert(id); setAlerts(alerts.filter(a => a.id !== id)); };
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openAddFamilyModal = () => {
|
|
||||||
setEditingFamily(null);
|
|
||||||
setFamilyForm({ name: '', unitNumber: '', stair: '', floor: '', notes: '', contactEmail: '', customMonthlyQuota: '' });
|
|
||||||
setShowFamilyModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openEditFamilyModal = (family: Family) => {
|
|
||||||
setEditingFamily(family);
|
|
||||||
setFamilyForm({ name: family.name, unitNumber: family.unitNumber, stair: family.stair || '', floor: family.floor || '', notes: family.notes || '', contactEmail: family.contactEmail || '', customMonthlyQuota: family.customMonthlyQuota ? family.customMonthlyQuota.toString() : '' });
|
|
||||||
setShowFamilyModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteFamily = async (id: string) => {
|
|
||||||
if (!window.confirm('Eliminare questa famiglia?')) return;
|
|
||||||
try {
|
|
||||||
await CondoService.deleteFamily(id);
|
|
||||||
setFamilies(families.filter(f => f.id !== id));
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFamilySubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
const quota = familyForm.customMonthlyQuota && familyForm.customMonthlyQuota.trim() !== '' ? parseFloat(familyForm.customMonthlyQuota) : undefined;
|
|
||||||
const payload: any = {
|
|
||||||
name: familyForm.name,
|
|
||||||
unitNumber: familyForm.unitNumber,
|
|
||||||
stair: familyForm.stair,
|
|
||||||
floor: familyForm.floor,
|
|
||||||
notes: familyForm.notes,
|
|
||||||
contactEmail: familyForm.contactEmail,
|
|
||||||
customMonthlyQuota: quota
|
|
||||||
};
|
|
||||||
if (editingFamily) {
|
|
||||||
const updatedFamily = { ...editingFamily, ...payload };
|
|
||||||
await CondoService.updateFamily(updatedFamily);
|
|
||||||
setFamilies(families.map(f => f.id === updatedFamily.id ? updatedFamily : f));
|
|
||||||
} else {
|
|
||||||
const newFamily = await CondoService.addFamily(payload);
|
|
||||||
setFamilies([...families, newFamily]);
|
|
||||||
}
|
|
||||||
setShowFamilyModal(false);
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e);
|
|
||||||
alert(`Errore: ${e.message || "Impossibile salvare la famiglia."}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openAddUserModal = () => {
|
|
||||||
setEditingUser(null);
|
|
||||||
setUserForm({ name: '', email: '', password: '', phone: '', role: 'user', familyId: '', receiveAlerts: true });
|
|
||||||
setShowUserModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openEditUserModal = (user: User) => {
|
|
||||||
setEditingUser(user);
|
|
||||||
setUserForm({ name: user.name || '', email: user.email, password: '', phone: user.phone || '', role: user.role || 'user', familyId: user.familyId || '', receiveAlerts: user.receiveAlerts ?? true });
|
|
||||||
setShowUserModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUserSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
if (editingUser) {
|
|
||||||
await CondoService.updateUser(editingUser.id, userForm);
|
|
||||||
} else {
|
|
||||||
await CondoService.createUser(userForm);
|
|
||||||
}
|
|
||||||
setUsers(await CondoService.getUsers(activeCondo?.id));
|
|
||||||
setShowUserModal(false);
|
|
||||||
} catch (e) {
|
|
||||||
alert("Errore nel salvataggio utente");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteUser = async (id: string) => {
|
|
||||||
if(!window.confirm("Eliminare utente?")) return;
|
|
||||||
await CondoService.deleteUser(id);
|
|
||||||
setUsers(users.filter(u => u.id !== id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const openAddNoticeModal = () => {
|
|
||||||
setEditingNotice(null);
|
|
||||||
setNoticeTargetMode('all');
|
|
||||||
setNoticeForm({ title: '', content: '', type: 'info', link: '', condoId: activeCondo?.id || '', active: true, targetFamilyIds: [] });
|
|
||||||
setShowNoticeModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openEditNoticeModal = (n: Notice) => {
|
|
||||||
setEditingNotice(n);
|
|
||||||
const isTargeted = n.targetFamilyIds && n.targetFamilyIds.length > 0;
|
|
||||||
setNoticeTargetMode(isTargeted ? 'specific' : 'all');
|
|
||||||
setNoticeForm({ title: n.title, content: n.content, type: n.type, link: n.link || '', condoId: n.condoId, active: n.active, targetFamilyIds: n.targetFamilyIds || [] });
|
|
||||||
setShowNoticeModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNoticeSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
const payload: Notice = {
|
|
||||||
id: editingNotice ? editingNotice.id : '',
|
|
||||||
...noticeForm,
|
|
||||||
targetFamilyIds: noticeTargetMode === 'all' ? [] : noticeForm.targetFamilyIds,
|
|
||||||
date: editingNotice ? editingNotice.date : new Date().toISOString()
|
|
||||||
};
|
|
||||||
await CondoService.saveNotice(payload);
|
|
||||||
setNotices(await CondoService.getNotices(activeCondo?.id));
|
|
||||||
setShowNoticeModal(false);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteNotice = async (id: string) => {
|
|
||||||
if(!window.confirm("Eliminare annuncio?")) return;
|
|
||||||
await CondoService.deleteNotice(id);
|
|
||||||
setNotices(notices.filter(n => n.id !== id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleNoticeActive = async (notice: Notice) => {
|
|
||||||
try {
|
|
||||||
const updated = { ...notice, active: !notice.active };
|
|
||||||
await CondoService.saveNotice(updated);
|
|
||||||
setNotices(notices.map(n => n.id === notice.id ? updated : n));
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleNoticeFamilyTarget = (familyId: string) => {
|
|
||||||
setNoticeForm(prev => {
|
|
||||||
const current = prev.targetFamilyIds;
|
|
||||||
if (current.includes(familyId)) {
|
|
||||||
return { ...prev, targetFamilyIds: current.filter(id => id !== familyId) };
|
|
||||||
} else {
|
|
||||||
return { ...prev, targetFamilyIds: [...current, familyId] };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const openReadDetails = (noticeId: string) => {
|
|
||||||
setSelectedNoticeId(noticeId);
|
|
||||||
setShowReadDetailsModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openAddAlertModal = () => {
|
|
||||||
setEditingAlert(null);
|
|
||||||
setAlertForm({ subject: '', body: '', daysOffset: 1, offsetType: 'before_next_month', sendHour: 9, active: true });
|
|
||||||
setShowAlertModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openEditAlertModal = (alert: AlertDefinition) => {
|
|
||||||
setEditingAlert(alert);
|
|
||||||
setAlertForm(alert);
|
|
||||||
setShowAlertModal(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAlertSubmit = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
try {
|
|
||||||
const payload: AlertDefinition = {
|
|
||||||
id: editingAlert ? editingAlert.id : '',
|
|
||||||
subject: alertForm.subject!,
|
|
||||||
body: alertForm.body!,
|
|
||||||
daysOffset: Number(alertForm.daysOffset),
|
|
||||||
offsetType: alertForm.offsetType as any,
|
|
||||||
sendHour: Number(alertForm.sendHour),
|
|
||||||
active: alertForm.active!
|
|
||||||
};
|
|
||||||
const saved = await CondoService.saveAlert(payload);
|
|
||||||
setAlerts(editingAlert ? alerts.map(a => a.id === saved.id ? saved : a) : [...alerts, saved]);
|
|
||||||
setShowAlertModal(false);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteAlert = async (id: string) => {
|
|
||||||
if(!window.confirm("Eliminare avviso?")) return;
|
|
||||||
await CondoService.deleteAlert(id);
|
|
||||||
setAlerts(alerts.filter(a => a.id !== id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCondoName = (id: string) => condos.find(c => c.id === id)?.name || 'Sconosciuto';
|
|
||||||
|
|
||||||
const toggleFeature = (key: keyof AppSettings['features']) => {
|
const toggleFeature = (key: keyof AppSettings['features']) => {
|
||||||
if (!globalSettings) return;
|
if (!globalSettings) return;
|
||||||
@@ -577,7 +280,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
if (isPrivileged) {
|
if (isPrivileged) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
{ id: 'general', label: 'Condominio', icon: <Building className="w-4 h-4"/> },
|
{ id: 'general', label: 'Condominio', icon: <Building className="w-4 h-4"/> },
|
||||||
{ id: 'storage', label: 'Cloud & Storage', icon: <HardDrive className="w-4 h-4"/> } // Moved up here
|
{ id: 'storage', label: 'Cloud & Storage', icon: <HardDrive className="w-4 h-4"/> }
|
||||||
);
|
);
|
||||||
if (globalSettings?.features.multiCondo) {
|
if (globalSettings?.features.multiCondo) {
|
||||||
tabs.push({ id: 'condos', label: 'Lista Condomini', icon: <List className="w-4 h-4"/> });
|
tabs.push({ id: 'condos', label: 'Lista Condomini', icon: <List className="w-4 h-4"/> });
|
||||||
@@ -597,32 +300,46 @@ export const SettingsPage: React.FC = () => {
|
|||||||
if (loading) return <div className="p-8 text-center text-slate-400">Caricamento...</div>;
|
if (loading) return <div className="p-8 text-center text-slate-400">Caricamento...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-5xl mx-auto space-y-6 pb-20">
|
<div className="max-w-6xl mx-auto pb-20">
|
||||||
{/* HEADER */}
|
<div className="mb-6">
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-bold text-slate-800">Impostazioni</h2>
|
<h2 className="text-2xl font-bold text-slate-800">Impostazioni</h2>
|
||||||
<p className="text-slate-500 text-sm md:text-base">{activeCondo ? `Gestione: ${activeCondo.name}` : 'Pannello di Controllo'}</p>
|
<p className="text-slate-500">{activeCondo ? `Gestione: ${activeCondo.name}` : 'Pannello di Controllo'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* TABS NAVIGATION */}
|
<div className="flex flex-col md:flex-row gap-6">
|
||||||
<div className="flex border-b border-slate-200 overflow-x-auto no-scrollbar pb-1 gap-1">
|
{/* NAVIGATION SIDEBAR / MOBILE SCROLL */}
|
||||||
|
<div className="md:w-64 flex-shrink-0">
|
||||||
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden sticky top-4">
|
||||||
|
<div className="p-4 border-b border-slate-100 md:block hidden">
|
||||||
|
<h3 className="font-bold text-slate-700 text-sm uppercase tracking-wider">Menu</h3>
|
||||||
|
</div>
|
||||||
|
{/* Mobile: Horizontal Scroll, Desktop: Vertical List */}
|
||||||
|
<div className="flex md:flex-col overflow-x-auto md:overflow-visible no-scrollbar p-2 md:p-2 gap-2">
|
||||||
{tabs.map(tab => (
|
{tabs.map(tab => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className={`
|
className={`
|
||||||
px-4 py-3 font-medium text-sm whitespace-nowrap flex items-center gap-2 rounded-t-lg transition-colors
|
flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-all whitespace-nowrap flex-shrink-0
|
||||||
${activeTab === tab.id ? 'text-blue-600 border-b-2 border-blue-600 bg-blue-50/50' : 'text-slate-500 hover:text-slate-700 hover:bg-slate-50'}
|
${activeTab === tab.id
|
||||||
|
? 'bg-blue-50 text-blue-700 shadow-sm'
|
||||||
|
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{tab.icon}{tab.label}
|
{tab.icon}
|
||||||
|
<span>{tab.label}</span>
|
||||||
|
{activeTab === tab.id && <ChevronRight className="w-4 h-4 ml-auto hidden md:block opacity-50"/>}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CONTENT AREA */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
{/* PROFILE TAB */}
|
{/* PROFILE TAB */}
|
||||||
{activeTab === 'profile' && (
|
{activeTab === 'profile' && (
|
||||||
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6 max-w-2xl">
|
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||||
<h3 className="text-lg font-bold text-slate-800 mb-6 flex items-center gap-2"><UserIcon className="w-5 h-5 text-blue-600" /> Il Tuo Profilo</h3>
|
<h3 className="text-lg font-bold text-slate-800 mb-6 flex items-center gap-2"><UserIcon className="w-5 h-5 text-blue-600" /> Il Tuo Profilo</h3>
|
||||||
<form onSubmit={handleProfileSubmit} className="space-y-5">
|
<form onSubmit={handleProfileSubmit} className="space-y-5">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||||
@@ -649,9 +366,9 @@ export const SettingsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* FEATURES TAB (ADMIN ONLY) */}
|
{/* FEATURES TAB */}
|
||||||
{isSuperAdmin && activeTab === 'features' && globalSettings && (
|
{isSuperAdmin && activeTab === 'features' && globalSettings && (
|
||||||
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6 max-w-3xl">
|
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2"><LayoutGrid className="w-5 h-5 text-blue-600" /> Funzionalità Piattaforma</h3>
|
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2"><LayoutGrid className="w-5 h-5 text-blue-600" /> Funzionalità Piattaforma</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -704,7 +421,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
|
|
||||||
{/* STORAGE CONFIG TAB */}
|
{/* STORAGE CONFIG TAB */}
|
||||||
{isPrivileged && activeTab === 'storage' && (
|
{isPrivileged && activeTab === 'storage' && (
|
||||||
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6 max-w-2xl">
|
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||||
<h3 className="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"><Cloud className="w-5 h-5 text-blue-600" /> Configurazione Storage</h3>
|
<h3 className="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"><Cloud className="w-5 h-5 text-blue-600" /> Configurazione Storage</h3>
|
||||||
<p className="text-sm text-slate-500 mb-6">Scegli dove salvare i documenti caricati.</p>
|
<p className="text-sm text-slate-500 mb-6">Scegli dove salvare i documenti caricati.</p>
|
||||||
|
|
||||||
@@ -773,7 +490,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
{isPrivileged && activeTab === 'general' && (
|
{isPrivileged && activeTab === 'general' && (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-6 animate-fade-in">
|
||||||
{!activeCondo ? <div className="bg-amber-50 border border-amber-200 text-amber-800 p-4 rounded-lg">Nessun condominio selezionato.</div> : (
|
{!activeCondo ? <div className="bg-amber-50 border border-amber-200 text-amber-800 p-4 rounded-lg">Nessun condominio selezionato.</div> : (
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8 max-w-2xl">
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||||
<h3 className="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"><Building className="w-5 h-5 text-blue-600" /> Dati Condominio Corrente</h3>
|
<h3 className="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2"><Building className="w-5 h-5 text-blue-600" /> Dati Condominio Corrente</h3>
|
||||||
<form onSubmit={handleGeneralSubmit} className="space-y-5">
|
<form onSubmit={handleGeneralSubmit} className="space-y-5">
|
||||||
<input type="text" value={activeCondo.name} onChange={(e) => setActiveCondo({ ...activeCondo, name: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Nome" required />
|
<input type="text" value={activeCondo.name} onChange={(e) => setActiveCondo({ ...activeCondo, name: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Nome" required />
|
||||||
@@ -798,7 +515,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{globalSettings && (<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8 max-w-2xl"><h3 className="font-bold text-slate-800 mb-2">Anno Fiscale</h3><p className="text-slate-600 mb-4">Corrente: <strong>{globalSettings.currentYear}</strong></p><button type="button" onClick={handleNewYear} className="bg-slate-800 text-white px-4 py-2 rounded-lg text-sm">Chiudi Anno {globalSettings.currentYear}</button></div>)}
|
{globalSettings && (<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8"><h3 className="font-bold text-slate-800 mb-2">Anno Fiscale</h3><p className="text-slate-600 mb-4">Corrente: <strong>{globalSettings.currentYear}</strong></p><button type="button" onClick={handleNewYear} className="bg-slate-800 text-white px-4 py-2 rounded-lg text-sm">Chiudi Anno {globalSettings.currentYear}</button></div>)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -806,7 +523,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
{isPrivileged && activeTab === 'condos' && (
|
{isPrivileged && activeTab === 'condos' && (
|
||||||
<div className="space-y-4 animate-fade-in">
|
<div className="space-y-4 animate-fade-in">
|
||||||
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100"><div><h3 className="font-bold text-blue-800">I Tuoi Condomini</h3></div><button onClick={openAddCondoModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex gap-2"><Plus className="w-4 h-4" /> Aggiungi</button></div>
|
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100"><div><h3 className="font-bold text-blue-800">I Tuoi Condomini</h3></div><button onClick={openAddCondoModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex gap-2"><Plus className="w-4 h-4" /> Aggiungi</button></div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{condos.map(condo => (
|
{condos.map(condo => (
|
||||||
<div key={condo.id} className={`bg-white p-5 rounded-xl border shadow-sm relative ${activeCondo?.id === condo.id ? 'border-blue-500 ring-1 ring-blue-500' : 'border-slate-200'}`}>
|
<div key={condo.id} className={`bg-white p-5 rounded-xl border shadow-sm relative ${activeCondo?.id === condo.id ? 'border-blue-500 ring-1 ring-blue-500' : 'border-slate-200'}`}>
|
||||||
{activeCondo?.id === condo.id && <div className="absolute top-3 right-3 text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded font-bold uppercase">Attivo</div>}
|
{activeCondo?.id === condo.id && <div className="absolute top-3 right-3 text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded font-bold uppercase">Attivo</div>}
|
||||||
@@ -923,6 +640,8 @@ export const SettingsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Condo Modal */}
|
{/* Condo Modal */}
|
||||||
{showCondoModal && (
|
{showCondoModal && (
|
||||||
|
|||||||
Reference in New Issue
Block a user