Initializes the Condopay frontend project using Vite, React, and TypeScript. Includes basic project structure, dependencies, and configuration for Tailwind CSS and React Router.
102 lines
4.2 KiB
TypeScript
102 lines
4.2 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { CondoService } from '../services/mockDb';
|
|
import { Family, AppSettings } from '../types';
|
|
import { Search, ChevronRight, UserCircle } from 'lucide-react';
|
|
|
|
export const FamilyList: React.FC = () => {
|
|
const [families, setFamilies] = useState<Family[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [settings, setSettings] = useState<AppSettings | null>(null);
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
CondoService.seedPayments();
|
|
const [fams, sets] = await Promise.all([
|
|
CondoService.getFamilies(),
|
|
CondoService.getSettings()
|
|
]);
|
|
setFamilies(fams);
|
|
setSettings(sets);
|
|
} catch (e) {
|
|
console.error("Error fetching data", e);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
fetchData();
|
|
}, []);
|
|
|
|
const filteredFamilies = families.filter(f =>
|
|
f.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
f.unitNumber.toLowerCase().includes(searchTerm.toLowerCase())
|
|
);
|
|
|
|
if (loading) {
|
|
return <div className="flex justify-center items-center h-64 text-slate-400">Caricamento in corso...</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Responsive Header */}
|
|
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-slate-800">Elenco Condomini</h2>
|
|
<p className="text-slate-500 text-sm md:text-base">{settings?.condoName || 'Gestione Condominiale'}</p>
|
|
</div>
|
|
|
|
<div className="relative w-full md:w-80 lg:w-96">
|
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
<Search className="h-5 w-5 text-slate-400" />
|
|
</div>
|
|
<input
|
|
type="text"
|
|
className="block w-full pl-10 pr-3 py-2.5 border border-slate-300 rounded-xl leading-5 bg-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition duration-150 ease-in-out sm:text-sm shadow-sm"
|
|
placeholder="Cerca nome o interno..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* List */}
|
|
<div className="bg-white shadow-sm rounded-xl overflow-hidden border border-slate-200">
|
|
<ul className="divide-y divide-slate-100">
|
|
{filteredFamilies.length === 0 ? (
|
|
<li className="p-8 text-center text-slate-500 flex flex-col items-center gap-2">
|
|
<Search className="w-8 h-8 text-slate-300" />
|
|
<span>Nessuna famiglia trovata.</span>
|
|
</li>
|
|
) : (
|
|
filteredFamilies.map((family) => (
|
|
<li key={family.id} className="hover:bg-slate-50 transition-colors active:bg-slate-100">
|
|
<Link to={`/family/${family.id}`} className="block p-4 sm:p-5">
|
|
<div className="flex items-center justify-between gap-3">
|
|
<div className="flex items-center gap-3 sm:gap-4 overflow-hidden">
|
|
<div className="bg-blue-100 p-2 sm:p-2.5 rounded-full flex-shrink-0">
|
|
<UserCircle className="w-6 h-6 sm:w-8 sm:h-8 text-blue-600" />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="text-base sm:text-lg font-semibold text-blue-600 truncate">
|
|
{family.name}
|
|
</p>
|
|
<p className="flex items-center text-sm text-slate-500 truncate">
|
|
Interno: <span className="font-medium text-slate-700 ml-1">{family.unitNumber}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex-shrink-0">
|
|
<ChevronRight className="h-5 w-5 text-slate-400" />
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</li>
|
|
))
|
|
)}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |