Introduces SMTP configuration settings and alert definitions to enable automated email notifications. This includes new types for `SmtpConfig` and `AlertDefinition`, and integrates these into the settings page and mock database. Adds styling for select elements and scrollbar hiding in the main HTML. Updates mock database logic to potentially support local development without a backend.
1133 lines
57 KiB
TypeScript
1133 lines
57 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { CondoService } from '../services/mockDb';
|
|
import { AppSettings, Family, User, AlertDefinition } from '../types';
|
|
import { Save, Building, Coins, Plus, Pencil, Trash2, X, CalendarCheck, AlertTriangle, User as UserIcon, Mail, Server, Bell, Clock, FileText, Send, Lock } from 'lucide-react';
|
|
|
|
export const SettingsPage: React.FC = () => {
|
|
const currentUser = CondoService.getCurrentUser();
|
|
const isAdmin = currentUser?.role === 'admin';
|
|
|
|
const [activeTab, setActiveTab] = useState<'profile' | 'general' | 'families' | 'users' | 'smtp' | 'alerts'>(isAdmin ? 'general' : 'profile');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// Profile State
|
|
const [profileForm, setProfileForm] = useState({
|
|
name: currentUser?.name || '',
|
|
phone: currentUser?.phone || '',
|
|
password: '',
|
|
receiveAlerts: currentUser?.receiveAlerts ?? true
|
|
});
|
|
const [profileSaving, setProfileSaving] = useState(false);
|
|
const [profileMsg, setProfileMsg] = useState('');
|
|
|
|
// General Settings State
|
|
const [settings, setSettings] = useState<AppSettings>({
|
|
defaultMonthlyQuota: 0,
|
|
condoName: '',
|
|
currentYear: new Date().getFullYear(),
|
|
smtpConfig: {
|
|
host: '', port: 587, user: '', pass: '', secure: false, fromEmail: ''
|
|
}
|
|
});
|
|
const [saving, setSaving] = useState(false);
|
|
const [successMsg, setSuccessMsg] = useState('');
|
|
|
|
// Families State
|
|
const [families, setFamilies] = useState<Family[]>([]);
|
|
const [showFamilyModal, setShowFamilyModal] = useState(false);
|
|
const [editingFamily, setEditingFamily] = useState<Family | null>(null);
|
|
const [familyForm, setFamilyForm] = useState({ name: '', unitNumber: '', contactEmail: '' });
|
|
|
|
// Users State
|
|
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
|
|
});
|
|
|
|
// Alerts State
|
|
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: 0,
|
|
offsetType: 'before_next_month',
|
|
sendHour: 9,
|
|
active: true
|
|
});
|
|
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
if (isAdmin) {
|
|
const [s, f, u, a] = await Promise.all([
|
|
CondoService.getSettings(),
|
|
CondoService.getFamilies(),
|
|
CondoService.getUsers(),
|
|
CondoService.getAlerts()
|
|
]);
|
|
setSettings(s);
|
|
setFamilies(f);
|
|
setUsers(u);
|
|
setAlerts(a);
|
|
} else {
|
|
// If not admin, we might only need limited data, or nothing if we rely on session
|
|
// For now, nothing extra to fetch for the profile as it's in session
|
|
}
|
|
} catch(e) {
|
|
console.error(e);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
fetchData();
|
|
}, [isAdmin]);
|
|
|
|
// --- Profile Handlers ---
|
|
const handleProfileSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setProfileSaving(true);
|
|
setProfileMsg('');
|
|
try {
|
|
await CondoService.updateProfile(profileForm);
|
|
setProfileMsg('Profilo aggiornato con successo!');
|
|
setTimeout(() => setProfileMsg(''), 3000);
|
|
setProfileForm(prev => ({ ...prev, password: '' })); // clear password
|
|
} catch (e) {
|
|
console.error(e);
|
|
setProfileMsg('Errore aggiornamento profilo');
|
|
} finally {
|
|
setProfileSaving(false);
|
|
}
|
|
};
|
|
|
|
|
|
// --- Settings Handlers ---
|
|
|
|
const handleSettingsSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setSaving(true);
|
|
setSuccessMsg('');
|
|
try {
|
|
await CondoService.updateSettings(settings);
|
|
setSuccessMsg('Impostazioni salvate con successo!');
|
|
setTimeout(() => setSuccessMsg(''), 3000);
|
|
} catch (e) {
|
|
console.error(e);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleNewYear = async () => {
|
|
const nextYear = settings.currentYear + 1;
|
|
if (window.confirm(`Sei sicuro di voler chiudere l'anno ${settings.currentYear} e aprire il ${nextYear}? \n\nI dati vecchi non verranno cancellati, ma la visualizzazione di default passerà al nuovo anno con saldi azzerati.`)) {
|
|
setSaving(true);
|
|
try {
|
|
const newSettings = { ...settings, currentYear: nextYear };
|
|
await CondoService.updateSettings(newSettings);
|
|
setSettings(newSettings);
|
|
setSuccessMsg(`Anno ${nextYear} aperto con successo!`);
|
|
setTimeout(() => setSuccessMsg(''), 3000);
|
|
} catch(e) {
|
|
console.error(e);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
// --- Family Handlers ---
|
|
|
|
const openAddFamilyModal = () => {
|
|
setEditingFamily(null);
|
|
setFamilyForm({ name: '', unitNumber: '', contactEmail: '' });
|
|
setShowFamilyModal(true);
|
|
};
|
|
|
|
const openEditFamilyModal = (family: Family) => {
|
|
setEditingFamily(family);
|
|
setFamilyForm({
|
|
name: family.name,
|
|
unitNumber: family.unitNumber,
|
|
contactEmail: family.contactEmail || ''
|
|
});
|
|
setShowFamilyModal(true);
|
|
};
|
|
|
|
const handleDeleteFamily = async (id: string) => {
|
|
if (!window.confirm('Sei sicuro di voler eliminare questa famiglia? Tutti i dati e lo storico pagamenti verranno persi.')) {
|
|
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 {
|
|
if (editingFamily) {
|
|
const updatedFamily = {
|
|
...editingFamily,
|
|
name: familyForm.name,
|
|
unitNumber: familyForm.unitNumber,
|
|
contactEmail: familyForm.contactEmail
|
|
};
|
|
await CondoService.updateFamily(updatedFamily);
|
|
setFamilies(families.map(f => f.id === updatedFamily.id ? updatedFamily : f));
|
|
} else {
|
|
const newFamily = await CondoService.addFamily(familyForm);
|
|
setFamilies([...families, newFamily]);
|
|
}
|
|
setShowFamilyModal(false);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
// --- User Handlers ---
|
|
|
|
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 handleDeleteUser = async (id: string) => {
|
|
if(!window.confirm("Sei sicuro di voler eliminare questo utente?")) return;
|
|
try {
|
|
await CondoService.deleteUser(id);
|
|
setUsers(users.filter(u => u.id !== id));
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
const handleUserSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
try {
|
|
if (editingUser) {
|
|
await CondoService.updateUser(editingUser.id, userForm);
|
|
const updatedUsers = await CondoService.getUsers();
|
|
setUsers(updatedUsers);
|
|
} else {
|
|
await CondoService.createUser(userForm);
|
|
const updatedUsers = await CondoService.getUsers();
|
|
setUsers(updatedUsers);
|
|
}
|
|
setShowUserModal(false);
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert("Errore nel salvataggio utente");
|
|
}
|
|
};
|
|
|
|
// --- Alert Handlers ---
|
|
|
|
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 handleDeleteAlert = async (id: string) => {
|
|
if(!window.confirm("Eliminare questo avviso automatico?")) return;
|
|
try {
|
|
await CondoService.deleteAlert(id);
|
|
setAlerts(alerts.filter(a => a.id !== id));
|
|
} catch (e) { console.error(e); }
|
|
};
|
|
|
|
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);
|
|
if (editingAlert) {
|
|
setAlerts(alerts.map(a => a.id === saved.id ? saved : a));
|
|
} else {
|
|
setAlerts([...alerts, saved]);
|
|
}
|
|
setShowAlertModal(false);
|
|
} catch (e) { console.error(e); }
|
|
};
|
|
|
|
if (loading) return <div className="p-8 text-center text-slate-400">Caricamento...</div>;
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto space-y-6 pb-20">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-slate-800">Impostazioni</h2>
|
|
<p className="text-slate-500 text-sm md:text-base">Gestisci configurazione, anagrafica, utenti e comunicazioni.</p>
|
|
</div>
|
|
|
|
{/* Tabs - Scrollable on mobile */}
|
|
<div className="flex border-b border-slate-200 overflow-x-auto no-scrollbar pb-1">
|
|
|
|
{/* Profile Tab (Always Visible) */}
|
|
<button
|
|
onClick={() => setActiveTab('profile')}
|
|
className={`px-4 md:px-6 py-3 font-medium text-sm transition-all relative whitespace-nowrap flex-shrink-0 ${
|
|
activeTab === 'profile' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
Il Mio Profilo
|
|
{activeTab === 'profile' && <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"></div>}
|
|
</button>
|
|
|
|
{/* Admin Tabs */}
|
|
{isAdmin && (
|
|
<>
|
|
<button
|
|
onClick={() => setActiveTab('general')}
|
|
className={`px-4 md:px-6 py-3 font-medium text-sm transition-all relative whitespace-nowrap flex-shrink-0 ${
|
|
activeTab === 'general' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
Generale
|
|
{activeTab === 'general' && <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"></div>}
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('families')}
|
|
className={`px-4 md:px-6 py-3 font-medium text-sm transition-all relative whitespace-nowrap flex-shrink-0 ${
|
|
activeTab === 'families' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
Famiglie
|
|
{activeTab === 'families' && <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"></div>}
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('users')}
|
|
className={`px-4 md:px-6 py-3 font-medium text-sm transition-all relative whitespace-nowrap flex-shrink-0 ${
|
|
activeTab === 'users' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
Utenti
|
|
{activeTab === 'users' && <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"></div>}
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('smtp')}
|
|
className={`px-4 md:px-6 py-3 font-medium text-sm transition-all relative whitespace-nowrap flex-shrink-0 ${
|
|
activeTab === 'smtp' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
SMTP
|
|
{activeTab === 'smtp' && <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"></div>}
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('alerts')}
|
|
className={`px-4 md:px-6 py-3 font-medium text-sm transition-all relative whitespace-nowrap flex-shrink-0 ${
|
|
activeTab === 'alerts' ? 'text-blue-600' : 'text-slate-500 hover:text-slate-700'
|
|
}`}
|
|
>
|
|
Avvisi Automatici
|
|
{activeTab === 'alerts' && <div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"></div>}
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{activeTab === 'profile' && (
|
|
<div className="animate-fade-in bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8 max-w-2xl">
|
|
<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" />
|
|
Dati Profilo
|
|
</h3>
|
|
|
|
<form onSubmit={handleProfileSubmit} className="space-y-5">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Il tuo Nome</label>
|
|
<input
|
|
type="text"
|
|
value={profileForm.name}
|
|
onChange={(e) => setProfileForm({...profileForm, name: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Email (Login)</label>
|
|
<input
|
|
type="email"
|
|
value={currentUser?.email || ''}
|
|
disabled
|
|
className="w-full border border-slate-200 bg-slate-50 text-slate-500 rounded-lg p-2.5 outline-none cursor-not-allowed"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Telefono</label>
|
|
<input
|
|
type="tel"
|
|
value={profileForm.phone}
|
|
onChange={(e) => setProfileForm({...profileForm, phone: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Nuova Password</label>
|
|
<div className="relative">
|
|
<input
|
|
type="password"
|
|
placeholder="Lascia vuoto per non cambiare"
|
|
value={profileForm.password}
|
|
onChange={(e) => setProfileForm({...profileForm, password: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 pl-9 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
<Lock className="w-4 h-4 text-slate-400 absolute left-3 top-3" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2 border-t border-slate-100">
|
|
<div className="flex items-center gap-3 bg-blue-50 p-4 rounded-lg border border-blue-100">
|
|
<input
|
|
type="checkbox"
|
|
id="myAlerts"
|
|
checked={profileForm.receiveAlerts}
|
|
onChange={(e) => setProfileForm({...profileForm, receiveAlerts: e.target.checked})}
|
|
className="w-5 h-5 text-blue-600 rounded border-slate-300 focus:ring-blue-500"
|
|
/>
|
|
<div>
|
|
<label htmlFor="myAlerts" className="text-sm font-bold text-slate-800 cursor-pointer block">Ricevi Avvisi Automatici</label>
|
|
<p className="text-xs text-slate-600">Abilita la ricezione di email per scadenze e comunicazioni condominiali.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2 flex items-center justify-between">
|
|
<span className={`text-sm font-medium ${profileMsg.includes('Errore') ? 'text-red-600' : 'text-green-600'}`}>{profileMsg}</span>
|
|
<button type="submit" disabled={profileSaving} className="bg-blue-600 text-white px-6 py-2.5 rounded-lg font-medium hover:bg-blue-700 transition-all flex items-center gap-2 disabled:opacity-70">
|
|
<Save className="w-4 h-4" /> Aggiorna Profilo
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
)}
|
|
|
|
{isAdmin && activeTab === 'general' && (
|
|
<div className="space-y-6 animate-fade-in">
|
|
{/* General Data Form */}
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8 max-w-2xl">
|
|
<h3 className="text-lg font-bold text-slate-800 mb-4">Dati Generali</h3>
|
|
<form onSubmit={handleSettingsSubmit} className="space-y-5">
|
|
<div>
|
|
<label className="block text-sm font-semibold text-slate-700 mb-2 flex items-center gap-2">
|
|
<Building className="w-4 h-4" />
|
|
Nome Condominio
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={settings.condoName}
|
|
onChange={(e) => setSettings({ ...settings, condoName: e.target.value })}
|
|
className="w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-blue-500 outline-none transition-all"
|
|
placeholder="Es. Condominio Roma"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-semibold text-slate-700 mb-2 flex items-center gap-2">
|
|
<Coins className="w-4 h-4" />
|
|
Quota Mensile (€)
|
|
</label>
|
|
<div className="relative">
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
value={settings.defaultMonthlyQuota}
|
|
onChange={(e) => setSettings({ ...settings, defaultMonthlyQuota: parseFloat(e.target.value) })}
|
|
className="w-full border border-slate-300 rounded-lg px-4 py-2.5 pl-8 focus:ring-2 focus:ring-blue-500 outline-none transition-all"
|
|
required
|
|
/>
|
|
<span className="absolute left-3 top-2.5 text-slate-400">€</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-4 flex flex-col-reverse md:flex-row items-center justify-between gap-4">
|
|
<span className={`text-sm font-medium h-5 ${successMsg ? 'text-green-600' : ''}`}>{successMsg}</span>
|
|
<button
|
|
type="submit"
|
|
disabled={saving}
|
|
className="w-full md:w-auto flex items-center justify-center gap-2 bg-blue-600 text-white px-6 py-2.5 rounded-lg font-medium hover:bg-blue-700 transition-all disabled:opacity-70"
|
|
>
|
|
<Save className="w-4 h-4" />
|
|
{saving ? '...' : 'Salva'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
{/* Fiscal Year Management */}
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8 max-w-2xl">
|
|
<h3 className="text-lg font-bold text-slate-800 mb-2 flex items-center gap-2">
|
|
<CalendarCheck className="w-5 h-5 text-slate-600" />
|
|
Anno Fiscale
|
|
</h3>
|
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 mb-6">
|
|
<p className="text-slate-600 text-sm">
|
|
Anno corrente: <span className="font-bold text-slate-900 text-lg">{settings.currentYear}</span>
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex items-start gap-3 bg-amber-50 p-3 rounded-lg border border-amber-100">
|
|
<AlertTriangle className="w-5 h-5 text-amber-500 flex-shrink-0 mt-0.5" />
|
|
<p className="text-xs text-amber-800">
|
|
Chiudendo l'anno, il sistema passerà al <strong>{settings.currentYear + 1}</strong>. I dati storici rimarranno consultabili.
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={handleNewYear}
|
|
disabled={saving}
|
|
className="w-full md:w-auto self-end bg-slate-800 text-white px-4 py-2.5 rounded-lg font-medium hover:bg-slate-900 transition-all disabled:opacity-70 text-sm"
|
|
>
|
|
Chiudi {settings.currentYear} e apri {settings.currentYear + 1}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{isAdmin && activeTab === 'families' && (
|
|
<div className="space-y-4 animate-fade-in">
|
|
<div className="flex justify-end">
|
|
<button
|
|
onClick={openAddFamilyModal}
|
|
className="w-full md:w-auto flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-5 py-2.5 rounded-lg font-medium shadow-sm transition-colors"
|
|
>
|
|
<Plus className="w-5 h-5" />
|
|
Aggiungi Famiglia
|
|
</button>
|
|
</div>
|
|
|
|
{/* Desktop Table */}
|
|
<div className="hidden md:block bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
<table className="w-full text-left text-sm text-slate-600">
|
|
<thead className="bg-slate-50 text-slate-700 font-semibold border-b border-slate-200">
|
|
<tr>
|
|
<th className="px-6 py-4">Nome Famiglia</th>
|
|
<th className="px-6 py-4">Interno</th>
|
|
<th className="px-6 py-4">Email</th>
|
|
<th className="px-6 py-4 text-right">Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{families.map(family => (
|
|
<tr key={family.id} className="hover:bg-slate-50 transition-colors">
|
|
<td className="px-6 py-4 font-medium text-slate-900">{family.name}</td>
|
|
<td className="px-6 py-4">{family.unitNumber}</td>
|
|
<td className="px-6 py-4 text-slate-400">{family.contactEmail || '-'}</td>
|
|
<td className="px-6 py-4 text-right">
|
|
<div className="flex items-center justify-end gap-2">
|
|
<button onClick={() => openEditFamilyModal(family)} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg"><Pencil className="w-4 h-4" /></button>
|
|
<button onClick={() => handleDeleteFamily(family.id)} className="p-2 text-red-600 hover:bg-red-50 rounded-lg"><Trash2 className="w-4 h-4" /></button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* Mobile Cards for Families */}
|
|
<div className="md:hidden space-y-3">
|
|
{families.map(family => (
|
|
<div key={family.id} className="bg-white p-5 rounded-xl border border-slate-200 shadow-sm">
|
|
<div className="flex justify-between items-start mb-2">
|
|
<div>
|
|
<h4 className="font-bold text-slate-800 text-lg">{family.name}</h4>
|
|
<p className="text-sm text-slate-500 font-medium">Interno: {family.unitNumber}</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-slate-500 mb-4 flex items-center gap-2">
|
|
<Mail className="w-4 h-4" />
|
|
{family.contactEmail || 'Nessuna email'}
|
|
</p>
|
|
<div className="grid grid-cols-2 gap-3 border-t border-slate-100 pt-3">
|
|
<button onClick={() => openEditFamilyModal(family)} className="flex items-center justify-center gap-2 py-2 text-blue-600 bg-blue-50 rounded-lg text-sm font-bold">
|
|
<Pencil className="w-4 h-4" /> Modifica
|
|
</button>
|
|
<button onClick={() => handleDeleteFamily(family.id)} className="flex items-center justify-center gap-2 py-2 text-red-600 bg-red-50 rounded-lg text-sm font-bold">
|
|
<Trash2 className="w-4 h-4" /> Elimina
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{isAdmin && activeTab === 'users' && (
|
|
<div className="space-y-4 animate-fade-in">
|
|
<div className="flex justify-end">
|
|
<button
|
|
onClick={openAddUserModal}
|
|
className="w-full md:w-auto flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-5 py-2.5 rounded-lg font-medium shadow-sm transition-colors"
|
|
>
|
|
<Plus className="w-5 h-5" />
|
|
Nuovo Utente
|
|
</button>
|
|
</div>
|
|
|
|
{/* Desktop Table */}
|
|
<div className="hidden md:block bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
<table className="w-full text-left text-sm text-slate-600">
|
|
<thead className="bg-slate-50 text-slate-700 font-semibold border-b border-slate-200">
|
|
<tr>
|
|
<th className="px-6 py-4">Utente</th>
|
|
<th className="px-6 py-4">Contatti</th>
|
|
<th className="px-6 py-4">Ruolo</th>
|
|
<th className="px-6 py-4">Alerts</th>
|
|
<th className="px-6 py-4 text-right">Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-100">
|
|
{users.map(user => (
|
|
<tr key={user.id} className="hover:bg-slate-50 transition-colors">
|
|
<td className="px-6 py-4 font-medium text-slate-900">{user.name || '-'}</td>
|
|
<td className="px-6 py-4">
|
|
<div className="font-medium">{user.email}</div>
|
|
{user.phone && <div className="text-xs text-slate-400 mt-0.5">{user.phone}</div>}
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span className={`px-2 py-1 rounded-full text-xs font-bold uppercase
|
|
${user.role === 'admin' ? 'bg-purple-100 text-purple-700' : ''}
|
|
${user.role === 'poweruser' ? 'bg-orange-100 text-orange-700' : ''}
|
|
${user.role === 'user' ? 'bg-green-100 text-green-700' : ''}
|
|
`}>
|
|
{user.role}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
{user.receiveAlerts ? (
|
|
<span className="text-green-600 flex items-center gap-1"><Bell className="w-3 h-3"/> Sì</span>
|
|
) : (
|
|
<span className="text-slate-400 flex items-center gap-1"><Bell className="w-3 h-3"/> No</span>
|
|
)}
|
|
</td>
|
|
<td className="px-6 py-4 text-right">
|
|
<div className="flex items-center justify-end gap-2">
|
|
<button onClick={() => openEditUserModal(user)} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg"><Pencil className="w-4 h-4" /></button>
|
|
<button onClick={() => handleDeleteUser(user.id)} className="p-2 text-red-600 hover:bg-red-50 rounded-lg"><Trash2 className="w-4 h-4" /></button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/* Mobile User Cards */}
|
|
<div className="md:hidden space-y-3">
|
|
{users.map(user => (
|
|
<div key={user.id} className="bg-white p-5 rounded-xl border border-slate-200 shadow-sm relative overflow-hidden">
|
|
<div className="absolute top-0 right-0 p-4">
|
|
<span className={`px-2 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider
|
|
${user.role === 'admin' ? 'bg-purple-100 text-purple-700' : ''}
|
|
${user.role === 'poweruser' ? 'bg-orange-100 text-orange-700' : ''}
|
|
${user.role === 'user' ? 'bg-green-100 text-green-700' : ''}
|
|
`}>
|
|
{user.role}
|
|
</span>
|
|
</div>
|
|
<div className="mb-3 pr-16">
|
|
<h4 className="font-bold text-slate-800 text-lg flex items-center gap-2">
|
|
<UserIcon className="w-4 h-4 text-slate-400"/>
|
|
{user.name || 'Senza Nome'}
|
|
</h4>
|
|
<p className="text-sm text-slate-500 mt-1">{user.email}</p>
|
|
</div>
|
|
<div className="flex gap-2 mb-2">
|
|
{user.receiveAlerts ? (
|
|
<span className="text-xs bg-green-50 text-green-600 px-2 py-1 rounded border border-green-100 flex items-center gap-1">
|
|
<Bell className="w-3 h-3"/> Riceve Avvisi
|
|
</span>
|
|
) : (
|
|
<span className="text-xs bg-slate-50 text-slate-400 px-2 py-1 rounded border border-slate-100 flex items-center gap-1">
|
|
<Bell className="w-3 h-3"/> Niente Avvisi
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3 mt-3">
|
|
<button onClick={() => openEditUserModal(user)} className="flex items-center justify-center gap-2 py-2 text-blue-600 bg-blue-50 rounded-lg text-sm font-bold">
|
|
<Pencil className="w-4 h-4" /> Modifica
|
|
</button>
|
|
<button onClick={() => handleDeleteUser(user.id)} className="flex items-center justify-center gap-2 py-2 text-red-600 bg-red-50 rounded-lg text-sm font-bold">
|
|
<Trash2 className="w-4 h-4" /> Elimina
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{isAdmin && activeTab === 'smtp' && (
|
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 md:p-8 max-w-2xl animate-fade-in">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="p-2 bg-blue-50 text-blue-600 rounded-lg">
|
|
<Server className="w-6 h-6" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-bold text-slate-800">Server SMTP</h3>
|
|
<p className="text-sm text-slate-500">Configura il server per l'invio delle email.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSettingsSubmit} className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Host SMTP</label>
|
|
<input
|
|
type="text"
|
|
placeholder="es. smtp.gmail.com"
|
|
value={settings.smtpConfig?.host || ''}
|
|
onChange={(e) => setSettings({...settings, smtpConfig: {...settings.smtpConfig!, host: e.target.value}})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Porta</label>
|
|
<input
|
|
type="number"
|
|
placeholder="587"
|
|
value={settings.smtpConfig?.port}
|
|
onChange={(e) => setSettings({...settings, smtpConfig: {...settings.smtpConfig!, port: parseInt(e.target.value)}})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Utente</label>
|
|
<input
|
|
type="text"
|
|
value={settings.smtpConfig?.user || ''}
|
|
onChange={(e) => setSettings({...settings, smtpConfig: {...settings.smtpConfig!, user: e.target.value}})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
|
<input
|
|
type="password"
|
|
value={settings.smtpConfig?.pass || ''}
|
|
onChange={(e) => setSettings({...settings, smtpConfig: {...settings.smtpConfig!, pass: e.target.value}})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Email Mittente</label>
|
|
<input
|
|
type="email"
|
|
placeholder="noreply@condominio.it"
|
|
value={settings.smtpConfig?.fromEmail || ''}
|
|
onChange={(e) => setSettings({...settings, smtpConfig: {...settings.smtpConfig!, fromEmail: e.target.value}})}
|
|
className="w-full border border-slate-300 rounded-lg p-2.5 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 mt-2">
|
|
<input
|
|
type="checkbox"
|
|
id="secure"
|
|
checked={settings.smtpConfig?.secure}
|
|
onChange={(e) => setSettings({...settings, smtpConfig: {...settings.smtpConfig!, secure: e.target.checked}})}
|
|
className="w-4 h-4 text-blue-600 rounded border-slate-300 focus:ring-blue-500"
|
|
/>
|
|
<label htmlFor="secure" className="text-sm text-slate-700">Usa connessione sicura (SSL/TLS)</label>
|
|
</div>
|
|
|
|
<div className="pt-4 flex items-center justify-between">
|
|
<span className={`text-sm font-medium ${successMsg ? 'text-green-600' : 'text-transparent'}`}>{successMsg || 'Salvataggio...'}</span>
|
|
<button type="submit" disabled={saving} className="bg-blue-600 text-white px-6 py-2.5 rounded-lg font-medium hover:bg-blue-700 transition-all flex items-center gap-2 disabled:opacity-70">
|
|
<Save className="w-4 h-4" /> Salva Configurazione
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
)}
|
|
|
|
{isAdmin && activeTab === 'alerts' && (
|
|
<div className="space-y-4 animate-fade-in">
|
|
<div className="flex justify-between items-end">
|
|
<div>
|
|
<h3 className="text-lg font-bold text-slate-800">Avvisi Automatici</h3>
|
|
<p className="text-sm text-slate-500">Pianifica email ricorrenti per i condomini.</p>
|
|
</div>
|
|
<button
|
|
onClick={openAddAlertModal}
|
|
className="flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-5 py-2.5 rounded-lg font-medium shadow-sm transition-colors"
|
|
>
|
|
<Plus className="w-5 h-5" />
|
|
Nuovo Avviso
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid gap-4">
|
|
{alerts.length === 0 && (
|
|
<div className="text-center p-8 bg-white rounded-xl border border-slate-200 text-slate-400">
|
|
Nessun avviso configurato.
|
|
</div>
|
|
)}
|
|
{alerts.map(alert => (
|
|
<div key={alert.id} className={`bg-white p-5 rounded-xl border shadow-sm flex flex-col md:flex-row justify-between gap-4 ${alert.active ? 'border-slate-200' : 'border-slate-100 opacity-70'}`}>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-1">
|
|
<h4 className="font-bold text-slate-800">{alert.subject}</h4>
|
|
{!alert.active && <span className="bg-slate-100 text-slate-500 text-xs px-2 py-0.5 rounded font-bold">DISATTIVO</span>}
|
|
</div>
|
|
<p className="text-sm text-slate-500 line-clamp-2 mb-3">{alert.body}</p>
|
|
|
|
<div className="flex flex-wrap gap-3 text-xs font-medium text-slate-600">
|
|
<div className="flex items-center gap-1 bg-slate-100 px-2 py-1 rounded">
|
|
<Clock className="w-3.5 h-3.5" />
|
|
{alert.offsetType === 'before_next_month'
|
|
? `${alert.daysOffset} giorni prima del prossimo mese`
|
|
: `${alert.daysOffset} giorni dopo inizio mese corrente`
|
|
}
|
|
</div>
|
|
<div className="flex items-center gap-1 bg-slate-100 px-2 py-1 rounded">
|
|
<Bell className="w-3.5 h-3.5" />
|
|
Alle ore {alert.sendHour}:00
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 md:border-l md:pl-4 border-slate-100">
|
|
<button onClick={() => openEditAlertModal(alert)} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg"><Pencil className="w-5 h-5" /></button>
|
|
<button onClick={() => handleDeleteAlert(alert.id)} className="p-2 text-red-600 hover:bg-red-50 rounded-lg"><Trash2 className="w-5 h-5" /></button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Family Modal */}
|
|
{showFamilyModal && (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
|
<div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden animate-in fade-in zoom-in duration-200">
|
|
<div className="bg-slate-50 px-6 py-4 border-b border-slate-100 flex justify-between items-center">
|
|
<h3 className="font-bold text-lg text-slate-800">
|
|
{editingFamily ? 'Modifica Famiglia' : 'Nuova Famiglia'}
|
|
</h3>
|
|
<button onClick={() => setShowFamilyModal(false)} className="text-slate-400 hover:text-slate-600 p-1">
|
|
<X className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleFamilySubmit} className="p-6 space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Nome Famiglia</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={familyForm.name}
|
|
onChange={(e) => setFamilyForm({...familyForm, name: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
placeholder="Es. Famiglia Rossi"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Interno / Scala</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={familyForm.unitNumber}
|
|
onChange={(e) => setFamilyForm({...familyForm, unitNumber: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Email</label>
|
|
<input
|
|
type="email"
|
|
value={familyForm.contactEmail}
|
|
onChange={(e) => setFamilyForm({...familyForm, contactEmail: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<div className="pt-4 flex gap-3">
|
|
<button type="button" onClick={() => setShowFamilyModal(false)} className="flex-1 px-4 py-3 border border-slate-300 rounded-lg font-medium text-slate-700 hover:bg-slate-50">Annulla</button>
|
|
<button type="submit" className="flex-1 px-4 py-3 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700">Salva</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* User Modal */}
|
|
{showUserModal && (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
|
<div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden overflow-y-auto max-h-[90vh] animate-in fade-in zoom-in duration-200">
|
|
<div className="bg-slate-50 px-6 py-4 border-b border-slate-100 flex justify-between items-center sticky top-0">
|
|
<h3 className="font-bold text-lg text-slate-800">
|
|
{editingUser ? 'Modifica Utente' : 'Nuovo Utente'}
|
|
</h3>
|
|
<button onClick={() => setShowUserModal(false)} className="text-slate-400 hover:text-slate-600 p-1">
|
|
<X className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleUserSubmit} className="p-6 space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Nome / Username</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={userForm.name}
|
|
onChange={(e) => setUserForm({...userForm, name: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Email (Login)</label>
|
|
<input
|
|
type="email"
|
|
required
|
|
value={userForm.email}
|
|
onChange={(e) => setUserForm({...userForm, email: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Telefono</label>
|
|
<input
|
|
type="tel"
|
|
value={userForm.phone}
|
|
onChange={(e) => setUserForm({...userForm, phone: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Password</label>
|
|
<input
|
|
type="password"
|
|
required={!editingUser}
|
|
value={userForm.password}
|
|
onChange={(e) => setUserForm({...userForm, password: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
placeholder={editingUser ? "Lascia vuoto per non cambiare" : "Inserisci password"}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Ruolo</label>
|
|
<select
|
|
value={userForm.role}
|
|
onChange={(e) => setUserForm({...userForm, role: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none bg-white"
|
|
>
|
|
<option value="user">User</option>
|
|
<option value="poweruser">Poweruser</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Famiglia Collegata</label>
|
|
<select
|
|
value={userForm.familyId}
|
|
onChange={(e) => setUserForm({...userForm, familyId: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none bg-white"
|
|
>
|
|
<option value="">Nessuna</option>
|
|
{families.map(f => (
|
|
<option key={f.id} value={f.id}>{f.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3 pt-2 bg-slate-50 p-3 rounded-lg border border-slate-100">
|
|
<input
|
|
type="checkbox"
|
|
id="receiveAlerts"
|
|
checked={userForm.receiveAlerts}
|
|
onChange={(e) => setUserForm({...userForm, receiveAlerts: e.target.checked})}
|
|
className="w-5 h-5 text-blue-600 rounded border-slate-300 focus:ring-blue-500"
|
|
/>
|
|
<label htmlFor="receiveAlerts" className="text-sm font-medium text-slate-700 select-none cursor-pointer">
|
|
Ricevi avvisi email automatici
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-4 flex gap-3">
|
|
<button type="button" onClick={() => setShowUserModal(false)} className="flex-1 px-4 py-3 border border-slate-300 rounded-lg font-medium text-slate-700">Annulla</button>
|
|
<button type="submit" className="flex-1 px-4 py-3 bg-blue-600 text-white rounded-lg font-medium">Salva</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Alert Config Modal */}
|
|
{showAlertModal && (
|
|
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
|
<div className="bg-white rounded-2xl shadow-xl w-full max-w-lg overflow-hidden animate-in fade-in zoom-in duration-200">
|
|
<div className="bg-slate-50 px-6 py-4 border-b border-slate-100 flex justify-between items-center">
|
|
<h3 className="font-bold text-lg text-slate-800">
|
|
{editingAlert ? 'Modifica Avviso' : 'Nuovo Avviso'}
|
|
</h3>
|
|
<button onClick={() => setShowAlertModal(false)} className="text-slate-400 hover:text-slate-600 p-1">
|
|
<X className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleAlertSubmit} className="p-6 space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Oggetto Email</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={alertForm.subject}
|
|
onChange={(e) => setAlertForm({...alertForm, subject: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none"
|
|
placeholder="Es. Promemoria Scadenza Rata"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Corpo del Messaggio</label>
|
|
<textarea
|
|
required
|
|
rows={6}
|
|
value={alertForm.body}
|
|
onChange={(e) => setAlertForm({...alertForm, body: e.target.value})}
|
|
className="w-full border border-slate-300 rounded-lg p-3 focus:ring-2 focus:ring-blue-500 outline-none resize-none"
|
|
placeholder="Gentile condomino, si ricorda che..."
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<FileText className="w-4 h-4 text-slate-400" />
|
|
<span className="text-xs text-slate-500">Allegati: Funzionalità in arrivo.</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 bg-slate-50 p-4 rounded-lg border border-slate-200">
|
|
<div className="md:col-span-2">
|
|
<h4 className="font-bold text-sm text-slate-700 mb-2 flex items-center gap-2">
|
|
<Clock className="w-4 h-4" /> Schedulazione Invio
|
|
</h4>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-500 mb-1">Quando</label>
|
|
<select
|
|
value={alertForm.offsetType}
|
|
onChange={(e) => setAlertForm({...alertForm, offsetType: e.target.value as any})}
|
|
className="w-full border border-slate-300 rounded-lg p-2 text-sm bg-white"
|
|
>
|
|
<option value="before_next_month">Prima del mese successivo</option>
|
|
<option value="after_current_month">Dopo inizio mese corrente</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-500 mb-1">Giorni di differenza</label>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
max="30"
|
|
value={alertForm.daysOffset}
|
|
onChange={(e) => setAlertForm({...alertForm, daysOffset: parseInt(e.target.value)})}
|
|
className="w-full border border-slate-300 rounded-lg p-2 text-sm"
|
|
/>
|
|
<span className="text-xs text-slate-400">gg</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs font-semibold text-slate-500 mb-1">Orario Invio</label>
|
|
<select
|
|
value={alertForm.sendHour}
|
|
onChange={(e) => setAlertForm({...alertForm, sendHour: parseInt(e.target.value)})}
|
|
className="w-full border border-slate-300 rounded-lg p-2 text-sm bg-white"
|
|
>
|
|
{Array.from({length: 24}, (_, i) => (
|
|
<option key={i} value={i}>{i.toString().padStart(2, '0')}:00</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="flex items-end">
|
|
<div className="flex items-center gap-2 mb-2 w-full">
|
|
<input
|
|
type="checkbox"
|
|
id="alertActive"
|
|
checked={alertForm.active}
|
|
onChange={(e) => setAlertForm({...alertForm, active: e.target.checked})}
|
|
className="w-4 h-4 text-blue-600 rounded border-slate-300"
|
|
/>
|
|
<label htmlFor="alertActive" className="text-sm font-medium text-slate-700">Attivo</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-4 flex gap-3">
|
|
<button type="button" onClick={() => setShowAlertModal(false)} className="flex-1 px-4 py-3 border border-slate-300 rounded-lg font-medium text-slate-700">Annulla</button>
|
|
<button type="submit" className="flex-1 px-4 py-3 bg-blue-600 text-white rounded-lg font-medium">Salva Avviso</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}; |