import React, { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { CondoService } from '../services/mockDb'; import { Family, Condo, Notice, AppSettings, Ticket, TicketStatus } from '../types'; import { Search, ChevronRight, UserCircle, Building, Bell, AlertTriangle, Hammer, Calendar, Info, Link as LinkIcon, Check, Wallet, Briefcase, MessageSquareWarning, ArrowRight, CheckCircle2, ChevronDown, ChevronUp, Eye } from 'lucide-react'; export const FamilyList: React.FC = () => { const navigate = useNavigate(); const [families, setFamilies] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [activeCondo, setActiveCondo] = useState(undefined); const [notices, setNotices] = useState([]); const [userReadIds, setUserReadIds] = useState([]); const [settings, setSettings] = useState(null); // User Dashboard Data const [myTickets, setMyTickets] = useState([]); const [myExtraExpenses, setMyExtraExpenses] = useState([]); const [myFamily, setMyFamily] = useState(null); // Payment Status State const [regularPaymentStatus, setRegularPaymentStatus] = useState<'OK' | 'PENDING' | 'OVERDUE'>('OK'); const [regularDebtAmount, setRegularDebtAmount] = useState(0); // UI State for Notices const [expandedNoticeId, setExpandedNoticeId] = useState(null); const currentUser = CondoService.getCurrentUser(); const isPrivileged = currentUser?.role === 'admin' || currentUser?.role === 'poweruser'; useEffect(() => { const fetchData = async () => { try { CondoService.seedPayments(); const [fams, condo, allNotices, appSettings] = await Promise.all([ CondoService.getFamilies(), CondoService.getActiveCondo(), CondoService.getNotices(), CondoService.getSettings() ]); setFamilies(fams); setActiveCondo(condo); setSettings(appSettings); // --- USER SPECIFIC DASHBOARD DATA --- if (currentUser && !isPrivileged && currentUser.familyId && condo) { // 1. Find My Family const me = fams.find(f => f.id === currentUser.familyId) || null; setMyFamily(me); // 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 Payment Status const payments = await CondoService.getPaymentsByFamily(currentUser.familyId); const currentYear = appSettings.currentYear; const now = new Date(); const currentRealMonth = now.getMonth() + 1; // 1-12 const currentDay = now.getDate(); const dueDay = condo.dueDay || 10; const quota = me?.customMonthlyQuota ?? condo.defaultMonthlyQuota; let totalDebt = 0; let status: 'OK' | 'PENDING' | 'OVERDUE' = 'OK'; // Check previous months first (Always Overdue if unpaid) for (let m = 1; m < currentRealMonth; m++) { const isPaid = payments.some(p => p.forMonth === m && p.forYear === currentYear); if (!isPaid) { totalDebt += quota; status = 'OVERDUE'; } } // Check current month const isCurrentMonthPaid = payments.some(p => p.forMonth === currentRealMonth && p.forYear === currentYear); if (!isCurrentMonthPaid) { if (currentDay > dueDay) { totalDebt += quota; status = 'OVERDUE'; } else if (currentDay >= (dueDay - 10)) { totalDebt += quota; if (status !== 'OVERDUE') status = 'PENDING'; } } setRegularDebtAmount(totalDebt); setRegularPaymentStatus(status); } // --- NOTICE LOGIC --- if (condo && currentUser && appSettings.features.notices) { // Filter: Must be same condo AND Active // Visibility: Admin sees all. User sees Public OR Targeted. const relevantNotices = allNotices.filter(n => { if (n.condoId !== condo.id || !n.active) return false; if (isPrivileged) return true; // Check visibility for regular users const isPublic = !n.targetFamilyIds || n.targetFamilyIds.length === 0; const isTargeted = currentUser.familyId && n.targetFamilyIds?.includes(currentUser.familyId); return isPublic || isTargeted; }); // Sort: Newest first relevantNotices.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); setNotices(relevantNotices); // Check read status for current user const readStatuses = await Promise.all(relevantNotices.map(n => CondoService.getNoticeReadStatus(n.id))); const readIds: string[] = []; readStatuses.forEach((reads, idx) => { if (reads.find(r => r.userId === currentUser.id)) { readIds.push(relevantNotices[idx].id); } }); setUserReadIds(readIds); } } catch (e) { console.error("Error fetching data", e); } finally { setLoading(false); } }; fetchData(); }, [currentUser?.id, isPrivileged]); const handleMarkAsRead = async (noticeId: string) => { if (!currentUser) return; try { await CondoService.markNoticeAsRead(noticeId, currentUser.id); setUserReadIds(prev => [...prev, noticeId]); } catch (e) { console.error("Error marking read", e); } }; const toggleExpandNotice = (id: string) => { setExpandedNoticeId(expandedNoticeId === id ? null : id); }; const filteredFamilies = families.filter(f => f.name.toLowerCase().includes(searchTerm.toLowerCase()) || f.unitNumber.toLowerCase().includes(searchTerm.toLowerCase()) ); const NoticeIcon = ({type}: {type: string}) => { switch(type) { case 'warning': return ; case 'maintenance': return ; case 'event': return ; default: return ; } }; 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) { return
Caricamento in corso...
; } if (!activeCondo) { return (

Nessun Condominio Selezionato

Seleziona o crea un condominio dalle impostazioni.

); } return (
{/* 1. BACHECA CONDOMINIALE (Notices) */} {/* This section is rendered if notices feature is enabled and there are notices */} {settings?.features.notices && notices.length > 0 && (

Bacheca Condominiale

{notices.map(notice => { const isRead = userReadIds.includes(notice.id); const isExpanded = expandedNoticeId === notice.id; // Style configuration // New notices get a distinct border and white background. // Read notices get a slate background and are slightly dimmed to indicate "archive" status. let containerClass = isRead ? 'bg-slate-50 border-slate-200' : 'bg-white border-blue-200 shadow-sm ring-1 ring-blue-50'; if (notice.type === 'warning' && !isRead) { containerClass = 'bg-amber-50/50 border-amber-300 shadow-sm ring-1 ring-amber-50'; } return (
{/* Icon Column */}
{/* Content Column */}
{/* Header: Title + Badges */}

{notice.title}

{!isRead && ( Nuovo )} {isRead && ( Letto )}
{new Date(notice.date).toLocaleDateString()}
{/* Body Text */}
{notice.content}
{/* Footer Actions */}
{/* Expand Button (only if long content) */} {(notice.content.length > 120 || notice.content.includes('\n')) && ( )} {/* Link Button */} {notice.link && ( Apri Allegato )}
{/* Mark as Read Button (Only if not read) */} {!isRead && ( )} {/* Re-read indicator (optional visual cue) */} {isRead && ( Archiviato )}
); })}
)} {/* 2. USER DASHBOARD (Widgets) - Only for Regular Users with a linked Family */} {!isPrivileged && myFamily && (

La Tua Situazione

{/* Regular Payments Widget */}
{regularPaymentStatus === 'OVERDUE' && Insoluto} {regularPaymentStatus === 'PENDING' && In Scadenza}

Rate Condominiali {settings?.currentYear}

{regularDebtAmount > 0 ? `€ -${regularDebtAmount.toFixed(2)}` : 'In Regola'}

{/* Extra Expenses Widget */} {settings?.features.extraordinaryExpenses && (
0 ? 'bg-white border-orange-200' : 'bg-white border-slate-200'}`}>
0 ? 'bg-orange-50 text-orange-600' : 'bg-slate-100 text-slate-500'}`}>
{extraDebt > 0 && Da Saldare}

Spese Straordinarie

0 ? 'text-orange-600' : 'text-slate-800'}`}> {extraDebt > 0 ? `€ -${extraDebt.toFixed(2)}` : 'Nessuna'}

)} {/* Tickets Widget */} {settings?.features.tickets && (
0 ? 'bg-blue-50 text-blue-600' : 'bg-slate-100 text-slate-500'}`}>
{activeTicketsCount > 0 && {activeTicketsCount} Attive}

Le tue Segnalazioni

{activeTicketsCount > 0 ? `${activeTicketsCount} in corso` : 'Tutto OK'}

)}
)} {/* 3. DIRECTORY HEADER */}

Rubrica Condominiale

{activeCondo.name}

setSearchTerm(e.target.value)} />
{/* 4. LIST */}
    {filteredFamilies.length === 0 ? (
  • Nessuna famiglia trovata in questo condominio.
  • ) : ( filteredFamilies.map((family) => (
  • {family.name}

    Interno: {family.unitNumber}

  • )) )}
); };