Update FamilyList.tsx

This commit is contained in:
2025-12-11 23:02:43 +01:00
committed by GitHub
parent b774661166
commit 9d74f7adab

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useState, useMemo } from 'react'; import React, { useEffect, useState } from 'react';
import { Link, useNavigate } 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, Ticket, TicketStatus } 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, Wallet, Briefcase, MessageSquareWarning, ArrowRight, AlertCircle, CheckCircle2, ChevronDown, ChevronUp } from 'lucide-react'; 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 = () => { export const FamilyList: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -60,11 +60,10 @@ export const FamilyList: React.FC = () => {
setMyExtraExpenses(extra); setMyExtraExpenses(extra);
} }
// 4. Calculate Regular Payment Status (Logic Update) // 4. Calculate Regular Payment Status
const payments = await CondoService.getPaymentsByFamily(currentUser.familyId); const payments = await CondoService.getPaymentsByFamily(currentUser.familyId);
const currentYear = appSettings.currentYear; const currentYear = appSettings.currentYear;
const now = new Date(); const now = new Date();
const currentRealYear = now.getFullYear();
const currentRealMonth = now.getMonth() + 1; // 1-12 const currentRealMonth = now.getMonth() + 1; // 1-12
const currentDay = now.getDate(); const currentDay = now.getDate();
const dueDay = condo.dueDay || 10; const dueDay = condo.dueDay || 10;
@@ -107,6 +106,7 @@ export const FamilyList: React.FC = () => {
if (isPrivileged) return true; if (isPrivileged) return true;
// Check visibility for regular users
const isPublic = !n.targetFamilyIds || n.targetFamilyIds.length === 0; const isPublic = !n.targetFamilyIds || n.targetFamilyIds.length === 0;
const isTargeted = currentUser.familyId && n.targetFamilyIds?.includes(currentUser.familyId); const isTargeted = currentUser.familyId && n.targetFamilyIds?.includes(currentUser.familyId);
@@ -117,7 +117,7 @@ export const FamilyList: React.FC = () => {
relevantNotices.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); relevantNotices.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
setNotices(relevantNotices); setNotices(relevantNotices);
// Check read status // Check read status for current user
const readStatuses = await Promise.all(relevantNotices.map(n => CondoService.getNoticeReadStatus(n.id))); const readStatuses = await Promise.all(relevantNotices.map(n => CondoService.getNoticeReadStatus(n.id)));
const readIds: string[] = []; const readIds: string[] = [];
readStatuses.forEach((reads, idx) => { readStatuses.forEach((reads, idx) => {
@@ -156,10 +156,10 @@ export const FamilyList: React.FC = () => {
const NoticeIcon = ({type}: {type: string}) => { const NoticeIcon = ({type}: {type: string}) => {
switch(type) { switch(type) {
case 'warning': return <AlertTriangle className="w-5 h-5 text-amber-600" />; case 'warning': return <AlertTriangle className="w-6 h-6 text-amber-600" />;
case 'maintenance': return <Hammer className="w-5 h-5 text-orange-600" />; case 'maintenance': return <Hammer className="w-6 h-6 text-orange-600" />;
case 'event': return <Calendar className="w-5 h-5 text-purple-600" />; case 'event': return <Calendar className="w-6 h-6 text-purple-600" />;
default: return <Info className="w-5 h-5 text-blue-600" />; default: return <Info className="w-6 h-6 text-blue-600" />;
} }
}; };
@@ -184,6 +184,7 @@ export const FamilyList: React.FC = () => {
<div className="space-y-8 pb-12 animate-fade-in"> <div className="space-y-8 pb-12 animate-fade-in">
{/* 1. BACHECA CONDOMINIALE (Notices) */} {/* 1. BACHECA CONDOMINIALE (Notices) */}
{/* This section is rendered if notices feature is enabled and there are notices */}
{settings?.features.notices && notices.length > 0 && ( {settings?.features.notices && notices.length > 0 && (
<div className="space-y-4"> <div className="space-y-4">
<h3 className="font-bold text-slate-800 text-lg flex items-center gap-2"> <h3 className="font-bold text-slate-800 text-lg flex items-center gap-2">
@@ -194,67 +195,86 @@ export const FamilyList: React.FC = () => {
const isRead = userReadIds.includes(notice.id); const isRead = userReadIds.includes(notice.id);
const isExpanded = expandedNoticeId === notice.id; const isExpanded = expandedNoticeId === notice.id;
// Style configuration based on type and read status // Style configuration
let borderClass = isRead ? 'border-slate-200' : 'border-blue-300 shadow-md ring-1 ring-blue-50'; // New notices get a distinct border and white background.
let bgClass = isRead ? 'bg-white opacity-75' : 'bg-white'; // 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) { if (notice.type === 'warning' && !isRead) {
borderClass = 'border-amber-300 shadow-md ring-1 ring-amber-50'; containerClass = 'bg-amber-50/50 border-amber-300 shadow-sm ring-1 ring-amber-50';
bgClass = 'bg-amber-50/30';
} }
return ( return (
<div key={notice.id} className={`rounded-xl border p-4 transition-all duration-300 ${borderClass} ${bgClass}`}> <div key={notice.id} className={`rounded-xl border p-4 transition-all duration-300 ${containerClass}`}>
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
{/* Icon Column */} {/* Icon Column */}
<div className={`p-3 rounded-full flex-shrink-0 ${isRead ? 'bg-slate-100 grayscale' : 'bg-white shadow-sm'}`}> <div className={`p-2.5 rounded-full flex-shrink-0 ${isRead ? 'bg-slate-200 grayscale opacity-70' : 'bg-white shadow-sm'}`}>
<NoticeIcon type={notice.type} /> <NoticeIcon type={notice.type} />
</div> </div>
{/* Content Column */} {/* Content Column */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2 mb-1"> {/* Header: Title + Badges */}
<div className="flex items-center justify-between gap-2 mb-1.5">
<div className="flex items-center gap-2 overflow-hidden"> <div className="flex items-center gap-2 overflow-hidden">
<h4 className={`font-bold text-base truncate ${isRead ? 'text-slate-600' : 'text-slate-900'}`}>{notice.title}</h4> <h4 className={`font-bold text-base truncate ${isRead ? 'text-slate-500' : 'text-slate-900'}`}>{notice.title}</h4>
{!isRead && <span className="text-[10px] bg-blue-600 text-white px-2 py-0.5 rounded-full font-bold uppercase tracking-wide animate-pulse">Nuovo</span>} {!isRead && (
{isRead && <span className="text-[10px] bg-slate-200 text-slate-500 px-2 py-0.5 rounded-full font-bold uppercase tracking-wide">Letto</span>} <span className="text-[10px] bg-blue-600 text-white px-2 py-0.5 rounded-full font-bold uppercase tracking-wide animate-pulse">
Nuovo
</span>
)}
{isRead && (
<span className="text-[10px] bg-slate-200 text-slate-500 px-2 py-0.5 rounded-full font-bold uppercase tracking-wide flex items-center gap-1">
<Check className="w-3 h-3"/> Letto
</span>
)}
</div> </div>
<span className="text-xs text-slate-400 whitespace-nowrap">{new Date(notice.date).toLocaleDateString()}</span> <span className="text-xs text-slate-400 whitespace-nowrap">{new Date(notice.date).toLocaleDateString()}</span>
</div> </div>
<div className={`text-sm text-slate-600 leading-relaxed whitespace-pre-wrap transition-all ${isExpanded ? '' : 'line-clamp-2'}`}> {/* Body Text */}
<div className={`text-sm leading-relaxed whitespace-pre-wrap transition-all ${isRead ? 'text-slate-500' : 'text-slate-700'} ${isExpanded ? '' : 'line-clamp-2'}`}>
{notice.content} {notice.content}
</div> </div>
{/* Action Footer */} {/* Footer Actions */}
<div className="flex items-center justify-between mt-3 pt-2 border-t border-slate-100/50"> <div className="flex items-center justify-between mt-3 pt-2 border-t border-slate-200/50">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{/* Expand Button */} {/* Expand Button (only if long content) */}
{notice.content.length > 100 && ( {(notice.content.length > 120 || notice.content.includes('\n')) && (
<button <button
onClick={() => toggleExpandNotice(notice.id)} onClick={() => toggleExpandNotice(notice.id)}
className="text-xs font-medium text-blue-600 hover:text-blue-800 flex items-center gap-1" className="text-xs font-medium text-slate-500 hover:text-blue-600 flex items-center gap-1"
> >
{isExpanded ? <><ChevronUp className="w-3 h-3"/> Riduci</> : <><ChevronDown className="w-3 h-3"/> Leggi tutto</>} {isExpanded ? <><ChevronUp className="w-3 h-3"/> Riduci</> : <><ChevronDown className="w-3 h-3"/> Leggi tutto</>}
</button> </button>
)} )}
{/* External Link */} {/* Link Button */}
{notice.link && ( {notice.link && (
<a href={notice.link} target="_blank" rel="noopener noreferrer" className="text-xs font-medium text-blue-600 hover:underline flex items-center gap-1"> <a href={notice.link} target="_blank" rel="noopener noreferrer" className="text-xs font-medium text-blue-600 hover:text-blue-800 hover:underline flex items-center gap-1">
<LinkIcon className="w-3 h-3"/> Apri Allegato <LinkIcon className="w-3 h-3"/> Apri Allegato
</a> </a>
)} )}
</div> </div>
{/* Mark as Read Button */} {/* Mark as Read Button (Only if not read) */}
{!isRead && ( {!isRead && (
<button <button
onClick={() => handleMarkAsRead(notice.id)} onClick={() => handleMarkAsRead(notice.id)}
className="text-xs bg-slate-100 hover:bg-slate-200 text-slate-600 px-3 py-1.5 rounded-lg font-medium transition-colors flex items-center gap-1" className="text-xs bg-white border border-slate-200 hover:bg-blue-50 hover:text-blue-700 hover:border-blue-200 text-slate-600 px-3 py-1.5 rounded-lg font-medium transition-all flex items-center gap-1 shadow-sm"
> >
<CheckCircle2 className="w-3 h-3"/> Segna come letto <CheckCircle2 className="w-3 h-3"/> Segna come letto
</button> </button>
)} )}
{/* Re-read indicator (optional visual cue) */}
{isRead && (
<span className="text-[10px] text-slate-400 flex items-center gap-1">
<Eye className="w-3 h-3"/> Archiviato
</span>
)}
</div> </div>
</div> </div>
</div> </div>