Initializes the Condopay frontend project using Vite, React, and TypeScript. Includes basic project structure, dependencies, and configuration for Tailwind CSS and React Router.
646 lines
31 KiB
TypeScript
646 lines
31 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { CondoService } from '../services/mockDb';
|
|
import { AppSettings, Family, User } from '../types';
|
|
import { Save, Building, Coins, Plus, Pencil, Trash2, X, CalendarCheck, AlertTriangle, UserCog, Mail, Phone, Lock, Shield, User as UserIcon } from 'lucide-react';
|
|
|
|
export const SettingsPage: React.FC = () => {
|
|
const [activeTab, setActiveTab] = useState<'general' | 'families' | 'users'>('general');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// General Settings State
|
|
const [settings, setSettings] = useState<AppSettings>({
|
|
defaultMonthlyQuota: 0,
|
|
condoName: '',
|
|
currentYear: new Date().getFullYear()
|
|
});
|
|
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: ''
|
|
});
|
|
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
const [s, f, u] = await Promise.all([
|
|
CondoService.getSettings(),
|
|
CondoService.getFamilies(),
|
|
CondoService.getUsers()
|
|
]);
|
|
setSettings(s);
|
|
setFamilies(f);
|
|
setUsers(u);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
fetchData();
|
|
}, []);
|
|
|
|
// --- 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: '' });
|
|
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 || ''
|
|
});
|
|
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");
|
|
}
|
|
};
|
|
|
|
const getFamilyName = (id: string | null | undefined) => {
|
|
if (!id) return '-';
|
|
return families.find(f => f.id === id)?.name || 'Sconosciuta';
|
|
};
|
|
|
|
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 e utenti.</p>
|
|
</div>
|
|
|
|
{/* Tabs - Scrollable on mobile */}
|
|
<div className="flex border-b border-slate-200 overflow-x-auto no-scrollbar pb-1">
|
|
<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>
|
|
</div>
|
|
|
|
{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>
|
|
)}
|
|
|
|
{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>
|
|
)}
|
|
|
|
{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">Famiglia</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">{getFamilyName(user.familyId)}</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 Cards for Users */}
|
|
<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="space-y-2 text-sm text-slate-600 bg-slate-50 p-3 rounded-lg mb-4">
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-400">Famiglia:</span>
|
|
<span className="font-medium">{getFamilyName(user.familyId)}</span>
|
|
</div>
|
|
{user.phone && (
|
|
<div className="flex justify-between">
|
|
<span className="text-slate-400">Telefono:</span>
|
|
<span className="font-medium">{user.phone}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-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>
|
|
)}
|
|
|
|
{/* 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>
|
|
|
|
<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>
|
|
)}
|
|
</div>
|
|
);
|
|
}; |