Update FamilyList.tsx

This commit is contained in:
2025-12-11 22:30:33 +01:00
committed by GitHub
parent 1a613c607c
commit 4137e57046

View File

@@ -1,11 +1,12 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState, useMemo } from 'react';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { CondoService } from '../services/mockDb'; import { CondoService } from '../services/mockDb';
import { Family, Condo, Notice, AppSettings } from '../types'; import { Family, Condo, Notice, AppSettings, Ticket, TicketStatus } from '../types';
import { Search, ChevronRight, UserCircle, Building, Bell, AlertTriangle, Hammer, Calendar, Info, Link as LinkIcon, Check } from 'lucide-react'; import { Search, ChevronRight, UserCircle, Building, Bell, AlertTriangle, Hammer, Calendar, Info, Link as LinkIcon, Check, Wallet, Briefcase, MessageSquareWarning, ArrowRight, AlertCircle, CheckCircle2 } from 'lucide-react';
export const FamilyList: React.FC = () => { export const FamilyList: React.FC = () => {
const navigate = useNavigate();
const [families, setFamilies] = useState<Family[]>([]); const [families, setFamilies] = useState<Family[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
@@ -13,7 +14,15 @@ export const FamilyList: React.FC = () => {
const [notices, setNotices] = useState<Notice[]>([]); const [notices, setNotices] = useState<Notice[]>([]);
const [userReadIds, setUserReadIds] = useState<string[]>([]); const [userReadIds, setUserReadIds] = useState<string[]>([]);
const [settings, setSettings] = useState<AppSettings | null>(null); const [settings, setSettings] = useState<AppSettings | null>(null);
// User Dashboard Data
const [myTickets, setMyTickets] = useState<Ticket[]>([]);
const [myExtraExpenses, setMyExtraExpenses] = useState<any[]>([]);
const [myRegularDebt, setMyRegularDebt] = useState<number>(0);
const [myFamily, setMyFamily] = useState<Family | null>(null);
const currentUser = CondoService.getCurrentUser(); const currentUser = CondoService.getCurrentUser();
const isPrivileged = currentUser?.role === 'admin' || currentUser?.role === 'poweruser';
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
@@ -29,28 +38,50 @@ export const FamilyList: React.FC = () => {
setActiveCondo(condo); setActiveCondo(condo);
setSettings(appSettings); setSettings(appSettings);
if (condo && currentUser && appSettings.features.notices) { // --- USER SPECIFIC DASHBOARD DATA ---
// Filter notices logic: if (currentUser && !isPrivileged && currentUser.familyId && condo) {
// 1. Must belong to current condo and be active // 1. Find My Family
// 2. If Admin/PowerUser -> See everything const me = fams.find(f => f.id === currentUser.familyId) || null;
// 3. If standard User -> See Public notices (no target) OR Targeted notices containing their familyId setMyFamily(me);
const isPrivileged = currentUser.role === 'admin' || currentUser.role === 'poweruser';
// 2. Fetch Tickets
const tickets = await CondoService.getTickets(); // Backend filters for user
setMyTickets(tickets);
// 3. Fetch Extra Expenses
if (appSettings.features.extraordinaryExpenses) {
const extra = await CondoService.getMyExpenses();
setMyExtraExpenses(extra);
}
// 4. Calculate Regular Debt (Current Year)
const payments = await CondoService.getPaymentsByFamily(currentUser.familyId);
const currentYear = appSettings.currentYear;
const now = new Date();
const currentMonth = now.getFullYear() === currentYear ? now.getMonth() + 1 : (now.getFullYear() > currentYear ? 12 : 0);
let debt = 0;
const quota = me?.customMonthlyQuota ?? condo.defaultMonthlyQuota;
for (let m = 1; m <= currentMonth; m++) {
const isPaid = payments.some(p => p.forMonth === m && p.forYear === currentYear);
if (!isPaid) debt += quota;
}
setMyRegularDebt(debt);
}
// --- NOTICE LOGIC ---
if (condo && currentUser && appSettings.features.notices) {
const condoNotices = allNotices.filter(n => { const condoNotices = allNotices.filter(n => {
if (n.condoId !== condo.id || !n.active) return false; if (n.condoId !== condo.id || !n.active) return false;
if (isPrivileged) return true; if (isPrivileged) return true;
// Check targeting
const hasTargets = n.targetFamilyIds && n.targetFamilyIds.length > 0; const hasTargets = n.targetFamilyIds && n.targetFamilyIds.length > 0;
if (!hasTargets) return true; // Public to all if (!hasTargets) return true;
return currentUser.familyId && n.targetFamilyIds?.includes(currentUser.familyId); return currentUser.familyId && n.targetFamilyIds?.includes(currentUser.familyId);
}); });
setNotices(condoNotices); setNotices(condoNotices);
// Check which ones are read
const readStatuses = await Promise.all(condoNotices.map(n => CondoService.getNoticeReadStatus(n.id))); const readStatuses = await Promise.all(condoNotices.map(n => CondoService.getNoticeReadStatus(n.id)));
const readIds: string[] = []; const readIds: string[] = [];
readStatuses.forEach((reads, idx) => { readStatuses.forEach((reads, idx) => {
@@ -68,7 +99,7 @@ export const FamilyList: React.FC = () => {
} }
}; };
fetchData(); fetchData();
}, []); }, [currentUser?.id, isPrivileged]); // Dependencies
const filteredFamilies = families.filter(f => const filteredFamilies = families.filter(f =>
f.name.toLowerCase().includes(searchTerm.toLowerCase()) || f.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
@@ -84,6 +115,10 @@ export const FamilyList: React.FC = () => {
} }
}; };
// Dashboard Calculations
const activeTicketsCount = myTickets.filter(t => t.status !== TicketStatus.RESOLVED && t.status !== TicketStatus.CLOSED).length;
const extraDebt = myExtraExpenses.reduce((acc, exp) => acc + Math.max(0, exp.myShare.amountDue - exp.myShare.amountPaid), 0);
if (loading) { if (loading) {
return <div className="flex justify-center items-center h-64 text-slate-400">Caricamento in corso...</div>; return <div className="flex justify-center items-center h-64 text-slate-400">Caricamento in corso...</div>;
} }
@@ -99,11 +134,11 @@ export const FamilyList: React.FC = () => {
} }
return ( return (
<div className="space-y-8 pb-12"> <div className="space-y-8 pb-12 animate-fade-in">
{/* Notices Section - Dashboard effect */} {/* 1. NOTICES (Bacheca) - High Priority */}
{settings?.features.notices && notices.length > 0 && ( {settings?.features.notices && notices.length > 0 && (
<div className="space-y-3 mb-6"> <div className="space-y-3">
<h3 className="font-bold text-slate-700 flex items-center gap-2"> <h3 className="font-bold text-slate-700 flex items-center gap-2">
<Bell className="w-5 h-5" /> Bacheca Avvisi <Bell className="w-5 h-5" /> Bacheca Avvisi
</h3> </h3>
@@ -138,10 +173,92 @@ export const FamilyList: React.FC = () => {
</div> </div>
)} )}
{/* Responsive Header */} {/* 2. USER DASHBOARD (Widgets) - Only for Regular Users with a linked Family */}
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4"> {!isPrivileged && myFamily && (
<div className="space-y-3">
<h3 className="font-bold text-slate-700 flex items-center gap-2">
<UserCircle className="w-5 h-5" /> La Tua Situazione
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Regular Payments Widget */}
<div className={`p-5 rounded-xl border shadow-sm flex flex-col justify-between ${myRegularDebt > 0 ? 'bg-white border-red-200' : 'bg-white border-slate-200'}`}>
<div>
<div className="flex justify-between items-start mb-2">
<div className={`p-2 rounded-lg ${myRegularDebt > 0 ? 'bg-red-50 text-red-600' : 'bg-green-50 text-green-600'}`}>
<Wallet className="w-6 h-6" />
</div>
{myRegularDebt > 0 && <span className="bg-red-100 text-red-700 text-[10px] font-bold px-2 py-1 rounded uppercase">Insoluto</span>}
</div>
<p className="text-slate-500 text-xs font-bold uppercase tracking-wide">Rate Condominiali {settings?.currentYear}</p>
<h4 className={`text-2xl font-bold mt-1 ${myRegularDebt > 0 ? 'text-red-600' : 'text-slate-800'}`}>
{myRegularDebt > 0 ? `€ -${myRegularDebt.toFixed(2)}` : 'In Regola'}
</h4>
</div>
<button
onClick={() => navigate(`/family/${myFamily.id}`)}
className="mt-4 w-full py-2 bg-slate-50 hover:bg-slate-100 text-slate-700 rounded-lg text-sm font-medium flex items-center justify-center gap-2 transition-colors"
>
{myRegularDebt > 0 ? 'Paga Ora' : 'Vedi Storico'} <ArrowRight className="w-4 h-4"/>
</button>
</div>
{/* Extra Expenses Widget */}
{settings?.features.extraordinaryExpenses && (
<div className={`p-5 rounded-xl border shadow-sm flex flex-col justify-between ${extraDebt > 0 ? 'bg-white border-orange-200' : 'bg-white border-slate-200'}`}>
<div>
<div className="flex justify-between items-start mb-2">
<div className={`p-2 rounded-lg ${extraDebt > 0 ? 'bg-orange-50 text-orange-600' : 'bg-slate-100 text-slate-500'}`}>
<Briefcase className="w-6 h-6" />
</div>
{extraDebt > 0 && <span className="bg-orange-100 text-orange-700 text-[10px] font-bold px-2 py-1 rounded uppercase">Da Saldare</span>}
</div>
<p className="text-slate-500 text-xs font-bold uppercase tracking-wide">Spese Straordinarie</p>
<h4 className={`text-2xl font-bold mt-1 ${extraDebt > 0 ? 'text-orange-600' : 'text-slate-800'}`}>
{extraDebt > 0 ? `€ -${extraDebt.toFixed(2)}` : 'Nessuna'}
</h4>
</div>
<button
onClick={() => navigate('/extraordinary')}
className="mt-4 w-full py-2 bg-slate-50 hover:bg-slate-100 text-slate-700 rounded-lg text-sm font-medium flex items-center justify-center gap-2 transition-colors"
>
{extraDebt > 0 ? 'Dettagli & Saldo' : 'Vedi Progetti'} <ArrowRight className="w-4 h-4"/>
</button>
</div>
)}
{/* Tickets Widget */}
{settings?.features.tickets && (
<div className="p-5 rounded-xl border border-slate-200 shadow-sm bg-white flex flex-col justify-between">
<div>
<div className="flex justify-between items-start mb-2">
<div className={`p-2 rounded-lg ${activeTicketsCount > 0 ? 'bg-blue-50 text-blue-600' : 'bg-slate-100 text-slate-500'}`}>
<MessageSquareWarning className="w-6 h-6" />
</div>
{activeTicketsCount > 0 && <span className="bg-blue-100 text-blue-700 text-[10px] font-bold px-2 py-1 rounded uppercase">{activeTicketsCount} Attive</span>}
</div>
<p className="text-slate-500 text-xs font-bold uppercase tracking-wide">Le tue Segnalazioni</p>
<h4 className="text-2xl font-bold mt-1 text-slate-800">
{activeTicketsCount > 0 ? `${activeTicketsCount} in corso` : 'Tutto OK'}
</h4>
</div>
<button
onClick={() => navigate('/tickets')}
className="mt-4 w-full py-2 bg-slate-50 hover:bg-slate-100 text-slate-700 rounded-lg text-sm font-medium flex items-center justify-center gap-2 transition-colors"
>
{activeTicketsCount > 0 ? 'Vedi Risposte' : 'Nuova Segnalazione'} <ArrowRight className="w-4 h-4"/>
</button>
</div>
)}
</div>
</div>
)}
{/* 3. DIRECTORY HEADER */}
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 pt-4 border-t border-slate-200">
<div> <div>
<h2 className="text-2xl font-bold text-slate-800">Elenco Condomini</h2> <h2 className="text-2xl font-bold text-slate-800">Rubrica Condominiale</h2>
<p className="text-slate-500 text-sm md:text-base flex items-center gap-1.5"> <p className="text-slate-500 text-sm md:text-base flex items-center gap-1.5">
<Building className="w-4 h-4" /> <Building className="w-4 h-4" />
{activeCondo.name} {activeCondo.name}
@@ -155,14 +272,14 @@ export const FamilyList: React.FC = () => {
<input <input
type="text" 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 text-slate-700" 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 text-slate-700"
placeholder="Cerca nome o interno..." placeholder="Cerca condomino..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
/> />
</div> </div>
</div> </div>
{/* List */} {/* 4. LIST */}
<div className="bg-white shadow-sm rounded-xl overflow-hidden border border-slate-200"> <div className="bg-white shadow-sm rounded-xl overflow-hidden border border-slate-200">
<ul className="divide-y divide-slate-100"> <ul className="divide-y divide-slate-100">
{filteredFamilies.length === 0 ? ( {filteredFamilies.length === 0 ? (