From b7746611666676d2faa314d9322afa8daf513367 Mon Sep 17 00:00:00 2001 From: frakarr Date: Thu, 11 Dec 2025 22:58:38 +0100 Subject: [PATCH] Update FamilyList.tsx --- pages/FamilyList.tsx | 140 ++++++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 42 deletions(-) diff --git a/pages/FamilyList.tsx b/pages/FamilyList.tsx index 6f965be..bf9e056 100644 --- a/pages/FamilyList.tsx +++ b/pages/FamilyList.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState, useMemo } 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, AlertCircle, CheckCircle2 } from 'lucide-react'; +import { Search, ChevronRight, UserCircle, Building, Bell, AlertTriangle, Hammer, Calendar, Info, Link as LinkIcon, Check, Wallet, Briefcase, MessageSquareWarning, ArrowRight, AlertCircle, CheckCircle2, ChevronDown, ChevronUp } from 'lucide-react'; export const FamilyList: React.FC = () => { const navigate = useNavigate(); @@ -24,6 +24,9 @@ export const FamilyList: React.FC = () => { 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'; @@ -72,8 +75,6 @@ export const FamilyList: React.FC = () => { // Check previous months first (Always Overdue if unpaid) for (let m = 1; m < currentRealMonth; m++) { - // Simplified: assuming user only cares about current active year for dashboard alert - // In a real app, check past years too. const isPaid = payments.some(p => p.forMonth === m && p.forYear === currentYear); if (!isPaid) { totalDebt += quota; @@ -84,14 +85,11 @@ export const FamilyList: React.FC = () => { // Check current month const isCurrentMonthPaid = payments.some(p => p.forMonth === currentRealMonth && p.forYear === currentYear); if (!isCurrentMonthPaid) { - // If today > dueDay -> Overdue if (currentDay > dueDay) { totalDebt += quota; status = 'OVERDUE'; - } - // If today is within 10 days before dueDay -> Pending - else if (currentDay >= (dueDay - 10)) { - totalDebt += quota; // It's due soon, so we count it + } else if (currentDay >= (dueDay - 10)) { + totalDebt += quota; if (status !== 'OVERDUE') status = 'PENDING'; } } @@ -101,29 +99,30 @@ export const FamilyList: React.FC = () => { } // --- NOTICE LOGIC --- - // Ensure notices are loaded even if user has no family yet (but belongs to condo) if (condo && currentUser && appSettings.features.notices) { - const condoNotices = allNotices.filter(n => { + // 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; - // Admin sees all active if (isPrivileged) return true; - // Public notices (targetFamilyIds is null/empty) - if (!n.targetFamilyIds || n.targetFamilyIds.length === 0) return true; - - // Targeted notices - return currentUser.familyId && n.targetFamilyIds.includes(currentUser.familyId); + const isPublic = !n.targetFamilyIds || n.targetFamilyIds.length === 0; + const isTargeted = currentUser.familyId && n.targetFamilyIds?.includes(currentUser.familyId); + + return isPublic || isTargeted; }); - setNotices(condoNotices); + // Sort: Newest first + relevantNotices.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + setNotices(relevantNotices); // Check read status - const readStatuses = await Promise.all(condoNotices.map(n => CondoService.getNoticeReadStatus(n.id))); + 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(condoNotices[idx].id); + readIds.push(relevantNotices[idx].id); } }); setUserReadIds(readIds); @@ -138,6 +137,18 @@ export const FamilyList: React.FC = () => { 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()) @@ -145,10 +156,10 @@ export const FamilyList: React.FC = () => { const NoticeIcon = ({type}: {type: string}) => { switch(type) { - case 'warning': return ; - case 'maintenance': return ; - case 'event': return ; - default: return ; + case 'warning': return ; + case 'maintenance': return ; + case 'event': return ; + default: return ; } }; @@ -172,34 +183,79 @@ export const FamilyList: React.FC = () => { return (
- {/* 1. NOTICES (Bacheca) - ALWAYS VISIBLE IF ENABLED */} + {/* 1. BACHECA CONDOMINIALE (Notices) */} {settings?.features.notices && notices.length > 0 && ( -
-

- Bacheca Avvisi +
+

+ Bacheca Condominiale

-
+
{notices.map(notice => { const isRead = userReadIds.includes(notice.id); + const isExpanded = expandedNoticeId === notice.id; + + // Style configuration based on type and read status + let borderClass = isRead ? 'border-slate-200' : 'border-blue-300 shadow-md ring-1 ring-blue-50'; + let bgClass = isRead ? 'bg-white opacity-75' : 'bg-white'; + if (notice.type === 'warning' && !isRead) { + borderClass = 'border-amber-300 shadow-md ring-1 ring-amber-50'; + bgClass = 'bg-amber-50/30'; + } + return ( -
-
-
+
+
+ {/* Icon Column */} +
-
+ + {/* Content Column */} +
-

{notice.title}

- {isRead && Letto} - {!isRead && Nuovo} +
+

{notice.title}

+ {!isRead && Nuovo} + {isRead && Letto} +
+ {new Date(notice.date).toLocaleDateString()} +
+ +
+ {notice.content} +
+ + {/* Action Footer */} +
+
+ {/* Expand Button */} + {notice.content.length > 100 && ( + + )} + + {/* External Link */} + {notice.link && ( + + Apri Allegato + + )} +
+ + {/* Mark as Read Button */} + {!isRead && ( + + )}
-

{new Date(notice.date).toLocaleDateString()}

-

{notice.content}

- {notice.link && ( - - Apri Link - - )}