Update Settings.tsx
This commit is contained in:
@@ -48,7 +48,8 @@ export const SettingsPage: React.FC = () => {
|
|||||||
zipCode: '',
|
zipCode: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
paypalClientId: '',
|
paypalClientId: '',
|
||||||
defaultMonthlyQuota: 100
|
defaultMonthlyQuota: 100,
|
||||||
|
dueDay: 10
|
||||||
});
|
});
|
||||||
|
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
@@ -302,13 +303,13 @@ export const SettingsPage: React.FC = () => {
|
|||||||
// CRUD Handlers
|
// CRUD Handlers
|
||||||
const openAddCondoModal = () => {
|
const openAddCondoModal = () => {
|
||||||
setEditingCondo(null);
|
setEditingCondo(null);
|
||||||
setCondoForm({ name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', paypalClientId: '', defaultMonthlyQuota: 100 });
|
setCondoForm({ name: '', address: '', streetNumber: '', city: '', province: '', zipCode: '', notes: '', paypalClientId: '', defaultMonthlyQuota: 100, dueDay: 10 });
|
||||||
setShowCondoModal(true);
|
setShowCondoModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openEditCondoModal = (c: Condo) => {
|
const openEditCondoModal = (c: Condo) => {
|
||||||
setEditingCondo(c);
|
setEditingCondo(c);
|
||||||
setCondoForm({ name: c.name, address: c.address || '', streetNumber: c.streetNumber || '', city: c.city || '', province: c.province || '', zipCode: c.zipCode || '', notes: c.notes || '', paypalClientId: c.paypalClientId || '', defaultMonthlyQuota: c.defaultMonthlyQuota });
|
setCondoForm({ name: c.name, address: c.address || '', streetNumber: c.streetNumber || '', city: c.city || '', province: c.province || '', zipCode: c.zipCode || '', notes: c.notes || '', paypalClientId: c.paypalClientId || '', defaultMonthlyQuota: c.defaultMonthlyQuota, dueDay: c.dueDay || 10 });
|
||||||
setShowCondoModal(true);
|
setShowCondoModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -325,7 +326,8 @@ export const SettingsPage: React.FC = () => {
|
|||||||
zipCode: condoForm.zipCode,
|
zipCode: condoForm.zipCode,
|
||||||
notes: condoForm.notes,
|
notes: condoForm.notes,
|
||||||
paypalClientId: condoForm.paypalClientId,
|
paypalClientId: condoForm.paypalClientId,
|
||||||
defaultMonthlyQuota: condoForm.defaultMonthlyQuota
|
defaultMonthlyQuota: condoForm.defaultMonthlyQuota,
|
||||||
|
dueDay: condoForm.dueDay
|
||||||
};
|
};
|
||||||
const savedCondo = await CondoService.saveCondo(payload);
|
const savedCondo = await CondoService.saveCondo(payload);
|
||||||
const list = await CondoService.getCondos();
|
const list = await CondoService.getCondos();
|
||||||
@@ -693,7 +695,18 @@ export const SettingsPage: React.FC = () => {
|
|||||||
<div className="grid grid-cols-2 gap-4"><div><label className="block text-xs font-bold text-slate-500 uppercase mb-1">Città</label><input type="text" value={activeCondo.city || ''} onChange={(e) => setActiveCondo({ ...activeCondo, city: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700" required/></div><div><label className="block text-xs font-bold text-slate-500 uppercase mb-1">Provincia</label><input type="text" value={activeCondo.province || ''} onChange={(e) => setActiveCondo({ ...activeCondo, province: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700" required/></div></div>
|
<div className="grid grid-cols-2 gap-4"><div><label className="block text-xs font-bold text-slate-500 uppercase mb-1">Città</label><input type="text" value={activeCondo.city || ''} onChange={(e) => setActiveCondo({ ...activeCondo, city: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700" required/></div><div><label className="block text-xs font-bold text-slate-500 uppercase mb-1">Provincia</label><input type="text" value={activeCondo.province || ''} onChange={(e) => setActiveCondo({ ...activeCondo, province: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700" required/></div></div>
|
||||||
<div><label className="block text-xs font-bold text-slate-500 uppercase mb-1">Note (Opzionali)</label><textarea value={activeCondo.notes || ''} onChange={(e) => setActiveCondo({ ...activeCondo, notes: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700 h-24"></textarea></div>
|
<div><label className="block text-xs font-bold text-slate-500 uppercase mb-1">Note (Opzionali)</label><textarea value={activeCondo.notes || ''} onChange={(e) => setActiveCondo({ ...activeCondo, notes: e.target.value })} className="w-full border p-2.5 rounded-lg text-slate-700 h-24"></textarea></div>
|
||||||
{globalSettings?.features.payPal && (<div className="bg-blue-50 p-4 rounded-lg border border-blue-100"><div className="flex items-center gap-2 mb-2 text-blue-800"><CreditCard className="w-4 h-4"/><span className="text-xs font-bold uppercase">Configurazione Pagamenti</span></div><div><label className="text-xs font-semibold text-slate-600 block mb-1">PayPal Client ID</label><input className="w-full border p-2 rounded text-slate-700 text-sm" placeholder="Es: Afg..." value={activeCondo.paypalClientId || ''} onChange={e => setActiveCondo({...activeCondo, paypalClientId: e.target.value})} /><p className="text-[10px] text-slate-500 mt-1">Necessario per abilitare i pagamenti online delle rate.</p></div></div>)}
|
{globalSettings?.features.payPal && (<div className="bg-blue-50 p-4 rounded-lg border border-blue-100"><div className="flex items-center gap-2 mb-2 text-blue-800"><CreditCard className="w-4 h-4"/><span className="text-xs font-bold uppercase">Configurazione Pagamenti</span></div><div><label className="text-xs font-semibold text-slate-600 block mb-1">PayPal Client ID</label><input className="w-full border p-2 rounded text-slate-700 text-sm" placeholder="Es: Afg..." value={activeCondo.paypalClientId || ''} onChange={e => setActiveCondo({...activeCondo, paypalClientId: e.target.value})} /><p className="text-[10px] text-slate-500 mt-1">Necessario per abilitare i pagamenti online delle rate.</p></div></div>)}
|
||||||
<div><label className="block text-sm font-medium text-slate-700 mb-1">Quota Mensile Standard (€)</label><input type="number" value={activeCondo.defaultMonthlyQuota} onChange={(e) => setActiveCondo({ ...activeCondo, defaultMonthlyQuota: parseFloat(e.target.value) })} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Quota Default" required /></div>
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="block text-sm font-medium text-slate-700 mb-1">Quota Mensile (€)</label>
|
||||||
|
<input type="number" value={activeCondo.defaultMonthlyQuota} onChange={(e) => setActiveCondo({ ...activeCondo, defaultMonthlyQuota: parseFloat(e.target.value) })} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Quota Default" required />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="block text-sm font-medium text-slate-700 mb-1">Giorno Scadenza (1-31)</label>
|
||||||
|
<input type="number" min="1" max="31" value={activeCondo.dueDay || 10} onChange={(e) => setActiveCondo({ ...activeCondo, dueDay: parseInt(e.target.value) })} className="w-full border p-2.5 rounded-lg text-slate-700" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="pt-2 flex justify-between"><span className="text-green-600">{successMsg}</span><button type="submit" className="bg-blue-600 text-white px-6 py-2.5 rounded-lg hover:bg-blue-700 flex gap-2"><Save className="w-4 h-4"/> Salva</button></div>
|
<div className="pt-2 flex justify-between"><span className="text-green-600">{successMsg}</span><button type="submit" className="bg-blue-600 text-white px-6 py-2.5 rounded-lg hover:bg-blue-700 flex gap-2"><Save className="w-4 h-4"/> Salva</button></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -722,37 +735,35 @@ export const SettingsPage: React.FC = () => {
|
|||||||
{/* FAMILIES TAB */}
|
{/* FAMILIES TAB */}
|
||||||
{isPrivileged && activeTab === 'families' && (
|
{isPrivileged && activeTab === 'families' && (
|
||||||
<div className="space-y-4 animate-fade-in">
|
<div className="space-y-4 animate-fade-in">
|
||||||
{!activeCondo ? (<div className="p-8 text-center bg-slate-50 rounded-xl border border-dashed border-slate-300"><p className="text-slate-500">Seleziona o crea un condominio per gestire le famiglie.</p></div>) : (
|
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100"><div><h3 className="font-bold text-blue-800">Elenco Famiglie</h3><p className="text-xs text-blue-600">Condominio: {activeCondo?.name}</p></div><button onClick={openAddFamilyModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex gap-2"><Plus className="w-4 h-4" /> Aggiungi</button></div>
|
||||||
<>
|
|
||||||
<div className="flex justify-between items-center"><div className="text-sm text-slate-500">Famiglie in: <span className="font-bold text-slate-800">{activeCondo.name}</span></div><button onClick={openAddFamilyModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2"><Plus className="w-4 h-4" /> Aggiungi</button></div>
|
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<div className="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"><tr><th className="px-6 py-4">Nome</th><th className="px-6 py-4">Dettagli</th><th className="px-6 py-4">Email</th><th className="px-6 py-4">Quota</th><th className="px-6 py-4 text-right">Azioni</th></tr></thead>
|
<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-4 py-3">Nominativo</th><th className="px-4 py-3">Interno</th><th className="px-4 py-3 hidden md:table-cell">Email</th><th className="px-4 py-3 text-right">Azioni</th></tr></thead>
|
||||||
<tbody className="divide-y divide-slate-100">
|
<tbody className="divide-y divide-slate-100">
|
||||||
{families.map(family => (
|
{families.map(f => (
|
||||||
<tr key={family.id} className="hover:bg-slate-50">
|
<tr key={f.id} className="hover:bg-slate-50"><td className="px-4 py-3 font-medium text-slate-800">{f.name}</td><td className="px-4 py-3">{f.unitNumber}</td><td className="px-4 py-3 hidden md:table-cell text-slate-500">{f.contactEmail}</td><td className="px-4 py-3 text-right"><button onClick={() => openEditFamilyModal(f)} className="text-blue-600 hover:underline mr-3">Modifica</button><button onClick={() => handleDeleteFamily(f.id)} className="text-red-600 hover:underline">Elimina</button></td></tr>
|
||||||
<td className="px-6 py-4 font-medium text-slate-900">{family.name}</td>
|
|
||||||
<td className="px-6 py-4 text-xs"><div className="space-y-0.5"><span className="block text-slate-700 font-medium">Int: {family.unitNumber || '-'}</span>{(family.stair || family.floor) && (<span className="block text-slate-500">{family.stair ? `Scala: ${family.stair} ` : ''} {family.floor ? `Piano: ${family.floor}` : ''}</span>)}</div></td>
|
|
||||||
<td className="px-6 py-4 text-slate-400">{family.contactEmail}</td>
|
|
||||||
<td className="px-6 py-4">{family.customMonthlyQuota ? (<span className="font-bold text-blue-600">€ {family.customMonthlyQuota}</span>) : (<span className="text-slate-400 italic">Default (€ {activeCondo.defaultMonthlyQuota})</span>)}</td>
|
|
||||||
<td className="px-6 py-4 text-right"><div className="flex justify-end gap-2"><button onClick={() => openEditFamilyModal(family)} className="text-blue-600"><Pencil className="w-4 h-4" /></button><button onClick={() => handleDeleteFamily(family.id)} className="text-red-600"><Trash2 className="w-4 h-4" /></button></div></td>
|
|
||||||
</tr>
|
|
||||||
))}
|
))}
|
||||||
|
{families.length === 0 && <tr><td colSpan={4} className="p-8 text-center text-slate-400">Nessuna famiglia registrata.</td></tr>}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* USERS TAB */}
|
{/* USERS TAB */}
|
||||||
{isPrivileged && activeTab === 'users' && (
|
{isPrivileged && activeTab === 'users' && (
|
||||||
<div className="space-y-4 animate-fade-in">
|
<div className="space-y-4 animate-fade-in">
|
||||||
<div className="flex justify-end"><button onClick={openAddUserModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 hover:bg-blue-700 transition-colors" type="button"><Plus className="w-4 h-4" /> Nuovo Utente</button></div>
|
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100"><div><h3 className="font-bold text-blue-800">Utenti & Accessi</h3><p className="text-xs text-blue-600">Gestione account di accesso</p></div><button onClick={openAddUserModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex gap-2"><Plus className="w-4 h-4" /> Aggiungi</button></div>
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<div className="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"><tr><th className="px-6 py-4">Utente</th><th className="px-6 py-4">Ruolo</th><th className="px-6 py-4 text-right">Azioni</th></tr></thead>
|
<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-4 py-3">Email</th><th className="px-4 py-3">Ruolo</th><th className="px-4 py-3 hidden md:table-cell">Famiglia</th><th className="px-4 py-3 text-right">Azioni</th></tr></thead>
|
||||||
<tbody className="divide-y divide-slate-100">
|
<tbody className="divide-y divide-slate-100">
|
||||||
{users.map(u => (<tr key={u.id} className="hover:bg-slate-50"><td className="px-6 py-4"><div className="font-medium text-slate-900">{u.name}</div><div className="text-xs text-slate-400">{u.email}</div></td><td className="px-6 py-4"><span className="bg-slate-100 px-2 py-1 rounded text-xs font-bold uppercase">{u.role}</span></td><td className="px-6 py-4 text-right"><div className="flex justify-end gap-2"><button onClick={() => openEditUserModal(u)} className="text-blue-600"><Pencil className="w-4 h-4"/></button><button onClick={() => handleDeleteUser(u.id)} className="text-red-600"><Trash2 className="w-4 h-4"/></button></div></td></tr>))}
|
{users.map(u => {
|
||||||
|
const fam = families.find(f => f.id === u.familyId);
|
||||||
|
return (
|
||||||
|
<tr key={u.id} className="hover:bg-slate-50"><td className="px-4 py-3 font-medium text-slate-800">{u.email}</td><td className="px-4 py-3"><span className={`px-2 py-1 rounded text-xs font-bold uppercase ${u.role === 'admin' ? 'bg-purple-100 text-purple-700' : u.role === 'poweruser' ? 'bg-orange-100 text-orange-700' : 'bg-slate-100 text-slate-600'}`}>{u.role}</span></td><td className="px-4 py-3 hidden md:table-cell">{fam ? `${fam.name} (${fam.unitNumber})` : '-'}</td><td className="px-4 py-3 text-right"><button onClick={() => openEditUserModal(u)} className="text-blue-600 hover:underline mr-3">Modifica</button><button onClick={() => handleDeleteUser(u.id)} className="text-red-600 hover:underline">Elimina</button></td></tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -762,39 +773,63 @@ export const SettingsPage: React.FC = () => {
|
|||||||
{/* NOTICES TAB */}
|
{/* NOTICES TAB */}
|
||||||
{isPrivileged && activeTab === 'notices' && (
|
{isPrivileged && activeTab === 'notices' && (
|
||||||
<div className="space-y-4 animate-fade-in">
|
<div className="space-y-4 animate-fade-in">
|
||||||
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100"><div><h3 className="font-bold text-blue-800">Bacheca Condominiale</h3><p className="text-sm text-blue-600">Pubblica avvisi visibili a tutti i condomini.</p></div><button onClick={openAddNoticeModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 hover:bg-blue-700 transition-colors"><Plus className="w-4 h-4" /> Nuovo Avviso</button></div>
|
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100"><div><h3 className="font-bold text-blue-800">Bacheca Avvisi</h3></div><button onClick={openAddNoticeModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex gap-2"><Plus className="w-4 h-4" /> Nuovo Avviso</button></div>
|
||||||
<div className="grid gap-4">
|
<div className="space-y-3">
|
||||||
{notices.map(notice => {
|
{notices.map(notice => {
|
||||||
const isTargeted = notice.targetFamilyIds && notice.targetFamilyIds.length > 0;
|
const reads = noticeReadStats[notice.id] || [];
|
||||||
|
const targetCount = notice.targetFamilyIds?.length || 0;
|
||||||
return (
|
return (
|
||||||
<div key={notice.id} className={`bg-white p-5 rounded-xl border shadow-sm relative transition-all ${notice.active ? 'border-slate-200' : 'border-slate-100 opacity-60 bg-slate-50'}`}>
|
<div key={notice.id} className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex flex-col md:flex-row justify-between gap-4">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex-1">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<div className={`p-2 rounded-lg ${notice.type === 'warning' ? 'bg-amber-100 text-amber-600' : notice.type === 'maintenance' ? 'bg-orange-100 text-orange-600' : 'bg-blue-100 text-blue-600'}`}>{notice.type === 'warning' ? <AlertTriangle className="w-5 h-5"/> : notice.type === 'maintenance' ? <Hammer className="w-5 h-5"/> : notice.type === 'event' ? <Calendar className="w-5 h-5"/> : <Info className="w-5 h-5"/>}</div>
|
<h4 className="font-bold text-slate-800">{notice.title}</h4>
|
||||||
<div><h4 className="font-bold text-slate-800 flex items-center gap-2">{notice.title}{isTargeted && (<span className="text-[10px] bg-slate-100 text-slate-500 border border-slate-200 px-2 py-0.5 rounded-full uppercase flex items-center gap-1"><Users className="w-3 h-3" /> Privato</span>)}</h4><p className="text-xs text-slate-400 font-medium uppercase tracking-wide mb-1">{getCondoName(notice.condoId)} • {new Date(notice.date).toLocaleDateString()}</p><p className="text-sm text-slate-600 line-clamp-2">{notice.content}</p>{notice.link && <a href={notice.link} className="text-xs text-blue-600 underline mt-1 flex items-center gap-1"><LinkIcon className="w-3 h-3"/> Allegato</a>}{isTargeted && (<p className="text-[10px] text-slate-400 mt-2">Visibile a: {notice.targetFamilyIds!.length} famiglie</p>)}</div>
|
<span className={`px-2 py-0.5 text-[10px] font-bold uppercase rounded ${notice.active ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-500'}`}>{notice.active ? 'Attivo' : 'Archiviato'}</span>
|
||||||
|
<span className="text-xs text-slate-400">• {new Date(notice.date).toLocaleDateString()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-end gap-3"><button onClick={() => toggleNoticeActive(notice)} className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${notice.active ? 'bg-green-500' : 'bg-slate-300'}`} title={notice.active ? "Attivo" : "Disattivato"}><span className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${notice.active ? 'translate-x-6' : 'translate-x-1'}`} /></button><button onClick={() => openReadDetails(notice.id)} className="text-center group" title="Vedi dettaglio letture"><span className="block text-lg font-bold text-slate-700 group-hover:text-blue-600">{noticeReadStats[notice.id]?.length || 0}</span><span className="text-[10px] text-slate-400 uppercase font-bold flex items-center gap-1 group-hover:text-blue-500"><Eye className="w-3 h-3"/> Letture</span></button></div>
|
<p className="text-sm text-slate-600 line-clamp-1">{notice.content}</p>
|
||||||
|
<div className="mt-2 flex gap-3 text-xs">
|
||||||
|
<span className="text-blue-600 font-medium bg-blue-50 px-2 py-0.5 rounded">
|
||||||
|
Destinatari: {targetCount === 0 ? 'Tutti' : `${targetCount} Famiglie`}
|
||||||
|
</span>
|
||||||
|
<button onClick={() => openReadDetails(notice.id)} className="text-slate-500 hover:text-blue-600 flex items-center gap-1">
|
||||||
|
<Eye className="w-3 h-3"/> Letto da: <strong>{reads.length}</strong>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button onClick={() => toggleNoticeActive(notice)} className="p-2 text-slate-500 hover:bg-slate-100 rounded" title={notice.active ? "Archivia" : "Attiva"}><Power className="w-4 h-4"/></button>
|
||||||
|
<button onClick={() => openEditNoticeModal(notice)} className="p-2 text-blue-600 hover:bg-blue-50 rounded" title="Modifica"><Pencil className="w-4 h-4"/></button>
|
||||||
|
<button onClick={() => handleDeleteNotice(notice.id)} className="p-2 text-red-600 hover:bg-red-50 rounded" title="Elimina"><Trash2 className="w-4 h-4"/></button>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 pt-3 border-t border-slate-100 flex justify-end gap-2"><button onClick={() => openEditNoticeModal(notice)} className="text-sm text-blue-600 font-medium px-3 py-1 hover:bg-blue-50 rounded">Modifica</button><button onClick={() => handleDeleteNotice(notice.id)} className="text-sm text-red-600 font-medium px-3 py-1 hover:bg-red-50 rounded">Elimina</button></div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{notices.length === 0 && <div className="text-center p-8 text-slate-400">Nessun avviso pubblicato.</div>}
|
{notices.length === 0 && <div className="text-center p-8 text-slate-400">Nessun avviso in bacheca.</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ALERTS TAB */}
|
{/* ALERTS TAB */}
|
||||||
{isPrivileged && activeTab === 'alerts' && (
|
{isPrivileged && activeTab === 'alerts' && (
|
||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-4 animate-fade-in">
|
||||||
<div className="flex justify-end"><button onClick={() => setShowSmtpModal(true)} className="flex items-center gap-2 bg-slate-100 hover:bg-slate-200 text-slate-700 px-4 py-2 rounded-lg font-medium border border-slate-200 transition-colors"><Mail className="w-4 h-4" />Impostazioni SMTP</button></div>
|
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100">
|
||||||
<div className="flex justify-between items-center bg-blue-50 p-4 rounded-xl border border-blue-100"><div><h3 className="font-bold text-blue-800">Avvisi Automatici</h3><p className="text-sm text-blue-600">Configura email automatiche per scadenze.</p></div><button onClick={openAddAlertModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2"><Plus className="w-4 h-4" /> Nuovo Avviso</button></div>
|
<div><h3 className="font-bold text-blue-800">Avvisi Automatici Email</h3><p className="text-xs text-blue-600">Configura email periodiche</p></div>
|
||||||
<div className="grid gap-4">
|
<div className="flex gap-2">
|
||||||
|
<button onClick={() => setShowSmtpModal(true)} className="bg-white text-blue-600 border border-blue-200 px-4 py-2 rounded-lg font-medium hover:bg-blue-50 flex gap-2"><Server className="w-4 h-4"/> Configura SMTP</button>
|
||||||
|
<button onClick={openAddAlertModal} className="bg-blue-600 text-white px-4 py-2 rounded-lg font-medium flex gap-2"><Plus className="w-4 h-4" /> Nuovo Alert</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
{alerts.map(alert => (
|
{alerts.map(alert => (
|
||||||
<div key={alert.id} className="bg-white p-5 rounded-xl border border-slate-200 shadow-sm relative">
|
<div key={alert.id} className="bg-white p-4 rounded-xl border border-slate-200 shadow-sm flex justify-between items-center">
|
||||||
<h4 className="font-bold text-slate-800">{alert.subject}</h4><p className="text-sm text-slate-600 mt-1">{alert.body}</p>
|
<div>
|
||||||
<div className="mt-3 flex gap-2 text-xs font-bold text-slate-500 uppercase"><span className="bg-slate-100 px-2 py-1 rounded">Offset: {alert.daysOffset} giorni ({alert.offsetType})</span><span className="bg-slate-100 px-2 py-1 rounded">Ore: {alert.sendHour}:00</span><span className={`px-2 py-1 rounded ${alert.active ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>{alert.active ? 'Attivo' : 'Inattivo'}</span></div>
|
<h4 className="font-bold text-slate-800">{alert.subject}</h4>
|
||||||
<div className="mt-4 pt-3 border-t border-slate-100 flex justify-end gap-2"><button onClick={() => openEditAlertModal(alert)} className="text-blue-600 text-sm font-medium">Modifica</button><button onClick={() => handleDeleteAlert(alert.id)} className="text-red-600 text-sm font-medium">Elimina</button></div>
|
<p className="text-sm text-slate-500">Invia {alert.daysOffset} giorni {alert.offsetType === 'before_next_month' ? 'prima del prossimo mese' : 'dopo il mese corrente'} alle {alert.sendHour}:00</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button onClick={() => openEditAlertModal(alert)} className="text-blue-600 hover:underline text-sm">Modifica</button>
|
||||||
|
<button onClick={() => handleDeleteAlert(alert.id)} className="text-red-600 hover:underline text-sm">Elimina</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{alerts.length === 0 && <div className="text-center p-8 text-slate-400">Nessun alert configurato.</div>}
|
{alerts.length === 0 && <div className="text-center p-8 text-slate-400">Nessun alert configurato.</div>}
|
||||||
@@ -802,157 +837,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* --- MODALS --- */}
|
{/* Condo Modal (Aggiornato con dueDay) */}
|
||||||
|
|
||||||
{/* Notice Modal */}
|
|
||||||
{showNoticeModal && (
|
|
||||||
<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-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200 flex flex-col max-h-[90vh]">
|
|
||||||
<h3 className="font-bold text-lg mb-4 text-slate-800 flex-shrink-0">{editingNotice ? 'Modifica Avviso' : 'Nuovo Avviso'}</h3>
|
|
||||||
<div className="overflow-y-auto flex-1 pr-2">
|
|
||||||
<form id="noticeForm" onSubmit={handleNoticeSubmit} className="space-y-4">
|
|
||||||
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Titolo" value={noticeForm.title} onChange={e => setNoticeForm({...noticeForm, title: e.target.value})} required />
|
|
||||||
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-24" placeholder="Contenuto avviso..." value={noticeForm.content} onChange={e => setNoticeForm({...noticeForm, content: e.target.value})} required />
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase mb-1 block">Tipo</label>
|
|
||||||
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={noticeForm.type} onChange={e => setNoticeForm({...noticeForm, type: e.target.value as any})}>
|
|
||||||
<option value="info">Info</option>
|
|
||||||
<option value="warning">Avviso</option>
|
|
||||||
<option value="maintenance">Manutenzione</option>
|
|
||||||
<option value="event">Evento</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase mb-1 block">Stato</label>
|
|
||||||
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={noticeForm.active ? 'true' : 'false'} onChange={e => setNoticeForm({...noticeForm, active: e.target.value === 'true'})}>
|
|
||||||
<option value="true">Attivo</option>
|
|
||||||
<option value="false">Bozza / Nascosto</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase mb-2 block">A chi è rivolto?</label>
|
|
||||||
<div className="flex gap-4 mb-3">
|
|
||||||
<label className="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
|
|
||||||
<input type="radio" name="targetMode" value="all" checked={noticeTargetMode === 'all'} onChange={() => setNoticeTargetMode('all')} className="w-4 h-4 text-blue-600"/>
|
|
||||||
Tutti i condomini
|
|
||||||
</label>
|
|
||||||
<label className="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
|
|
||||||
<input type="radio" name="targetMode" value="specific" checked={noticeTargetMode === 'specific'} onChange={() => setNoticeTargetMode('specific')} className="w-4 h-4 text-blue-600"/>
|
|
||||||
Seleziona Famiglie
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{noticeTargetMode === 'specific' && (
|
|
||||||
<div className="border border-slate-200 rounded-lg p-2 max-h-40 overflow-y-auto bg-slate-50">
|
|
||||||
{families.length === 0 ? (
|
|
||||||
<p className="text-xs text-slate-400 text-center py-2">Nessuna famiglia disponibile.</p>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-1">
|
|
||||||
{families.map(fam => (
|
|
||||||
<label key={fam.id} className="flex items-center gap-2 p-1.5 hover:bg-white rounded cursor-pointer transition-colors">
|
|
||||||
<input type="checkbox" checked={noticeForm.targetFamilyIds.includes(fam.id)} onChange={() => toggleNoticeFamilyTarget(fam.id)} className="w-4 h-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500"/>
|
|
||||||
<span className="text-sm text-slate-700">{fam.name} <span className="text-slate-400 text-xs">(Int. {fam.unitNumber})</span></span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase mb-1 block">Link Esterno (Opzionale)</label>
|
|
||||||
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="https://..." value={noticeForm.link} onChange={e => setNoticeForm({...noticeForm, link: e.target.value})} />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 pt-4 border-t border-slate-100 flex-shrink-0">
|
|
||||||
<button type="button" onClick={() => setShowNoticeModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600 hover:bg-slate-50">Annulla</button>
|
|
||||||
<button type="submit" form="noticeForm" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg hover:bg-blue-700">Salva</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Notice Read Details Modal */}
|
|
||||||
{showReadDetailsModal && selectedNoticeId && (
|
|
||||||
<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-xl shadow-xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h3 className="font-bold text-lg text-slate-800">Dettaglio Letture</h3>
|
|
||||||
<button onClick={() => setShowReadDetailsModal(false)} className="p-1 rounded hover:bg-slate-100 text-slate-500"><X className="w-5 h-5"/></button>
|
|
||||||
</div>
|
|
||||||
<div className="max-h-64 overflow-y-auto pr-2">
|
|
||||||
{noticeReadStats[selectedNoticeId] && noticeReadStats[selectedNoticeId].length > 0 ? (
|
|
||||||
<div className="space-y-3">
|
|
||||||
{noticeReadStats[selectedNoticeId].map((read, idx) => {
|
|
||||||
const user = users.find(u => u.id === read.userId);
|
|
||||||
return (
|
|
||||||
<div key={idx} className="flex items-center justify-between p-3 bg-slate-50 rounded-lg border border-slate-100">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="bg-blue-100 p-2 rounded-full"><UserIcon className="w-4 h-4 text-blue-600"/></div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-bold text-slate-800">{user?.name || 'Utente Sconosciuto'}</p>
|
|
||||||
<p className="text-xs text-slate-500">{user?.email}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<p className="text-xs text-slate-500">{new Date(read.readAt).toLocaleDateString()}</p>
|
|
||||||
<p className="text-xs font-mono text-slate-400">{new Date(read.readAt).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="text-center text-slate-400 py-8">Nessuna lettura registrata.</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</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-xl shadow-xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
|
|
||||||
<h3 className="font-bold text-lg mb-4 text-slate-800">{editingUser ? 'Modifica Utente' : 'Nuovo Utente'}</h3>
|
|
||||||
<form onSubmit={handleUserSubmit} className="space-y-4">
|
|
||||||
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Nome Completo" value={userForm.name} onChange={e => setUserForm({...userForm, name: e.target.value})} required />
|
|
||||||
<input className="w-full border p-2.5 rounded-lg text-slate-700" type="email" placeholder="Email" value={userForm.email} onChange={e => setUserForm({...userForm, email: e.target.value})} required />
|
|
||||||
<input className="w-full border p-2.5 rounded-lg text-slate-700" type="tel" placeholder="Telefono" value={userForm.phone} onChange={e => setUserForm({...userForm, phone: e.target.value})} />
|
|
||||||
<input className="w-full border p-2.5 rounded-lg text-slate-700" type="password" placeholder={editingUser ? "Nuova Password (opzionale)" : "Password"} value={userForm.password} onChange={e => setUserForm({...userForm, password: e.target.value})} required={!editingUser} />
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase mb-1 block">Ruolo</label>
|
|
||||||
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={userForm.role} onChange={e => setUserForm({...userForm, role: e.target.value as any})}>
|
|
||||||
<option value="user">Utente</option>
|
|
||||||
<option value="admin">Admin</option>
|
|
||||||
<option value="poweruser">Power User</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase mb-1 block">Famiglia</label>
|
|
||||||
<select className="w-full border p-2.5 rounded-lg text-slate-700 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}</option>)}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<input type="checkbox" checked={userForm.receiveAlerts} onChange={e => setUserForm({...userForm, receiveAlerts: e.target.checked})} className="w-4 h-4 text-blue-600"/>
|
|
||||||
<span className="text-sm font-medium text-slate-700">Ricevi avvisi email</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2 pt-2">
|
|
||||||
<button type="button" onClick={() => setShowUserModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600 hover:bg-slate-50">Annulla</button>
|
|
||||||
<button type="submit" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg hover:bg-blue-700">Salva</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Condo Modal */}
|
|
||||||
{showCondoModal && (
|
{showCondoModal && (
|
||||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
<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-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200 overflow-y-auto max-h-[90vh]">
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200 overflow-y-auto max-h-[90vh]">
|
||||||
@@ -996,9 +881,15 @@ export const SettingsPage: React.FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-16" placeholder="Note (opzionali)" value={condoForm.notes} onChange={e => setCondoForm({...condoForm, notes: e.target.value})} />
|
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-16" placeholder="Note (opzionali)" value={condoForm.notes} onChange={e => setCondoForm({...condoForm, notes: e.target.value})} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex gap-4">
|
||||||
<span className="text-sm font-medium text-slate-600">Quota Default €</span>
|
<div className="flex-1">
|
||||||
<input type="number" className="border p-2 rounded w-24 text-slate-700" value={condoForm.defaultMonthlyQuota} onChange={e => setCondoForm({...condoForm, defaultMonthlyQuota: parseFloat(e.target.value)})} />
|
<label className="text-sm font-medium text-slate-600 block mb-1">Quota Default €</label>
|
||||||
|
<input type="number" className="border p-2 rounded w-full text-slate-700" value={condoForm.defaultMonthlyQuota} onChange={e => setCondoForm({...condoForm, defaultMonthlyQuota: parseFloat(e.target.value)})} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="text-sm font-medium text-slate-600 block mb-1">Giorno Scadenza (1-31)</label>
|
||||||
|
<input type="number" min="1" max="31" className="border p-2 rounded w-full text-slate-700" value={condoForm.dueDay} onChange={e => setCondoForm({...condoForm, dueDay: parseInt(e.target.value)})} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 pt-2">
|
<div className="flex gap-2 pt-2">
|
||||||
<button type="button" onClick={() => setShowCondoModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600 hover:bg-slate-50">Annulla</button>
|
<button type="button" onClick={() => setShowCondoModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600 hover:bg-slate-50">Annulla</button>
|
||||||
@@ -1012,78 +903,20 @@ export const SettingsPage: React.FC = () => {
|
|||||||
{/* Family Modal */}
|
{/* Family Modal */}
|
||||||
{showFamilyModal && (
|
{showFamilyModal && (
|
||||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
<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 p-6">
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200">
|
||||||
<h3 className="font-bold text-lg mb-4 text-slate-800">{editingFamily ? 'Modifica Famiglia' : 'Nuova Famiglia'}</h3>
|
<h3 className="font-bold text-lg mb-4 text-slate-800">{editingFamily ? 'Modifica Famiglia' : 'Nuova Famiglia'}</h3>
|
||||||
<form onSubmit={handleFamilySubmit} className="space-y-4">
|
<form onSubmit={handleFamilySubmit} className="space-y-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Cognome / Nome Famiglia" value={familyForm.name} onChange={e => setFamilyForm({...familyForm, name: e.target.value})} required />
|
||||||
<div className="md:col-span-2">
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Nome Famiglia</label>
|
|
||||||
<input type="text" required value={familyForm.name} onChange={(e) => setFamilyForm({...familyForm, name: e.target.value})} className="w-full border rounded-lg p-2.5 text-slate-700" placeholder="Es. Rossi"/>
|
|
||||||
</div>
|
|
||||||
<div className="md:col-span-2">
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Email Contatto (Obbligatoria)</label>
|
|
||||||
<input type="email" required value={familyForm.contactEmail} onChange={(e) => setFamilyForm({...familyForm, contactEmail: e.target.value})} className="w-full border rounded-lg p-2.5 text-slate-700" placeholder="email@esempio.it"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div>
|
<input className="border p-2.5 rounded-lg text-slate-700" placeholder="Interno" value={familyForm.unitNumber} onChange={e => setFamilyForm({...familyForm, unitNumber: e.target.value})} required />
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Interno</label>
|
<input className="border p-2.5 rounded-lg text-slate-700" placeholder="Scala" value={familyForm.stair} onChange={e => setFamilyForm({...familyForm, stair: e.target.value})} />
|
||||||
<input type="text" value={familyForm.unitNumber} onChange={(e) => setFamilyForm({...familyForm, unitNumber: e.target.value})} className="w-full border rounded-lg p-2.5 text-slate-700" />
|
<input className="border p-2.5 rounded-lg text-slate-700" placeholder="Piano" value={familyForm.floor} onChange={e => setFamilyForm({...familyForm, floor: e.target.value})} />
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Scala</label>
|
|
||||||
<input type="text" value={familyForm.stair} onChange={(e) => setFamilyForm({...familyForm, stair: e.target.value})} className="w-full border rounded-lg p-2.5 text-slate-700" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Piano</label>
|
|
||||||
<input type="text" value={familyForm.floor} onChange={(e) => setFamilyForm({...familyForm, floor: e.target.value})} className="w-full border rounded-lg p-2.5 text-slate-700" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Note</label>
|
|
||||||
<textarea value={familyForm.notes} onChange={(e) => setFamilyForm({...familyForm, notes: e.target.value})} className="w-full border rounded-lg p-2.5 text-slate-700 h-16" placeholder="Note opzionali..."></textarea>
|
|
||||||
</div>
|
|
||||||
<div className="pt-2 border-t border-slate-100">
|
|
||||||
<label className="block text-xs font-bold text-slate-500 uppercase mb-1">Quota Mensile Personalizzata</label>
|
|
||||||
<p className="text-xs text-slate-400 mb-2">Lasciare vuoto per usare il default del condominio (€ {activeCondo?.defaultMonthlyQuota})</p>
|
|
||||||
<input type="number" step="0.01" value={familyForm.customMonthlyQuota} onChange={(e) => setFamilyForm({...familyForm, customMonthlyQuota: e.target.value})} className="w-full border rounded-lg p-2.5 text-slate-700" placeholder="Es. 120.00"/>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-3 pt-4">
|
|
||||||
<button type="button" onClick={() => setShowFamilyModal(false)} className="flex-1 p-3 border rounded-lg text-slate-600">Annulla</button>
|
|
||||||
<button type="submit" className="flex-1 p-3 bg-blue-600 text-white rounded-lg">Salva</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Alert 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-xl shadow-xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
|
|
||||||
<h3 className="font-bold text-lg mb-4 text-slate-800">{editingAlert ? 'Modifica Avviso' : 'Nuovo Avviso Automatico'}</h3>
|
|
||||||
<form onSubmit={handleAlertSubmit} className="space-y-4">
|
|
||||||
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Oggetto Email" value={alertForm.subject} onChange={e => setAlertForm({...alertForm, subject: e.target.value})} required />
|
|
||||||
<textarea className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Corpo Email" value={alertForm.body} onChange={e => setAlertForm({...alertForm, body: e.target.value})} required />
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase">Giorni Offset</label>
|
|
||||||
<input type="number" className="w-full border p-2.5 rounded-lg text-slate-700" value={alertForm.daysOffset} onChange={e => setAlertForm({...alertForm, daysOffset: parseInt(e.target.value)})} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase">Ora Invio</label>
|
|
||||||
<input type="number" min="0" max="23" className="w-full border p-2.5 rounded-lg text-slate-700" value={alertForm.sendHour} onChange={e => setAlertForm({...alertForm, sendHour: parseInt(e.target.value)})} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-500 font-bold uppercase">Tipo Offset</label>
|
|
||||||
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={alertForm.offsetType} onChange={e => setAlertForm({...alertForm, offsetType: e.target.value as any})}>
|
|
||||||
<option value="before_next_month">Giorni prima del prossimo mese</option>
|
|
||||||
<option value="after_current_month">Giorni dopo inizio mese</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input type="email" className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Email Contatto Principal" value={familyForm.contactEmail} onChange={e => setFamilyForm({...familyForm, contactEmail: e.target.value})} />
|
||||||
|
<input type="number" className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Quota Personalizzata (Opzionale)" value={familyForm.customMonthlyQuota} onChange={e => setFamilyForm({...familyForm, customMonthlyQuota: e.target.value})} />
|
||||||
|
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-20" placeholder="Note..." value={familyForm.notes} onChange={e => setFamilyForm({...familyForm, notes: e.target.value})} />
|
||||||
<div className="flex gap-2 pt-2">
|
<div className="flex gap-2 pt-2">
|
||||||
<button type="button" onClick={() => setShowAlertModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600">Annulla</button>
|
<button type="button" onClick={() => setShowFamilyModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600">Annulla</button>
|
||||||
<button type="submit" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg">Salva</button>
|
<button type="submit" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg">Salva</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -1091,60 +924,188 @@ export const SettingsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* SMTP Modal */}
|
{/* User Modal */}
|
||||||
{showSmtpModal && (
|
{showUserModal && (
|
||||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4 backdrop-blur-sm">
|
<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-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200">
|
<div className="bg-white rounded-xl shadow-xl w-full max-w-md p-6 animate-in fade-in zoom-in duration-200">
|
||||||
<div className="flex justify-between items-center mb-6">
|
<h3 className="font-bold text-lg mb-4 text-slate-800">{editingUser ? 'Modifica Utente' : 'Nuovo Utente'}</h3>
|
||||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2"><Server className="w-5 h-5 text-blue-600"/> Configurazione SMTP</h3>
|
<form onSubmit={handleUserSubmit} className="space-y-4">
|
||||||
<button onClick={() => setShowSmtpModal(false)} className="text-slate-400 hover:text-slate-600"><X className="w-6 h-6"/></button>
|
<input type="email" className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Email" value={userForm.email} onChange={e => setUserForm({...userForm, email: e.target.value})} required />
|
||||||
</div>
|
<input type="text" className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Nome" value={userForm.name} onChange={e => setUserForm({...userForm, name: e.target.value})} />
|
||||||
<form onSubmit={handleSmtpSubmit} className="space-y-5">
|
<input type="password" className="w-full border p-2.5 rounded-lg text-slate-700" placeholder={editingUser ? "Password (lascia vuoto per mantenere)" : "Password"} value={userForm.password} onChange={e => setUserForm({...userForm, password: e.target.value})} />
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={userForm.role} onChange={e => setUserForm({...userForm, role: e.target.value})}>
|
||||||
<div>
|
<option value="user">Utente (Condomino)</option>
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Host</label>
|
<option value="poweruser">Power User (Gestore)</option>
|
||||||
<input type="text" value={globalSettings?.smtpConfig?.host || ''} onChange={(e) => setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), host: e.target.value} as any} : null)} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="smtp.gmail.com"/>
|
<option value="admin">Amministratore</option>
|
||||||
</div>
|
</select>
|
||||||
<div>
|
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={userForm.familyId} onChange={e => setUserForm({...userForm, familyId: e.target.value})}>
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Porta</label>
|
<option value="">-- Associa a Famiglia --</option>
|
||||||
<input type="number" value={globalSettings?.smtpConfig?.port || 0} onChange={(e) => setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), port: parseInt(e.target.value)} as any} : null)} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="587"/>
|
{families.map(f => <option key={f.id} value={f.id}>{f.name} (Int. {f.unitNumber})</option>)}
|
||||||
</div>
|
</select>
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Utente</label>
|
|
||||||
<input type="text" value={globalSettings?.smtpConfig?.user || ''} onChange={(e) => setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), user: e.target.value} as any} : null)} className="w-full border p-2.5 rounded-lg text-slate-700"/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Password</label>
|
|
||||||
<input type="password" value={globalSettings?.smtpConfig?.pass || ''} onChange={(e) => setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), pass: e.target.value} as any} : null)} className="w-full border p-2.5 rounded-lg text-slate-700"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs font-bold text-slate-500 uppercase mb-1 block">Email Mittente</label>
|
|
||||||
<input type="email" value={globalSettings?.smtpConfig?.fromEmail || ''} onChange={(e) => setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), fromEmail: e.target.value} as any} : null)} className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="no-reply@condominio.it"/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input type="checkbox" checked={globalSettings?.smtpConfig?.secure || false} onChange={(e) => setGlobalSettings(prev => prev ? {...prev, smtpConfig: {...(prev.smtpConfig || {}), secure: e.target.checked} as any} : null)} className="w-4 h-4 text-blue-600"/>
|
<input type="checkbox" checked={userForm.receiveAlerts} onChange={e => setUserForm({...userForm, receiveAlerts: e.target.checked})} />
|
||||||
<label className="text-sm text-slate-700 font-medium">Usa SSL/TLS (Secure)</label>
|
<span className="text-sm text-slate-600">Ricevi notifiche email</span>
|
||||||
</div>
|
|
||||||
<button type="button" onClick={handleSmtpTest} disabled={testingSmtp} className="text-xs font-bold bg-amber-100 text-amber-700 px-3 py-1.5 rounded hover:bg-amber-200 transition-colors flex items-center gap-1 disabled:opacity-50">
|
|
||||||
{testingSmtp ? <span className="animate-pulse">Test...</span> : <><Send className="w-3 h-3"/> Test Configurazione</>}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{testSmtpMsg && <p className={`text-xs font-medium text-center ${testSmtpMsg.startsWith('Errore') ? 'text-red-500' : 'text-green-600'}`}>{testSmtpMsg}</p>}
|
|
||||||
<div className="pt-2 flex justify-between items-center border-t border-slate-100 mt-2">
|
|
||||||
<span className="text-green-600 text-sm font-medium">{successMsg}</span>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button type="button" onClick={() => setShowSmtpModal(false)} className="px-4 py-2 text-slate-600 border rounded-lg hover:bg-slate-50">Chiudi</button>
|
|
||||||
<button type="submit" className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 font-medium">Salva</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-2 pt-2">
|
||||||
|
<button type="button" onClick={() => setShowUserModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600">Annulla</button>
|
||||||
|
<button type="submit" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg">Salva</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Notice Modal */}
|
||||||
|
{showNoticeModal && (
|
||||||
|
<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-xl shadow-xl w-full max-w-2xl p-6 animate-in fade-in zoom-in duration-200 flex flex-col max-h-[90vh]">
|
||||||
|
<h3 className="font-bold text-lg mb-4 text-slate-800">{editingNotice ? 'Modifica Avviso' : 'Nuovo Avviso in Bacheca'}</h3>
|
||||||
|
<div className="flex-1 overflow-y-auto pr-2">
|
||||||
|
<form id="noticeForm" onSubmit={handleNoticeSubmit} className="space-y-4">
|
||||||
|
<input className="w-full border p-2.5 rounded-lg text-slate-700 font-bold" placeholder="Titolo Avviso" value={noticeForm.title} onChange={e => setNoticeForm({...noticeForm, title: e.target.value})} required />
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={noticeForm.type} onChange={e => setNoticeForm({...noticeForm, type: e.target.value as any})}>
|
||||||
|
<option value="info">Informazione</option>
|
||||||
|
<option value="warning">Avviso Urgente</option>
|
||||||
|
<option value="maintenance">Manutenzione</option>
|
||||||
|
<option value="event">Evento / Assemblea</option>
|
||||||
|
</select>
|
||||||
|
<div className="flex items-center gap-2 border p-2.5 rounded-lg bg-slate-50">
|
||||||
|
<span className="text-sm text-slate-600">Stato:</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button type="button" onClick={() => setNoticeForm({...noticeForm, active: true})} className={`px-2 py-1 text-xs rounded ${noticeForm.active ? 'bg-green-500 text-white' : 'bg-slate-200'}`}>Attivo</button>
|
||||||
|
<button type="button" onClick={() => setNoticeForm({...noticeForm, active: false})} className={`px-2 py-1 text-xs rounded ${!noticeForm.active ? 'bg-slate-500 text-white' : 'bg-slate-200'}`}>Bozza</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-32" placeholder="Testo dell'avviso..." value={noticeForm.content} onChange={e => setNoticeForm({...noticeForm, content: e.target.value})} required />
|
||||||
|
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Link esterno (es. PDF verbale)" value={noticeForm.link} onChange={e => setNoticeForm({...noticeForm, link: e.target.value})} />
|
||||||
|
|
||||||
|
{/* Targeting */}
|
||||||
|
<div className="border-t pt-4">
|
||||||
|
<label className="block text-sm font-bold text-slate-700 mb-2">Visibilità</label>
|
||||||
|
<div className="flex gap-4 mb-3">
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="radio" name="targetMode" checked={noticeTargetMode === 'all'} onChange={() => setNoticeTargetMode('all')} className="text-blue-600"/>
|
||||||
|
<span className="text-sm">Pubblico (Tutti)</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="radio" name="targetMode" checked={noticeTargetMode === 'specific'} onChange={() => setNoticeTargetMode('specific')} className="text-blue-600"/>
|
||||||
|
<span className="text-sm">Seleziona Famiglie</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{noticeTargetMode === 'specific' && (
|
||||||
|
<div className="border rounded-lg p-2 max-h-40 overflow-y-auto bg-slate-50 grid grid-cols-2 gap-2">
|
||||||
|
{families.map(f => (
|
||||||
|
<label key={f.id} className="flex items-center gap-2 text-sm p-1 hover:bg-white rounded cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={noticeForm.targetFamilyIds.includes(f.id)}
|
||||||
|
onChange={() => toggleNoticeFamilyTarget(f.id)}
|
||||||
|
className="rounded text-blue-600"
|
||||||
|
/>
|
||||||
|
<span className="truncate">{f.name} ({f.unitNumber})</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="pt-4 border-t mt-4 flex gap-2">
|
||||||
|
<button onClick={() => setShowNoticeModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600">Annulla</button>
|
||||||
|
<button type="submit" form="noticeForm" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg">Salva e Pubblica</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Alert 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-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200">
|
||||||
|
<h3 className="font-bold text-lg mb-4 text-slate-800">Configura Alert Email</h3>
|
||||||
|
<form onSubmit={handleAlertSubmit} className="space-y-4">
|
||||||
|
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Oggetto Email" value={alertForm.subject} onChange={e => setAlertForm({...alertForm, subject: e.target.value})} required />
|
||||||
|
<textarea className="w-full border p-2.5 rounded-lg text-slate-700 h-24" placeholder="Corpo del messaggio..." 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 block mb-1">Offset Giorni</label>
|
||||||
|
<input type="number" className="w-full border p-2.5 rounded-lg text-slate-700" value={alertForm.daysOffset} onChange={e => setAlertForm({...alertForm, daysOffset: parseInt(e.target.value)})} required />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-bold text-slate-500 block mb-1">Tipo Offset</label>
|
||||||
|
<select className="w-full border p-2.5 rounded-lg text-slate-700 bg-white" value={alertForm.offsetType} onChange={e => setAlertForm({...alertForm, offsetType: e.target.value as any})}>
|
||||||
|
<option value="before_next_month">Giorni prima del prox mese</option>
|
||||||
|
<option value="after_current_month">Giorni dopo inizio mese</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 pt-2">
|
||||||
|
<button type="button" onClick={() => setShowAlertModal(false)} className="flex-1 border p-2.5 rounded-lg text-slate-600">Annulla</button>
|
||||||
|
<button type="submit" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg">Salva Configurazione</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* SMTP Modal */}
|
||||||
|
{showSmtpModal && globalSettings && (
|
||||||
|
<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-xl shadow-xl w-full max-w-lg p-6 animate-in fade-in zoom-in duration-200">
|
||||||
|
<h3 className="font-bold text-lg mb-4 text-slate-800 flex items-center gap-2"><Server className="w-5 h-5"/> Configurazione SMTP</h3>
|
||||||
|
<form onSubmit={handleSmtpSubmit} className="space-y-4">
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
|
<div className="col-span-2"><input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Host (es. smtp.gmail.com)" value={globalSettings.smtpConfig?.host || ''} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, host: e.target.value}})} /></div>
|
||||||
|
<div><input type="number" className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Porta" value={globalSettings.smtpConfig?.port || 587} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, port: parseInt(e.target.value)}})} /></div>
|
||||||
|
</div>
|
||||||
|
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Utente SMTP" value={globalSettings.smtpConfig?.user || ''} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, user: e.target.value}})} />
|
||||||
|
<input type="password" className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Password SMTP" value={globalSettings.smtpConfig?.pass || ''} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, pass: e.target.value}})} />
|
||||||
|
<input className="w-full border p-2.5 rounded-lg text-slate-700" placeholder="Email Mittente (From)" value={globalSettings.smtpConfig?.fromEmail || ''} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, fromEmail: e.target.value}})} />
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input type="checkbox" checked={globalSettings.smtpConfig?.secure || false} onChange={e => setGlobalSettings({...globalSettings, smtpConfig: {...globalSettings.smtpConfig!, secure: e.target.checked}})} />
|
||||||
|
<span className="text-sm text-slate-600">Usa SSL/TLS (Secure)</span>
|
||||||
|
</div>
|
||||||
|
{testSmtpMsg && <div className={`text-xs p-2 rounded ${testSmtpMsg.includes('Successo') ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>{testSmtpMsg}</div>}
|
||||||
|
<div className="flex gap-2 pt-2">
|
||||||
|
<button type="button" onClick={handleSmtpTest} disabled={testingSmtp} className="flex-1 border border-slate-300 text-slate-600 p-2.5 rounded-lg hover:bg-slate-50">{testingSmtp ? 'Test...' : 'Test Connessione'}</button>
|
||||||
|
<button type="submit" className="flex-1 bg-blue-600 text-white p-2.5 rounded-lg hover:bg-blue-700">Salva</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" onClick={() => setShowSmtpModal(false)} className="w-full text-center text-sm text-slate-400 mt-2 hover:text-slate-600">Chiudi</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Read Details Modal */}
|
||||||
|
{showReadDetailsModal && selectedNoticeId && (
|
||||||
|
<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-xl shadow-xl w-full max-w-sm p-6 animate-in fade-in zoom-in duration-200 flex flex-col max-h-[80vh]">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h3 className="font-bold text-slate-800">Dettaglio Letture</h3>
|
||||||
|
<button onClick={() => setShowReadDetailsModal(false)}><X className="w-5 h-5 text-slate-400"/></button>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
{(noticeReadStats[selectedNoticeId] || []).length === 0 ? (
|
||||||
|
<p className="text-slate-500 text-sm text-center py-4">Nessuna lettura registrata.</p>
|
||||||
|
) : (
|
||||||
|
<ul className="divide-y">
|
||||||
|
{(noticeReadStats[selectedNoticeId] || []).map((read, idx) => {
|
||||||
|
const u = users.find(usr => usr.id === read.userId);
|
||||||
|
return (
|
||||||
|
<li key={idx} className="py-2 text-sm">
|
||||||
|
<div className="font-medium text-slate-700">{u?.name || u?.email || 'Utente sconosciuto'}</div>
|
||||||
|
<div className="text-xs text-slate-400">{new Date(read.readAt).toLocaleString()}</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user