Update Settings.tsx
This commit is contained in:
@@ -1,25 +1,23 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { CondoService } from '../services/mockDb';
|
||||
import { AppSettings, Family, User, AlertDefinition, Condo, Notice, NoticeIconType, NoticeRead } from '../types';
|
||||
import { AppSettings, Family, User, AlertDefinition, Condo, Notice, NoticeIconType, NoticeRead, BrandingConfig, StorageConfig } from '../types';
|
||||
import {
|
||||
Save, Building, Coins, Plus, Pencil, Trash2, X, CalendarCheck,
|
||||
AlertTriangle, User as UserIcon, Server, Bell, Clock, FileText,
|
||||
Lock, Megaphone, CheckCircle2, Info, Hammer, Link as LinkIcon,
|
||||
Eye, Calendar, List, UserCog, Mail, Power, MapPin, CreditCard,
|
||||
ToggleLeft, ToggleRight, LayoutGrid, PieChart, Users, Send, Cloud, HardDrive, ChevronRight, Palette, Monitor,
|
||||
Sun, Moon, Check
|
||||
ToggleLeft, ToggleRight, LayoutGrid, PieChart, Users, Send, Cloud, HardDrive, ChevronRight, Palette, Image as ImageIcon, Upload
|
||||
} from 'lucide-react';
|
||||
|
||||
const PALETTES = [
|
||||
{ name: 'Blue (Standard)', color: '#2563eb' },
|
||||
{ name: 'Indigo', color: '#4f46e5' },
|
||||
{ name: 'Emerald', color: '#059669' },
|
||||
{ name: 'Rose', color: '#e11d48' },
|
||||
{ name: 'Amber', color: '#d97706' },
|
||||
{ name: 'Slate', color: '#475569' },
|
||||
{ name: 'Purple', color: '#9333ea' }
|
||||
];
|
||||
const COLOR_MAP: Record<string, string> = {
|
||||
blue: '#2563eb',
|
||||
purple: '#9333ea',
|
||||
green: '#16a34a',
|
||||
red: '#dc2626',
|
||||
orange: '#ea580c',
|
||||
slate: '#475569'
|
||||
};
|
||||
|
||||
export const SettingsPage: React.FC = () => {
|
||||
const currentUser = CondoService.getCurrentUser();
|
||||
@@ -31,75 +29,80 @@ export const SettingsPage: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<TabType>(isPrivileged ? 'general' : 'profile');
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Profile State
|
||||
// Profile Form
|
||||
const [profileForm, setProfileForm] = useState({
|
||||
name: currentUser?.name || '',
|
||||
phone: currentUser?.phone || '',
|
||||
password: '',
|
||||
receiveAlerts: currentUser?.receiveAlerts ?? true,
|
||||
theme: localStorage.getItem('app-theme') || 'light'
|
||||
receiveAlerts: currentUser?.receiveAlerts ?? true
|
||||
});
|
||||
const [profileSaving, setProfileSaving] = useState(false);
|
||||
const [profileMsg, setProfileMsg] = useState('');
|
||||
|
||||
// Branding State
|
||||
const [brandingForm, setBrandingForm] = useState({
|
||||
appName: '',
|
||||
appIcon: '',
|
||||
loginBg: '',
|
||||
primaryColor: '#2563eb'
|
||||
});
|
||||
|
||||
// Branding & Features
|
||||
const [activeCondo, setActiveCondo] = useState<Condo | undefined>(undefined);
|
||||
const [globalSettings, setGlobalSettings] = useState<AppSettings | null>(null);
|
||||
const [brandingForm, setBrandingForm] = useState<BrandingConfig>({ appName: 'CondoPay', primaryColor: 'blue', logoUrl: '', loginBackgroundUrl: '' });
|
||||
|
||||
// Condos List
|
||||
const [condos, setCondos] = useState<Condo[]>([]);
|
||||
const [showCondoModal, setShowCondoModal] = useState(false);
|
||||
const [editingCondo, setEditingCondo] = useState<Condo | null>(null);
|
||||
const [condoForm, setCondoForm] = useState({ name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', paypalClientId: '', defaultMonthlyQuota: 100, dueDay: 10 });
|
||||
const [condoForm, setCondoForm] = useState<Partial<Condo>>({
|
||||
name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', paypalClientId: '', defaultMonthlyQuota: 100, dueDay: 10
|
||||
});
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [successMsg, setSuccessMsg] = useState('');
|
||||
|
||||
// Families Management
|
||||
const [families, setFamilies] = useState<Family[]>([]);
|
||||
const [showFamilyModal, setShowFamilyModal] = useState(false);
|
||||
const [editingFamily, setEditingFamily] = useState<Family | null>(null);
|
||||
const [familyForm, setFamilyForm] = useState({ name: '', unitNumber: '', stair: '', floor: '', notes: '', contactEmail: '', customMonthlyQuota: '' });
|
||||
|
||||
// Users Management
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [showUserModal, setShowUserModal] = useState(false);
|
||||
const [editingUser, setEditingUser] = useState<User | null>(null);
|
||||
const [userForm, setUserForm] = useState({ name: '', email: '', password: '', phone: '', role: 'user', familyId: '', receiveAlerts: true });
|
||||
const [userForm, setUserForm] = useState({ name: '', email: '', password: '', phone: '', role: 'user' as User['role'], familyId: '', receiveAlerts: true });
|
||||
|
||||
// Alerts & Email Rules
|
||||
const [alerts, setAlerts] = useState<AlertDefinition[]>([]);
|
||||
const [showAlertModal, setShowAlertModal] = useState(false);
|
||||
const [editingAlert, setEditingAlert] = useState<AlertDefinition | null>(null);
|
||||
const [alertForm, setAlertForm] = useState<Partial<AlertDefinition>>({ subject: '', body: '', daysOffset: 1, offsetType: 'before_next_month', sendHour: 9, active: true });
|
||||
|
||||
const [showSmtpModal, setShowSmtpModal] = useState(false);
|
||||
const [testingSmtp, setTestingSmtp] = useState(false);
|
||||
const [testSmtpMsg, setTestSmtpMsg] = useState('');
|
||||
|
||||
// Notices (Bacheca)
|
||||
const [notices, setNotices] = useState<Notice[]>([]);
|
||||
const [showNoticeModal, setShowNoticeModal] = useState(false);
|
||||
const [editingNotice, setEditingNotice] = useState<Notice | null>(null);
|
||||
const [noticeTargetMode, setNoticeTargetMode] = useState<'all' | 'specific'>('all');
|
||||
const [noticeForm, setNoticeForm] = useState<{ title: string; content: string; type: 'info' | 'warning' | 'maintenance' | 'event'; link: string; condoId: string; active: boolean; targetFamilyIds: string[]; }>({ title: '', content: '', type: 'info', link: '', condoId: '', active: true, targetFamilyIds: [] });
|
||||
const [noticeForm, setNoticeForm] = useState({ title: '', content: '', type: 'info' as NoticeIconType, link: '', condoId: '', active: true, targetFamilyIds: [] as string[] });
|
||||
const [noticeReadStats, setNoticeReadStats] = useState<Record<string, NoticeRead[]>>({});
|
||||
const [showReadDetailsModal, setShowReadDetailsModal] = useState(false);
|
||||
const [selectedNoticeId, setSelectedNoticeId] = useState<string | null>(null);
|
||||
|
||||
// Cloud Storage Settings
|
||||
const [storageForm, setStorageForm] = useState<StorageConfig>({ provider: 'local_db', apiKey: '', apiSecret: '', bucket: '', region: '' });
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const activeC = await CondoService.getActiveCondo();
|
||||
setActiveCondo(activeC);
|
||||
const gSettings = await CondoService.getSettings();
|
||||
|
||||
if (isPrivileged) {
|
||||
const condoList = await CondoService.getCondos();
|
||||
const gSettings = await CondoService.getSettings();
|
||||
setCondos(condoList);
|
||||
setGlobalSettings(gSettings);
|
||||
setBrandingForm({
|
||||
appName: gSettings.branding?.appName || 'CondoPay',
|
||||
appIcon: gSettings.branding?.appIcon || '',
|
||||
loginBg: gSettings.branding?.loginBg || '',
|
||||
primaryColor: gSettings.branding?.primaryColor || '#2563eb'
|
||||
});
|
||||
setActiveCondo(activeC);
|
||||
setGlobalSettings(gSettings);
|
||||
setBrandingForm(gSettings.branding || { appName: 'CondoPay', primaryColor: 'blue' });
|
||||
setStorageForm(gSettings.storageConfig || { provider: 'local_db', apiKey: '', apiSecret: '', bucket: '', region: '' });
|
||||
|
||||
if (isPrivileged) {
|
||||
setCondos(await CondoService.getCondos());
|
||||
if (activeC) {
|
||||
setFamilies(await CondoService.getFamilies(activeC.id));
|
||||
setUsers(await CondoService.getUsers(activeC.id));
|
||||
@@ -113,211 +116,520 @@ export const SettingsPage: React.FC = () => {
|
||||
setNoticeReadStats(stats);
|
||||
}
|
||||
}
|
||||
} catch(e) { console.error(e); } finally { setLoading(false); }
|
||||
} catch(e) { console.error("Error loading settings data", e); } finally { setLoading(false); }
|
||||
};
|
||||
fetchData();
|
||||
}, [isPrivileged]);
|
||||
|
||||
// --- SUBMIT HANDLERS ---
|
||||
const handleProfileSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault(); setProfileSaving(true); setProfileMsg('');
|
||||
try {
|
||||
await CondoService.updateProfile(profileForm);
|
||||
// Apply theme
|
||||
localStorage.setItem('app-theme', profileForm.theme);
|
||||
if (profileForm.theme === 'dark') document.documentElement.classList.add('dark');
|
||||
else document.documentElement.classList.remove('dark');
|
||||
|
||||
setProfileMsg('Profilo aggiornato!');
|
||||
setTimeout(() => setProfileMsg(''), 3000);
|
||||
} catch (e) { setProfileMsg('Errore'); } finally { setProfileSaving(false); }
|
||||
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) => {
|
||||
e.preventDefault(); if (!activeCondo) return; setSaving(true); setSuccessMsg('');
|
||||
try { await CondoService.saveCondo(activeCondo); setSuccessMsg('Dati condominio aggiornati!'); setTimeout(() => setSuccessMsg(''), 3000); setCondos(await CondoService.getCondos()); } catch (e) { console.error(e); } finally { setSaving(false); }
|
||||
};
|
||||
const handleFeaturesSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault(); if (!globalSettings) return; setSaving(true);
|
||||
try { await CondoService.updateSettings(globalSettings); setSuccessMsg('Configurazione salvata!'); setTimeout(() => { setSuccessMsg(''); window.location.reload(); }, 1500); } catch (e) { console.error(e); } finally { setSaving(false); }
|
||||
};
|
||||
|
||||
const handleBrandingSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!globalSettings) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
e.preventDefault(); if (!globalSettings) return; setSaving(true);
|
||||
try {
|
||||
const updated = { ...globalSettings, branding: brandingForm };
|
||||
await CondoService.updateSettings(updated);
|
||||
setGlobalSettings(updated);
|
||||
setSuccessMsg('Branding aggiornato!');
|
||||
window.dispatchEvent(new Event('branding-updated'));
|
||||
// Apply color immediately to UI
|
||||
document.documentElement.style.setProperty('--primary-color', brandingForm.primaryColor);
|
||||
setTimeout(() => setSuccessMsg(''), 3000);
|
||||
setSuccessMsg('Branding salvato!'); setTimeout(() => setSuccessMsg(''), 3000);
|
||||
} catch(e) { console.error(e); } finally { setSaving(false); }
|
||||
};
|
||||
const handleStorageSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault(); if (!globalSettings) return; setSaving(true);
|
||||
try { const updated = { ...globalSettings, storageConfig: storageForm }; await CondoService.updateSettings(updated); setGlobalSettings(updated); setSuccessMsg('Configurazione Storage salvata!'); setTimeout(() => setSuccessMsg(''), 3000); } catch (e) { console.error(e); } finally { setSaving(false); }
|
||||
};
|
||||
const handleLogoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) { const reader = new FileReader(); reader.onload = (ev) => setBrandingForm(prev => ({...prev, logoUrl: ev.target?.result as string})); reader.readAsDataURL(file); }
|
||||
};
|
||||
const handleBgUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) { const reader = new FileReader(); reader.onload = (ev) => setBrandingForm(prev => ({...prev, loginBackgroundUrl: ev.target?.result as string})); reader.readAsDataURL(file); }
|
||||
};
|
||||
|
||||
// --- CRUD Handlers (Simplified for brevity as logic is unchanged) ---
|
||||
const handleGeneralSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!activeCondo) return; setSaving(true); try { await CondoService.saveCondo(activeCondo); setSuccessMsg('Aggiornato!'); setTimeout(() => setSuccessMsg(''), 3000); } catch (e) {} finally { setSaving(false); } };
|
||||
const handleFeaturesSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!globalSettings) return; setSaving(true); try { await CondoService.updateSettings(globalSettings); setSuccessMsg('Salvato!'); window.location.reload(); } catch (e) {} finally { setSaving(false); } };
|
||||
const handleSmtpSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!globalSettings) return; setSaving(true); try { await CondoService.updateSettings(globalSettings); setShowSmtpModal(false); } catch (e) {} finally { setSaving(false); } };
|
||||
const handleStorageSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!globalSettings) return; setSaving(true); try { await CondoService.updateSettings(globalSettings); setSuccessMsg('Salvato!'); } catch (e) {} finally { setSaving(false); } };
|
||||
const handleCondoSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await CondoService.saveCondo({ id: editingCondo?.id || '', ...condoForm }); setCondos(await CondoService.getCondos()); setShowCondoModal(false); } catch (e) {} };
|
||||
const handleDeleteCondo = async (id: string) => { if(!confirm("Eliminare?")) return; await CondoService.deleteCondo(id); setCondos(await CondoService.getCondos()); };
|
||||
const handleFamilySubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const quota = familyForm.customMonthlyQuota ? parseFloat(familyForm.customMonthlyQuota) : undefined; if (editingFamily) { await CondoService.updateFamily({ ...editingFamily, ...familyForm, customMonthlyQuota: quota }); } else { await CondoService.addFamily({ ...familyForm, customMonthlyQuota: quota }); } setFamilies(await CondoService.getFamilies(activeCondo?.id)); setShowFamilyModal(false); } catch (e) {} };
|
||||
const handleDeleteFamily = async (id: string) => { if(!confirm("Eliminare?")) return; await CondoService.deleteFamily(id); setFamilies(families.filter(f => f.id !== id)); };
|
||||
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) {} };
|
||||
const handleDeleteUser = async (id: string) => { if(!confirm("Eliminare?")) return; await CondoService.deleteUser(id); setUsers(users.filter(u => u.id !== id)); };
|
||||
const handleNoticeSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const now = new Date(); const sqlDate = now.toISOString().slice(0, 19).replace('T', ' '); await CondoService.saveNotice({ id: editingNotice?.id || '', ...noticeForm, targetFamilyIds: noticeTargetMode === 'all' ? [] : noticeForm.targetFamilyIds, date: editingNotice ? editingNotice.date : sqlDate }); setNotices(await CondoService.getNotices(activeCondo?.id)); setShowNoticeModal(false); } catch (e) {} };
|
||||
const handleDeleteNotice = async (id: string) => { if(!confirm("Eliminare?")) return; await CondoService.deleteNotice(id); setNotices(notices.filter(n => n.id !== id)); };
|
||||
const handleAlertSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await CondoService.saveAlert({ id: editingAlert?.id || '', ...alertForm } as any); setAlerts(await CondoService.getAlerts(activeCondo?.id)); setShowAlertModal(false); } catch (e) {} };
|
||||
const handleDeleteAlert = async (id: string) => { if(!confirm("Eliminare?")) return; await CondoService.deleteAlert(id); setAlerts(alerts.filter(a => a.id !== id)); };
|
||||
// --- CRUD HELPERS ---
|
||||
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(c); setShowCondoModal(true); };
|
||||
const handleCondoSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await CondoService.saveCondo({ id: editingCondo?.id || '', ...condoForm } as Condo); setCondos(await CondoService.getCondos()); setShowCondoModal(false); window.dispatchEvent(new Event('condo-updated')); } catch (e) { alert("Errore"); } };
|
||||
const handleDeleteCondo = async (id: string) => { if(window.confirm("Eliminare definitivamente il condominio?")) { await CondoService.deleteCondo(id); setCondos(await CondoService.getCondos()); } };
|
||||
|
||||
const openAddFamilyModal = () => { setEditingFamily(null); setFamilyForm({ name: '', unitNumber: '', stair: '', floor: '', notes: '', contactEmail: '', customMonthlyQuota: '' }); setShowFamilyModal(true); };
|
||||
const openEditFamilyModal = (f: Family) => { setEditingFamily(f); setFamilyForm({ name: f.name, unitNumber: f.unitNumber, stair: f.stair || '', floor: f.floor || '', notes: f.notes || '', contactEmail: f.contactEmail || '', customMonthlyQuota: f.customMonthlyQuota?.toString() || '' }); setShowFamilyModal(true); };
|
||||
const handleFamilySubmit = async (e: React.FormEvent) => { e.preventDefault(); try { if (editingFamily) { await CondoService.updateFamily({ ...editingFamily, ...familyForm, customMonthlyQuota: familyForm.customMonthlyQuota ? parseFloat(familyForm.customMonthlyQuota) : undefined }); } else { await CondoService.addFamily({ ...familyForm, customMonthlyQuota: familyForm.customMonthlyQuota ? parseFloat(familyForm.customMonthlyQuota) : undefined }); } setFamilies(await CondoService.getFamilies(activeCondo?.id)); setShowFamilyModal(false); } catch (e) { alert("Errore"); } };
|
||||
const handleDeleteFamily = async (id: string) => { if (window.confirm("Eliminare la famiglia?")) { await CondoService.deleteFamily(id); setFamilies(await CondoService.getFamilies(activeCondo?.id)); } };
|
||||
|
||||
const openAddUserModal = () => { setEditingUser(null); setUserForm({ name: '', email: '', password: '', phone: '', role: 'user', familyId: '', receiveAlerts: true }); setShowUserModal(true); };
|
||||
const openEditUserModal = (u: User) => { setEditingUser(u); setUserForm({ name: u.name || '', email: u.email, password: '', phone: u.phone || '', role: u.role || 'user', familyId: u.familyId || '', receiveAlerts: u.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 salvataggio utente"); } };
|
||||
const handleDeleteUser = async (id: string) => { if(window.confirm("Eliminare questo account utente?")) { await CondoService.deleteUser(id); setUsers(await CondoService.getUsers(activeCondo?.id)); } };
|
||||
|
||||
const openAddNoticeModal = () => { setEditingNotice(null); setNoticeForm({ title: '', content: '', type: 'info', link: '', condoId: activeCondo?.id || '', active: true, targetFamilyIds: [] }); setNoticeTargetMode('all'); setShowNoticeModal(true); };
|
||||
const openEditNoticeModal = (n: Notice) => { setEditingNotice(n); setNoticeForm({ title: n.title, content: n.content, type: n.type, link: n.link || '', condoId: n.condoId, active: n.active, targetFamilyIds: n.targetFamilyIds || [] }); setNoticeTargetMode(n.targetFamilyIds && n.targetFamilyIds.length > 0 ? 'specific' : 'all'); setShowNoticeModal(true); };
|
||||
const handleNoticeSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const payload = { ...noticeForm, targetFamilyIds: noticeTargetMode === 'all' ? [] : noticeForm.targetFamilyIds }; if (editingNotice) { await CondoService.saveNotice({ ...editingNotice, ...payload }); } else { await CondoService.saveNotice(payload as any); } setNotices(await CondoService.getNotices(activeCondo?.id)); setShowNoticeModal(false); } catch (e) { alert("Errore"); } };
|
||||
const handleDeleteNotice = async (id: string) => { if(confirm('Eliminare l\'avviso?')) { await CondoService.deleteNotice(id); setNotices(await CondoService.getNotices(activeCondo?.id)); } };
|
||||
|
||||
const openAddAlertModal = () => { setEditingAlert(null); setAlertForm({ subject: '', body: '', daysOffset: 1, offsetType: 'before_next_month', sendHour: 9, active: true }); setShowAlertModal(true); };
|
||||
const openEditAlertModal = (a: AlertDefinition) => { setEditingAlert(a); setAlertForm(a); setShowAlertModal(true); };
|
||||
const handleAlertSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { await CondoService.saveAlert({ ...editingAlert, ...alertForm } as AlertDefinition); setAlerts(await CondoService.getAlerts(activeCondo?.id)); setShowAlertModal(false); } catch(e) { alert("Errore"); } };
|
||||
const handleDeleteAlert = async (id: string) => { if(window.confirm("Eliminare la regola di avviso?")) { await CondoService.deleteAlert(id); setAlerts(await CondoService.getAlerts(activeCondo?.id)); } };
|
||||
|
||||
const handleTestSmtp = async (e: React.FormEvent) => {
|
||||
e.preventDefault(); if (!globalSettings?.smtpConfig) return; setTestingSmtp(true); setTestSmtpMsg('');
|
||||
try { await CondoService.testSmtpConfig(globalSettings.smtpConfig); setTestSmtpMsg('Invio riuscito!'); } catch (e: any) { setTestSmtpMsg('Errore: ' + e.message); } finally { setTestingSmtp(false); }
|
||||
};
|
||||
|
||||
const toggleFeature = (key: keyof AppSettings['features']) => {
|
||||
if (!globalSettings) return;
|
||||
setGlobalSettings({ ...globalSettings, features: { ...globalSettings.features, [key]: !globalSettings.features[key] } });
|
||||
};
|
||||
|
||||
// --- RENDER HELPERS ---
|
||||
const tabs: {id: TabType, label: string, icon: React.ReactNode}[] = [
|
||||
{ id: 'profile', label: 'Profilo & Tema', icon: <UserIcon className="w-4 h-4"/> },
|
||||
{ id: 'profile', label: 'Il Tuo Profilo', icon: <UserIcon className="w-4 h-4"/> },
|
||||
];
|
||||
if (isSuperAdmin) {
|
||||
tabs.push({ id: 'features', label: 'Funzionalità', icon: <LayoutGrid className="w-4 h-4"/> });
|
||||
tabs.push({ id: 'branding', label: 'Personalizzazione', icon: <Palette className="w-4 h-4"/> });
|
||||
tabs.push(
|
||||
{ id: 'features', label: 'Funzionalità', icon: <LayoutGrid className="w-4 h-4"/> },
|
||||
{ id: 'branding', label: 'Personalizzazione', icon: <Palette className="w-4 h-4"/> }
|
||||
);
|
||||
}
|
||||
if (isPrivileged) {
|
||||
tabs.push({ id: 'general', label: 'Condominio', icon: <Building className="w-4 h-4"/> });
|
||||
if (globalSettings?.features.documents) tabs.push({ id: 'storage', label: 'Storage', icon: <HardDrive className="w-4 h-4"/> });
|
||||
if (globalSettings?.features.multiCondo) tabs.push({ id: 'condos', label: 'Condomini', icon: <List className="w-4 h-4"/> });
|
||||
tabs.push({ id: 'families', label: 'Famiglie', icon: <Coins className="w-4 h-4"/> }, { id: 'users', label: 'Utenti', icon: <UserCog className="w-4 h-4"/> });
|
||||
if (globalSettings?.features.notices) tabs.push({ id: 'notices', label: 'Bacheca', icon: <Megaphone className="w-4 h-4"/> });
|
||||
tabs.push({ id: 'alerts', label: 'Avvisi', icon: <Bell className="w-4 h-4"/> });
|
||||
if (globalSettings?.features.documents) tabs.push({ id: 'storage', label: 'Cloud Storage', icon: <HardDrive className="w-4 h-4"/> });
|
||||
if (globalSettings?.features.multiCondo) tabs.push({ id: 'condos', label: 'Lista Condomini', icon: <List className="w-4 h-4"/> });
|
||||
tabs.push(
|
||||
{ id: 'families', label: 'Famiglie', icon: <Coins className="w-4 h-4"/> },
|
||||
{ id: 'users', label: 'Account Utenti', icon: <UserCog className="w-4 h-4"/> }
|
||||
);
|
||||
if (globalSettings?.features.notices) tabs.push({ id: 'notices', label: 'Bacheca Avvisi', icon: <Megaphone className="w-4 h-4"/> });
|
||||
tabs.push({ id: 'alerts', label: 'Scadenze & Email', icon: <Bell className="w-4 h-4"/> });
|
||||
}
|
||||
|
||||
if (loading) return <div className="p-8 text-center text-slate-400">Caricamento...</div>;
|
||||
// Componente per lo stato vuoto quando non c'è un condominio attivo
|
||||
const NoCondoState = () => (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-2xl p-8 text-center">
|
||||
<Building className="w-12 h-12 text-amber-400 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-bold text-amber-800 mb-2">Nessun Condominio Selezionato</h3>
|
||||
<p className="text-amber-700 mb-6">Per accedere a questa sezione devi prima creare o selezionare un condominio.</p>
|
||||
<button onClick={openAddCondoModal} className="bg-amber-600 text-white px-6 py-3 rounded-xl font-bold hover:bg-amber-700 inline-flex items-center gap-2">
|
||||
<Plus className="w-5 h-5"/> Crea il tuo primo Condominio
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (loading) return <div className="p-12 text-center text-slate-400">Inizializzazione impostazioni...</div>;
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto pb-20">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-2xl font-bold text-slate-800 dark:text-slate-100">Impostazioni</h2>
|
||||
<p className="text-slate-500 dark:text-slate-400">{activeCondo ? `Gestione: ${activeCondo.name}` : 'Pannello di Controllo'}</p>
|
||||
<div className="max-w-6xl mx-auto pb-32 animate-fade-in">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-3xl font-bold text-slate-900">Pannello di Controllo</h2>
|
||||
<p className="text-slate-500 mt-1">{activeCondo ? `Configurazione per: ${activeCondo.name}` : 'Benvenuto! Inizia configurando il tuo stabile.'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-6">
|
||||
<div className="md:w-64 flex-shrink-0">
|
||||
<div className="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden sticky top-4">
|
||||
<div className="flex md:flex-col overflow-x-auto no-scrollbar p-2 gap-2">
|
||||
<div className="flex flex-col md:flex-row gap-8">
|
||||
{/* Sidebar Nav */}
|
||||
<div className="md:w-72 flex-shrink-0">
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden sticky top-4">
|
||||
<div className="flex md:flex-col overflow-x-auto no-scrollbar p-3 gap-1.5">
|
||||
{tabs.map(tab => (
|
||||
<button key={tab.id} onClick={() => setActiveTab(tab.id)} className={`flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${activeTab === tab.id ? 'bg-primary/10 text-primary shadow-sm' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-700'}`}>
|
||||
{tab.icon} <span>{tab.label}</span>
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all whitespace-nowrap ${activeTab === tab.id ? 'bg-blue-600 text-white shadow-lg shadow-blue-200' : 'text-slate-600 hover:bg-slate-50'}`}
|
||||
>
|
||||
{tab.icon}
|
||||
<span>{tab.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content Area */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{activeTab === 'profile' && (
|
||||
<div className="animate-fade-in bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
||||
<h3 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-6 flex items-center gap-2"><UserIcon className="w-5 h-5 text-primary" /> Profilo & Preferenze</h3>
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex items-center gap-3 mb-8"><div className="p-3 bg-blue-50 rounded-2xl text-blue-600"><UserIcon className="w-6 h-6" /></div><h3 className="text-xl font-bold text-slate-800">Il Tuo Profilo</h3></div>
|
||||
<form onSubmit={handleProfileSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div><label className="text-sm font-medium text-slate-700 dark:text-slate-300">Nome</label><input type="text" value={profileForm.name} onChange={e => setProfileForm({...profileForm, name: e.target.value})} className="w-full border p-2.5 rounded-lg text-slate-700 dark:bg-slate-700 dark:text-slate-100"/></div>
|
||||
<div><label className="text-sm font-medium text-slate-700 dark:text-slate-300">Tema App</label>
|
||||
<div className="flex gap-2 mt-1">
|
||||
<button type="button" onClick={() => setProfileForm({...profileForm, theme: 'light'})} className={`flex-1 py-2 px-3 rounded-lg border flex items-center justify-center gap-2 transition-all ${profileForm.theme === 'light' ? 'bg-white border-primary text-primary ring-1 ring-primary' : 'bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600 text-slate-500'}`}><Sun className="w-4 h-4"/> Chiaro</button>
|
||||
<button type="button" onClick={() => setProfileForm({...profileForm, theme: 'dark'})} className={`flex-1 py-2 px-3 rounded-lg border flex items-center justify-center gap-2 transition-all ${profileForm.theme === 'dark' ? 'bg-slate-800 border-primary text-primary ring-1 ring-primary' : 'bg-slate-50 dark:bg-slate-700 border-slate-200 dark:border-slate-600 text-slate-500'}`}><Moon className="w-4 h-4"/> Scuro</button>
|
||||
</div>
|
||||
</div>
|
||||
<div><label className="text-sm font-medium text-slate-700 dark:text-slate-300">Telefono</label><input type="tel" value={profileForm.phone} onChange={e => setProfileForm({...profileForm, phone: e.target.value})} className="w-full border p-2.5 rounded-lg text-slate-700 dark:bg-slate-700 dark:text-slate-100"/></div>
|
||||
<div><label className="text-sm font-medium text-slate-700 dark:text-slate-300">Password</label><input type="password" placeholder="Nuova password" value={profileForm.password} onChange={e => setProfileForm({...profileForm, password: e.target.value})} className="w-full border p-2.5 rounded-lg text-slate-700 dark:bg-slate-700 dark:text-slate-100"/></div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Nome Completo</label><input type="text" value={profileForm.name} onChange={(e) => setProfileForm({...profileForm, name: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none" required /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Email</label><input type="email" value={currentUser?.email || ''} disabled className="w-full border border-slate-100 bg-slate-50 p-3 rounded-xl text-slate-400 cursor-not-allowed" /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Telefono</label><input type="tel" value={profileForm.phone} onChange={(e) => setProfileForm({...profileForm, phone: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none" /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Password</label><input type="password" placeholder="Opzionale" value={profileForm.password} onChange={(e) => setProfileForm({...profileForm, password: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none" /></div>
|
||||
</div>
|
||||
<button type="submit" disabled={profileSaving} className="bg-primary text-white px-6 py-2.5 rounded-lg font-bold hover:opacity-90 flex gap-2"><Save className="w-4 h-4" /> Aggiorna Impostazioni</button>
|
||||
{profileMsg && <p className="text-sm text-green-600 font-medium">{profileMsg}</p>}
|
||||
<div className="pt-2 border-t flex justify-between items-center"><button type="submit" disabled={profileSaving} className="bg-blue-600 text-white px-8 py-3 rounded-xl font-bold flex items-center gap-2 hover:bg-blue-700 shadow-lg shadow-blue-200 disabled:opacity-50"><Save className="w-4 h-4" /> {profileSaving ? '...' : 'Salva'}</button>{profileMsg && <p className="text-sm font-bold text-green-600">{profileMsg}</p>}</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'branding' && isSuperAdmin && (
|
||||
<div className="animate-fade-in bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
||||
<h3 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-6 flex items-center gap-2"><Palette className="w-5 h-5 text-primary" /> Personalizzazione Piattaforma</h3>
|
||||
<form onSubmit={handleBrandingSubmit} className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-bold text-slate-700 dark:text-slate-300 mb-1 block">Nome Applicazione</label>
|
||||
<input type="text" className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={brandingForm.appName} onChange={e => setBrandingForm({...brandingForm, appName: e.target.value})} placeholder="Es: MyCondo Manager" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-bold text-slate-700 dark:text-slate-300 mb-1 block">Icona App (Link URL Pubblico)</label>
|
||||
<input type="url" className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={brandingForm.appIcon} onChange={e => setBrandingForm({...brandingForm, appIcon: e.target.value})} placeholder="https://image.com/logo.png" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-bold text-slate-700 dark:text-slate-300 mb-1 block">Sfondo Pagina Login (Link URL Pubblico)</label>
|
||||
<input type="url" className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={brandingForm.loginBg} onChange={e => setBrandingForm({...brandingForm, loginBg: e.target.value})} placeholder="https://image.com/bg.jpg" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-bold text-slate-700 dark:text-slate-300 mb-3 block">Colore Primario / Palette</label>
|
||||
<div className="grid grid-cols-4 sm:grid-cols-7 gap-3">
|
||||
{PALETTES.map(p => (
|
||||
<button
|
||||
key={p.color}
|
||||
type="button"
|
||||
onClick={() => setBrandingForm({...brandingForm, primaryColor: p.color})}
|
||||
className={`h-12 rounded-lg border-2 flex items-center justify-center transition-all ${brandingForm.primaryColor === p.color ? 'border-primary ring-2 ring-primary/20 scale-105' : 'border-transparent'}`}
|
||||
style={{ backgroundColor: p.color }}
|
||||
title={p.name}
|
||||
>
|
||||
{brandingForm.primaryColor === p.color && <Check className="w-6 h-6 text-white"/>}
|
||||
</button>
|
||||
))}
|
||||
<div className="col-span-full flex items-center gap-3 mt-2">
|
||||
<input type="color" value={brandingForm.primaryColor} onChange={e => setBrandingForm({...brandingForm, primaryColor: e.target.value})} className="h-10 w-10 border-none bg-transparent cursor-pointer" />
|
||||
<span className="text-sm text-slate-500 font-medium">Scegli un colore personalizzato ({brandingForm.primaryColor})</span>
|
||||
</div>
|
||||
</div>
|
||||
{isSuperAdmin && activeTab === 'branding' && globalSettings && (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex items-center gap-3 mb-8"><div className="p-3 bg-purple-50 rounded-2xl text-purple-600"><Palette className="w-6 h-6" /></div><h3 className="text-xl font-bold text-slate-800">Branding</h3></div>
|
||||
<form onSubmit={handleBrandingSubmit} className="space-y-8">
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Nome App</label><input className="w-full border border-slate-200 p-3 rounded-xl font-bold text-slate-800" value={brandingForm.appName} onChange={e => setBrandingForm({...brandingForm, appName: e.target.value})}/></div>
|
||||
<div>
|
||||
<label className="text-xs font-bold text-slate-500 uppercase mb-4 block">Colore Primario</label>
|
||||
<div className="flex gap-4 flex-wrap items-center bg-slate-50 p-6 rounded-2xl border border-slate-100">
|
||||
{Object.keys(COLOR_MAP).map(colorKey => (
|
||||
<button key={colorKey} type="button" onClick={() => setBrandingForm({...brandingForm, primaryColor: colorKey})} className={`w-12 h-12 rounded-2xl border-4 transition-all hover:scale-110 flex items-center justify-center shadow-sm ${brandingForm.primaryColor === colorKey ? 'border-white ring-4 ring-slate-900' : 'border-transparent'}`} style={{ backgroundColor: COLOR_MAP[colorKey] }}>{brandingForm.primaryColor === colorKey && <CheckCircle2 className="w-6 h-6 text-white"/>}</button>
|
||||
))}
|
||||
<div className="h-10 w-px bg-slate-200 mx-2" />
|
||||
<div className="flex items-center gap-2"><input type="color" value={brandingForm.primaryColor.startsWith('#') ? brandingForm.primaryColor : '#2563eb'} onChange={e => setBrandingForm({...brandingForm, primaryColor: e.target.value})} className="w-12 h-12 rounded-2xl border-2 cursor-pointer p-0 bg-transparent"/><input type="text" value={brandingForm.primaryColor} onChange={e => setBrandingForm({...brandingForm, primaryColor: e.target.value})} className="w-28 text-sm font-mono border border-slate-200 rounded-xl p-3 uppercase font-bold"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-4 border-t dark:border-slate-700">
|
||||
<button type="submit" disabled={saving} className="bg-primary text-white px-8 py-3 rounded-lg font-bold shadow-lg hover:opacity-90 flex gap-2 items-center"><Save className="w-5 h-5"/> Salva Personalizzazioni</button>
|
||||
{successMsg && <p className="text-sm text-green-600 mt-2 font-bold">{successMsg}</p>}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Logo</label><div className="border-4 border-dashed border-slate-100 rounded-3xl p-8 text-center relative h-48 flex items-center justify-center bg-slate-50/50 hover:bg-slate-50 transition-colors"><input type="file" onChange={handleLogoUpload} className="absolute inset-0 opacity-0 cursor-pointer z-10" />{brandingForm.logoUrl ? <img src={brandingForm.logoUrl} alt="Logo" className="max-h-full max-w-full object-contain" /> : <ImageIcon className="w-12 h-12 text-slate-300"/>}</div></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Sfondo Login</label><div className="border-4 border-dashed border-slate-100 rounded-3xl p-8 text-center relative h-48 flex items-center justify-center bg-slate-50/50 hover:bg-slate-50 transition-colors"><input type="file" onChange={handleBgUpload} className="absolute inset-0 opacity-0 cursor-pointer z-10" />{brandingForm.loginBackgroundUrl ? <img src={brandingForm.loginBackgroundUrl} alt="Bg" className="w-full h-full object-cover rounded-xl" /> : <Upload className="w-12 h-12 text-slate-300"/>}</div></div>
|
||||
</div>
|
||||
<div className="pt-6 border-t flex justify-end"><button type="submit" className="bg-slate-900 text-white px-10 py-4 rounded-2xl font-bold hover:bg-black shadow-xl"><Save className="w-5 h-5" /> Applica</button></div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'features' && isSuperAdmin && globalSettings && (
|
||||
<div className="animate-fade-in bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-bold text-slate-800 dark:text-slate-100 flex items-center gap-2"><LayoutGrid className="w-5 h-5 text-primary" /> Funzionalità Piattaforma</h3>
|
||||
</div>
|
||||
<form onSubmit={handleFeaturesSubmit} className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{Object.keys(globalSettings.features).map((key) => (
|
||||
<div key={key} className="flex items-center justify-between p-4 bg-slate-50 dark:bg-slate-700/50 rounded-xl border border-slate-100 dark:border-slate-700">
|
||||
<div><p className="font-bold text-slate-800 dark:text-slate-100 capitalize">{key.replace(/([A-Z])/g, ' $1')}</p></div>
|
||||
<button type="button" onClick={() => {
|
||||
const newFeats = { ...globalSettings.features, [key]: !globalSettings.features[key as keyof AppSettings['features']] };
|
||||
setGlobalSettings({ ...globalSettings, features: newFeats });
|
||||
}} className={`${globalSettings.features[key as keyof AppSettings['features']] ? 'bg-green-500' : 'bg-slate-300 dark:bg-slate-600'} relative inline-flex h-6 w-11 items-center rounded-full transition-colors`}><span className={`${globalSettings.features[key as keyof AppSettings['features']] ? 'translate-x-6' : 'translate-x-1'} inline-block h-4 w-4 transform rounded-full bg-white transition-transform`}/></button>
|
||||
{isSuperAdmin && activeTab === 'features' && globalSettings && (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex items-center gap-3 mb-8"><div className="p-3 bg-green-50 rounded-2xl text-green-600"><LayoutGrid className="w-6 h-6" /></div><h3 className="text-xl font-bold text-slate-800">Moduli</h3></div>
|
||||
<form onSubmit={handleFeaturesSubmit} className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{['multiCondo', 'tickets', 'documents', 'payPal', 'notices', 'reports', 'extraordinaryExpenses', 'condoFinancialsView'].map(k => (
|
||||
<div key={k} onClick={() => toggleFeature(k as any)} className={`flex items-center justify-between p-5 rounded-2xl border cursor-pointer ${globalSettings.features[k as keyof AppSettings['features']] ? 'bg-blue-50 border-blue-200' : 'bg-white border-slate-200'}`}>
|
||||
<span className="font-bold text-slate-800 capitalize">{k.replace(/([A-Z])/g, ' $1').trim()}</span>
|
||||
<span className={`h-6 w-11 rounded-full relative transition-colors ${globalSettings.features[k as keyof AppSettings['features']] ? 'bg-blue-600' : 'bg-slate-300'}`}><span className={`absolute top-1 left-1 bg-white w-4 h-4 rounded-full transition-transform ${globalSettings.features[k as keyof AppSettings['features']] ? 'translate-x-5' : ''}`}/></span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button type="submit" className="bg-primary text-white px-6 py-2.5 rounded-lg font-bold"><Save className="w-4 h-4 inline mr-2"/> Salva Funzionalità</button>
|
||||
<div className="pt-8 border-t flex justify-end"><button type="submit" className="bg-blue-600 text-white px-8 py-3 rounded-xl font-bold hover:bg-blue-700 shadow-lg">Salva</button></div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'general' && isPrivileged && activeCondo && (
|
||||
<div className="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-6">
|
||||
<h3 className="text-lg font-bold text-slate-800 dark:text-slate-100 mb-4 flex items-center gap-2"><Building className="w-5 h-5 text-primary" /> Dati Condominio</h3>
|
||||
<form onSubmit={handleGeneralSubmit} className="space-y-5">
|
||||
<input className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={activeCondo.name} onChange={e => setActiveCondo({...activeCondo, name: e.target.value})} placeholder="Nome" required />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<input className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={activeCondo.address || ''} onChange={e => setActiveCondo({...activeCondo, address: e.target.value})} placeholder="Indirizzo" required />
|
||||
<input className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={activeCondo.city || ''} onChange={e => setActiveCondo({...activeCondo, city: e.target.value})} placeholder="Città" required />
|
||||
|
||||
{/* TAB CONDOMINIO (GENERAL) */}
|
||||
{activeTab === 'general' && (
|
||||
<>
|
||||
{!activeCondo ? <NoCondoState /> : (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex items-center gap-3 mb-8"><div className="p-3 bg-blue-50 rounded-2xl text-blue-600"><Building className="w-6 h-6" /></div><h3 className="text-xl font-bold text-slate-800">Dati Condominio</h3></div>
|
||||
<form onSubmit={handleGeneralSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Nome</label><input type="text" value={activeCondo.name} onChange={e => setActiveCondo({...activeCondo, name: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl font-bold" required /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Indirizzo</label><input type="text" value={activeCondo.address || ''} onChange={e => setActiveCondo({...activeCondo, address: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl" required /></div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Città</label><input type="text" value={activeCondo.city || ''} onChange={e => setActiveCondo({...activeCondo, city: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl" /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Prov</label><input type="text" value={activeCondo.province || ''} onChange={e => setActiveCondo({...activeCondo, province: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl" /></div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Quota Mensile</label><input type="number" value={activeCondo.defaultMonthlyQuota} onChange={e => setActiveCondo({...activeCondo, defaultMonthlyQuota: parseFloat(e.target.value)})} className="w-full border border-slate-200 p-3 rounded-xl font-bold" /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">Scadenza (Giorno)</label><input type="number" value={activeCondo.dueDay || 10} onChange={e => setActiveCondo({...activeCondo, dueDay: parseInt(e.target.value)})} className="w-full border border-slate-200 p-3 rounded-xl" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-2 block">PayPal Client ID</label><input type="text" value={activeCondo.paypalClientId || ''} onChange={e => setActiveCondo({...activeCondo, paypalClientId: e.target.value})} className="w-full border border-slate-200 p-3 rounded-xl text-xs font-mono" /></div>
|
||||
<div className="pt-6 border-t flex justify-end"><button type="submit" className="bg-blue-600 text-white px-8 py-3 rounded-xl font-bold hover:bg-blue-700 shadow-lg">Salva Dati</button></div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">Quota Mensile €</label>
|
||||
<input type="number" className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={activeCondo.defaultMonthlyQuota} onChange={e => setActiveCondo({...activeCondo, defaultMonthlyQuota: parseFloat(e.target.value)})} required />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* TAB STORAGE */}
|
||||
{activeTab === 'storage' && (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex items-center gap-3 mb-8"><div className="p-3 bg-amber-50 rounded-2xl text-amber-600"><HardDrive className="w-6 h-6" /></div><h3 className="text-xl font-bold text-slate-800">Storage Cloud</h3></div>
|
||||
<form onSubmit={handleStorageSubmit} className="space-y-6">
|
||||
<select value={storageForm.provider} onChange={e => setStorageForm({...storageForm, provider: e.target.value as any})} className="w-full border border-slate-200 p-3 rounded-xl font-bold text-slate-700"><option value="local_db">Database Locale</option><option value="s3">Amazon S3</option></select>
|
||||
{storageForm.provider !== 'local_db' && (
|
||||
<div className="space-y-6 p-6 bg-slate-50 rounded-2xl border border-slate-100">
|
||||
<input value={storageForm.apiKey || ''} onChange={e => setStorageForm({...storageForm, apiKey: e.target.value})} className="w-full border p-3 rounded-xl" placeholder="API Key / Access Key" />
|
||||
<input value={storageForm.apiSecret || ''} onChange={e => setStorageForm({...storageForm, apiSecret: e.target.value})} className="w-full border p-3 rounded-xl" placeholder="Secret Key" type="password" />
|
||||
<input value={storageForm.bucket || ''} onChange={e => setStorageForm({...storageForm, bucket: e.target.value})} className="w-full border p-3 rounded-xl" placeholder="Bucket Name" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">Scadenza Giorno</label>
|
||||
<input type="number" className="w-full border p-2.5 rounded-lg dark:bg-slate-700 dark:text-slate-100" value={activeCondo.dueDay || 10} onChange={e => setActiveCondo({...activeCondo, dueDay: parseInt(e.target.value)})} required />
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" className="bg-primary text-white px-6 py-2.5 rounded-lg font-bold">Salva Modifiche</button>
|
||||
)}
|
||||
<div className="pt-4 border-t flex justify-end"><button type="submit" className="bg-slate-900 text-white px-8 py-3 rounded-xl font-bold">Salva</button></div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB LISTA CONDOMINI */}
|
||||
{activeTab === 'condos' && (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex justify-between items-center mb-8"><h3 className="text-xl font-bold text-slate-800">Elenco Condomini</h3><button onClick={openAddCondoModal} className="bg-blue-600 text-white px-4 py-2 rounded-xl font-bold flex gap-2"><Plus className="w-4 h-4"/> Aggiungi</button></div>
|
||||
<div className="grid gap-4">
|
||||
{condos.map(c => (
|
||||
<div key={c.id} className="flex items-center justify-between p-5 rounded-2xl border bg-slate-50 border-slate-100">
|
||||
<div><p className="font-bold text-slate-800">{c.name}</p><p className="text-xs text-slate-500">{c.city}</p></div>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => CondoService.setActiveCondo(c.id)} className="px-4 py-2 bg-white border border-slate-200 rounded-lg text-xs font-bold hover:bg-slate-50">Usa</button>
|
||||
<button onClick={() => openEditCondoModal(c)} className="p-2 text-blue-600 hover:bg-blue-100 rounded-lg"><Pencil className="w-5 h-5"/></button>
|
||||
<button onClick={() => handleDeleteCondo(c.id)} className="p-2 text-red-500 hover:bg-red-50 rounded-lg"><Trash2 className="w-5 h-5"/></button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB FAMIGLIE */}
|
||||
{activeTab === 'families' && (
|
||||
<>
|
||||
{!activeCondo ? <NoCondoState /> : (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex justify-between items-center mb-8"><h3 className="text-xl font-bold text-slate-800">Famiglie</h3><button onClick={openAddFamilyModal} className="bg-blue-600 text-white px-4 py-2 rounded-xl font-bold flex gap-2"><Plus className="w-4 h-4"/> Aggiungi</button></div>
|
||||
<div className="overflow-x-auto border border-slate-100 rounded-2xl">
|
||||
<table className="w-full text-sm text-left">
|
||||
<thead className="bg-slate-50 text-slate-500 font-bold uppercase text-[10px]"><tr><th className="px-6 py-4">Nome</th><th className="px-6 py-4">Unità</th><th className="px-6 py-4 text-right">Azioni</th></tr></thead>
|
||||
<tbody className="divide-y divide-slate-50">{families.map(f => (<tr key={f.id} className="hover:bg-slate-50"><td className="px-6 py-4 font-bold text-slate-800">{f.name}</td><td className="px-6 py-4">{f.unitNumber}</td><td className="px-6 py-4 text-right"><div className="flex justify-end gap-2"><button onClick={() => openEditFamilyModal(f)} className="text-blue-600 p-2"><Pencil className="w-4 h-4"/></button><button onClick={() => handleDeleteFamily(f.id)} className="text-red-500 p-2"><Trash2 className="w-4 h-4"/></button></div></td></tr>))}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* TAB USERS (ADMIN) */}
|
||||
{activeTab === 'users' && (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex justify-between items-center mb-8"><h3 className="text-xl font-bold text-slate-800">Utenti</h3><button onClick={openAddUserModal} className="bg-blue-600 text-white px-4 py-2 rounded-xl font-bold flex gap-2"><Plus className="w-4 h-4"/> Aggiungi</button></div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{users.map(u => (
|
||||
<div key={u.id} className="p-5 border border-slate-100 rounded-2xl bg-slate-50 flex items-center justify-between">
|
||||
<div><p className="font-bold text-slate-800">{u.name}</p><p className="text-xs text-slate-400">{u.email}</p><span className="text-[10px] uppercase font-bold bg-white px-2 py-0.5 rounded border border-slate-200 mt-1 inline-block">{u.role}</span></div>
|
||||
<div className="flex gap-1"><button onClick={() => openEditUserModal(u)} className="p-2 text-blue-600 hover:bg-white rounded-lg"><Pencil className="w-4 h-4"/></button><button onClick={() => handleDeleteUser(u.id)} className="p-2 text-red-500 hover:bg-white rounded-lg"><Trash2 className="w-4 h-4"/></button></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB NOTICES */}
|
||||
{activeTab === 'notices' && (
|
||||
<>
|
||||
{!activeCondo ? <NoCondoState /> : (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex justify-between items-center mb-8"><h3 className="text-xl font-bold text-slate-800">Bacheca</h3><button onClick={openAddNoticeModal} className="bg-blue-600 text-white px-4 py-2 rounded-xl font-bold flex gap-2"><Plus className="w-4 h-4"/> Pubblica</button></div>
|
||||
<div className="grid gap-4">{notices.map(n => (<div key={n.id} className="p-5 border rounded-2xl flex justify-between items-center"><div><h4 className="font-bold text-slate-800">{n.title}</h4><p className="text-sm text-slate-500 line-clamp-1">{n.content}</p></div><div className="flex gap-2"><button onClick={() => { setSelectedNoticeId(n.id); setShowReadDetailsModal(true); }} className="p-2 text-slate-500 bg-slate-50 rounded-lg"><Eye className="w-4 h-4"/></button><button onClick={() => openEditNoticeModal(n)} className="p-2 text-blue-600 bg-blue-50 rounded-lg"><Pencil className="w-4 h-4"/></button><button onClick={() => handleDeleteNotice(n.id)} className="p-2 text-red-500 bg-red-50 rounded-lg"><Trash2 className="w-4 h-4"/></button></div></div>))}</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* TAB ALERTS */}
|
||||
{activeTab === 'alerts' && (
|
||||
<>
|
||||
{!activeCondo ? <NoCondoState /> : (
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<div className="flex justify-between items-center mb-8"><h3 className="text-xl font-bold text-slate-800">Alerts</h3><div className="flex gap-2"><button onClick={() => setShowSmtpModal(true)} className="bg-slate-100 text-slate-700 px-4 py-2 rounded-xl font-bold">SMTP</button><button onClick={openAddAlertModal} className="bg-blue-600 text-white px-4 py-2 rounded-xl font-bold"><Plus className="w-4 h-4"/></button></div></div>
|
||||
<div className="grid gap-4">{alerts.map(a => (<div key={a.id} className="p-5 border rounded-2xl flex justify-between"><div><h4 className="font-bold text-slate-800">{a.subject}</h4><p className="text-xs text-slate-500">{a.daysOffset}gg {a.offsetType}</p></div><button onClick={() => handleDeleteAlert(a.id)} className="text-red-500"><Trash2 className="w-4 h-4"/></button></div>))}</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- MODALS --- */}
|
||||
{showCondoModal && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-lg p-8 animate-in fade-in zoom-in duration-200">
|
||||
<h3 className="font-bold text-2xl mb-6 text-slate-800">{editingCondo ? 'Modifica Condominio' : 'Nuovo Condominio'}</h3>
|
||||
<form onSubmit={handleCondoSubmit} className="space-y-4">
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Nome Condominio" value={condoForm.name} onChange={e => setCondoForm({...condoForm, name: e.target.value})} required />
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Indirizzo" value={condoForm.address} onChange={e => setCondoForm({...condoForm, address: e.target.value})} />
|
||||
<div className="flex gap-4 pt-4"><button type="button" onClick={() => setShowCondoModal(false)} className="flex-1 bg-slate-100 py-3 rounded-xl font-bold hover:bg-slate-200">Annulla</button><button type="submit" className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700">Salva</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showFamilyModal && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md p-8 animate-in fade-in zoom-in duration-200">
|
||||
<h3 className="font-bold text-2xl mb-6 text-slate-800">{editingFamily ? 'Modifica Famiglia' : 'Nuova Famiglia'}</h3>
|
||||
<form onSubmit={handleFamilySubmit} className="space-y-4">
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Nome (es. Fam. Rossi)" value={familyForm.name} onChange={e => setFamilyForm({...familyForm, name: e.target.value})} required />
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Interno / Unità" value={familyForm.unitNumber} onChange={e => setFamilyForm({...familyForm, unitNumber: e.target.value})} required />
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Email Contatto" type="email" value={familyForm.contactEmail} onChange={e => setFamilyForm({...familyForm, contactEmail: e.target.value})} />
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Quota Personalizzata (Opzionale)" type="number" step="0.01" value={familyForm.customMonthlyQuota} onChange={e => setFamilyForm({...familyForm, customMonthlyQuota: e.target.value})} />
|
||||
<div className="flex gap-4 pt-4"><button type="button" onClick={() => setShowFamilyModal(false)} className="flex-1 bg-slate-100 py-3 rounded-xl font-bold hover:bg-slate-200">Annulla</button><button type="submit" className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700">Salva</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showUserModal && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md p-8 animate-in fade-in zoom-in duration-200">
|
||||
<h3 className="font-bold text-2xl mb-6 text-slate-800">{editingUser ? 'Modifica Utente' : 'Nuovo Utente'}</h3>
|
||||
<form onSubmit={handleUserSubmit} className="space-y-4">
|
||||
<input type="email" className="w-full border p-3 rounded-xl" placeholder="Email" value={userForm.email} onChange={e => setUserForm({...userForm, email: e.target.value})} required />
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Nome" value={userForm.name} onChange={e => setUserForm({...userForm, name: e.target.value})} />
|
||||
<input type="password" className="w-full border p-3 rounded-xl" placeholder="Password (lascia vuoto se invariata)" value={userForm.password} onChange={e => setUserForm({...userForm, password: e.target.value})} />
|
||||
<div>
|
||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Ruolo</label>
|
||||
<select className="w-full border p-3 rounded-xl bg-white" value={userForm.role} onChange={(e: any) => setUserForm({...userForm, role: e.target.value})}>
|
||||
<option value="user">Utente Standard</option>
|
||||
<option value="poweruser">Power User</option>
|
||||
<option value="admin">Amministratore</option>
|
||||
</select>
|
||||
</div>
|
||||
{userForm.role === 'user' && (
|
||||
<div>
|
||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Associa Famiglia</label>
|
||||
<select className="w-full border p-3 rounded-xl bg-white" value={userForm.familyId} onChange={(e) => setUserForm({...userForm, familyId: e.target.value})}>
|
||||
<option value="">Nessuna</option>
|
||||
{families.map(f => <option key={f.id} value={f.id}>{f.name} ({f.unitNumber})</option>)}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-4 pt-4"><button type="button" onClick={() => setShowUserModal(false)} className="flex-1 bg-slate-100 py-3 rounded-xl font-bold hover:bg-slate-200">Annulla</button><button type="submit" className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700">Salva</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showNoticeModal && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-lg p-8 animate-in fade-in zoom-in duration-200">
|
||||
<h3 className="font-bold text-2xl mb-6 text-slate-800">{editingNotice ? 'Modifica Avviso' : 'Nuovo Avviso'}</h3>
|
||||
<form onSubmit={handleNoticeSubmit} className="space-y-4">
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Titolo" value={noticeForm.title} onChange={e => setNoticeForm({...noticeForm, title: e.target.value})} required />
|
||||
<textarea className="w-full border p-3 rounded-xl h-24" placeholder="Contenuto..." value={noticeForm.content} onChange={e => setNoticeForm({...noticeForm, content: e.target.value})} required />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Tipo</label>
|
||||
<select className="w-full border p-3 rounded-xl bg-white" value={noticeForm.type} onChange={(e: any) => setNoticeForm({...noticeForm, type: e.target.value})}>
|
||||
<option value="info">Info</option>
|
||||
<option value="warning">Avviso</option>
|
||||
<option value="maintenance">Lavori</option>
|
||||
<option value="event">Evento</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Visibilità</label>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<button type="button" onClick={() => setNoticeTargetMode('all')} className={`flex-1 py-2 rounded-lg text-xs font-bold border ${noticeTargetMode === 'all' ? 'bg-blue-50 border-blue-200 text-blue-600' : 'bg-white border-slate-200 text-slate-500'}`}>Tutti</button>
|
||||
<button type="button" onClick={() => setNoticeTargetMode('specific')} className={`flex-1 py-2 rounded-lg text-xs font-bold border ${noticeTargetMode === 'specific' ? 'bg-blue-50 border-blue-200 text-blue-600' : 'bg-white border-slate-200 text-slate-500'}`}>Specifici</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{noticeTargetMode === 'specific' && (
|
||||
<div className="border rounded-xl p-2 max-h-32 overflow-y-auto">
|
||||
{families.map(f => (
|
||||
<label key={f.id} className="flex items-center gap-2 p-2 hover:bg-slate-50 rounded cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={noticeForm.targetFamilyIds.includes(f.id)}
|
||||
onChange={e => {
|
||||
if (e.target.checked) setNoticeForm(prev => ({...prev, targetFamilyIds: [...prev.targetFamilyIds, f.id]}));
|
||||
else setNoticeForm(prev => ({...prev, targetFamilyIds: prev.targetFamilyIds.filter(id => id !== f.id)}));
|
||||
}}
|
||||
className="rounded text-blue-600"
|
||||
/>
|
||||
<span className="text-sm text-slate-700">{f.name}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Link esterno (es. PDF Drive)" value={noticeForm.link} onChange={e => setNoticeForm({...noticeForm, link: e.target.value})} />
|
||||
<div className="flex gap-4 pt-4"><button type="button" onClick={() => setShowNoticeModal(false)} className="flex-1 bg-slate-100 py-3 rounded-xl font-bold hover:bg-slate-200">Annulla</button><button type="submit" className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700">Pubblica</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showAlertModal && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-lg p-8 animate-in fade-in zoom-in duration-200">
|
||||
<h3 className="font-bold text-2xl mb-6 text-slate-800">Regola Avviso Automatico</h3>
|
||||
<form onSubmit={handleAlertSubmit} className="space-y-4">
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Oggetto Email" value={alertForm.subject} onChange={e => setAlertForm({...alertForm, subject: e.target.value})} required />
|
||||
<textarea className="w-full border p-3 rounded-xl h-24" placeholder="Testo Email..." value={alertForm.body} onChange={e => setAlertForm({...alertForm, body: e.target.value})} required />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Giorni Offset</label><input type="number" className="w-full border p-3 rounded-xl" value={alertForm.daysOffset} onChange={e => setAlertForm({...alertForm, daysOffset: parseInt(e.target.value)})} /></div>
|
||||
<div><label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Tipo Offset</label><select className="w-full border p-3 rounded-xl bg-white" value={alertForm.offsetType} onChange={(e: any) => setAlertForm({...alertForm, offsetType: e.target.value})}><option value="before_next_month">Giorni prima scadenza</option><option value="after_current_month">Giorni dopo scadenza</option></select></div>
|
||||
</div>
|
||||
<div className="flex gap-4 pt-4"><button type="button" onClick={() => setShowAlertModal(false)} className="flex-1 bg-slate-100 py-3 rounded-xl font-bold hover:bg-slate-200">Annulla</button><button type="submit" className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700">Salva</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSmtpModal && globalSettings?.smtpConfig && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md p-8 animate-in fade-in zoom-in duration-200">
|
||||
<h3 className="font-bold text-2xl mb-6 text-slate-800">Configurazione SMTP</h3>
|
||||
<form onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
// Update settings directly via API
|
||||
const updated = { ...globalSettings, smtpConfig: globalSettings.smtpConfig };
|
||||
await CondoService.updateSettings(updated);
|
||||
setGlobalSettings(updated);
|
||||
setShowSmtpModal(false);
|
||||
}} className="space-y-4">
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Host (smtp.gmail.com)" value={globalSettings.smtpConfig.host} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, host: e.target.value}})} required />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<input type="number" className="w-full border p-3 rounded-xl" placeholder="Porta (465)" value={globalSettings.smtpConfig.port} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, port: parseInt(e.target.value)}})} required />
|
||||
<label className="flex items-center gap-2 border p-3 rounded-xl bg-slate-50 cursor-pointer"><input type="checkbox" checked={globalSettings.smtpConfig.secure} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, secure: e.target.checked}})} /> <span className="text-sm font-bold text-slate-600">SSL/TLS</span></label>
|
||||
</div>
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="Utente/Email" value={globalSettings.smtpConfig.user} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, user: e.target.value}})} required />
|
||||
<input className="w-full border p-3 rounded-xl" type="password" placeholder="Password" value={globalSettings.smtpConfig.pass} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, pass: e.target.value}})} required />
|
||||
<input className="w-full border p-3 rounded-xl" placeholder="From Email (Opzionale)" value={globalSettings.smtpConfig.fromEmail || ''} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, fromEmail: e.target.value}})} />
|
||||
|
||||
{testSmtpMsg && <div className={`p-3 rounded-xl text-sm font-bold text-center ${testSmtpMsg.includes('Errore') ? 'bg-red-50 text-red-600' : 'bg-green-50 text-green-600'}`}>{testSmtpMsg}</div>}
|
||||
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button type="button" onClick={handleTestSmtp} disabled={testingSmtp} className="flex-1 bg-slate-100 text-slate-600 py-3 rounded-xl font-bold hover:bg-slate-200">{testingSmtp ? '...' : 'Test'}</button>
|
||||
<button type="submit" className="flex-1 bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700">Salva Config</button>
|
||||
</div>
|
||||
</form>
|
||||
<button onClick={() => setShowSmtpModal(false)} className="absolute top-4 right-4 text-slate-400 hover:text-slate-600"><X className="w-6 h-6"/></button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showReadDetailsModal && selectedNoticeId && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
||||
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200 max-h-[80vh] flex flex-col">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="font-bold text-lg text-slate-800">Stato Lettura</h3>
|
||||
<button onClick={() => setShowReadDetailsModal(false)}><X className="w-5 h-5 text-slate-400"/></button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{users.length === 0 ? <p className="text-center text-slate-400">Nessun utente.</p> : (
|
||||
<div className="space-y-2">
|
||||
{users.map(u => {
|
||||
const readInfo = noticeReadStats[selectedNoticeId]?.find(r => r.userId === u.id);
|
||||
return (
|
||||
<div key={u.id} className="flex items-center justify-between p-3 border rounded-xl bg-slate-50">
|
||||
<div>
|
||||
<p className="font-bold text-sm text-slate-700">{u.name}</p>
|
||||
<p className="text-xs text-slate-400">{u.email}</p>
|
||||
</div>
|
||||
{readInfo ? (
|
||||
<div className="text-right">
|
||||
<span className="text-xs font-bold text-green-600 bg-green-100 px-2 py-1 rounded-full flex items-center gap-1 justify-end"><CheckCircle2 className="w-3 h-3"/> Letto</span>
|
||||
<p className="text-[10px] text-slate-400 mt-1">{new Date(readInfo.readAt).toLocaleString()}</p>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs font-bold text-slate-400 bg-white border px-2 py-1 rounded-full">Non letto</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user