import React, { useEffect, useState } from 'react'; import { NavLink, Outlet } from 'react-router-dom'; import { Users, Settings, Building, LogOut, Menu, X, ChevronDown, Check, LayoutDashboard, Megaphone, Info, AlertTriangle, Hammer, Calendar, MessageSquareWarning, PieChart, Briefcase } from 'lucide-react'; import { CondoService } from '../services/mockDb'; import { Condo, Notice, AppSettings } from '../types'; export const Layout: React.FC = () => { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const user = CondoService.getCurrentUser(); const isAdmin = user?.role === 'admin' || user?.role === 'poweruser'; const [condos, setCondos] = useState([]); const [activeCondo, setActiveCondo] = useState(undefined); const [showCondoDropdown, setShowCondoDropdown] = useState(false); const [settings, setSettings] = useState(null); // Notifications const [activeNotice, setActiveNotice] = useState(null); const [hasNewExpenses, setHasNewExpenses] = useState(false); // Ticket Badges const [ticketBadgeCount, setTicketBadgeCount] = useState(0); const fetchContext = async () => { // Fetch global settings to check features try { const globalSettings = await CondoService.getSettings(); setSettings(globalSettings); if (isAdmin && globalSettings.features.multiCondo) { const list = await CondoService.getCondos(); setCondos(list); } else if (isAdmin) { const list = await CondoService.getCondos(); setCondos(list); } } catch(e) { console.error("Error fetching settings", e); } const active = await CondoService.getActiveCondo(); setActiveCondo(active); // --- NOTIFICATION LOGIC --- const lastViewedTicketsStr = localStorage.getItem('lastViewedTickets'); const lastViewedTickets = lastViewedTicketsStr ? parseInt(lastViewedTicketsStr) : 0; // 1. Tickets Badge Logic try { if (settings?.features.tickets || true) { // Check features if available or default const tickets = await CondoService.getTickets(); let count = 0; for (const t of tickets) { const ticketDate = new Date(t.createdAt).getTime(); const isTicketNew = ticketDate > lastViewedTickets; const isArchived = t.status === 'RESOLVED' || t.status === 'CLOSED'; if (isAdmin) { // Admin: Count new unarchived tickets OR tickets with new comments from users if (isTicketNew && !isArchived) { count++; } else { // Check for new comments from users // Optimization: In a real app we'd need a lighter query. // Here we iterate because we have the data or fetch lightly. // Assuming getTickets includes basic info or we need to check updatedAt const updatedDate = new Date(t.updatedAt).getTime(); if (updatedDate > lastViewedTickets) { // Deep check: fetch comments only if recently updated const comments = await CondoService.getTicketComments(t.id); const hasNewUserReply = comments.some(c => new Date(c.createdAt).getTime() > lastViewedTickets && c.userId !== user?.id); if (hasNewUserReply) count++; } } } else { // User: Count tickets with new comments from Admin (or others) const updatedDate = new Date(t.updatedAt).getTime(); if (updatedDate > lastViewedTickets) { const comments = await CondoService.getTicketComments(t.id); const hasNewReply = comments.some(c => new Date(c.createdAt).getTime() > lastViewedTickets && c.userId !== user?.id); if (hasNewReply) count++; } } } setTicketBadgeCount(count); } } catch(e) { console.error("Error calc ticket badges", e); } // Check for notices & expenses for User if (!isAdmin && active && user) { try { // 2. Check Notices const unread = await CondoService.getUnreadNoticesForUser(user.id, active.id); if (unread.length > 0) { setActiveNotice(unread[0]); } // 3. Check New Extraordinary Expenses const myExpenses = await CondoService.getMyExpenses(); const lastViewed = localStorage.getItem('lastViewedExpensesTime'); const lastViewedTime = lastViewed ? parseInt(lastViewed) : 0; // Check if any expense was created AFTER the last visit const hasNew = myExpenses.some((e: any) => new Date(e.createdAt).getTime() > lastViewedTime); setHasNewExpenses(hasNew); } catch(e) {} } }; useEffect(() => { fetchContext(); // Listen for updates from Settings or Expense views const handleUpdate = () => fetchContext(); window.addEventListener('condo-updated', handleUpdate); window.addEventListener('expenses-viewed', handleUpdate); window.addEventListener('tickets-viewed', handleUpdate); // Listen for ticket view return () => { window.removeEventListener('condo-updated', handleUpdate); window.removeEventListener('expenses-viewed', handleUpdate); window.removeEventListener('tickets-viewed', handleUpdate); }; }, [isAdmin]); const handleCondoSwitch = (condoId: string) => { CondoService.setActiveCondo(condoId); setShowCondoDropdown(false); }; const handleReadNotice = async () => { if (activeNotice && user) { await CondoService.markNoticeAsRead(activeNotice.id, user.id); setActiveNotice(null); } }; const closeNoticeModal = () => setActiveNotice(null); const navClass = ({ isActive }: { isActive: boolean }) => `flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 ${ isActive ? 'bg-blue-600 text-white shadow-md' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100' }`; const closeMenu = () => setIsMobileMenuOpen(false); const NoticeIcon = ({type}: {type: string}) => { switch(type) { case 'warning': return ; case 'maintenance': return ; case 'event': return ; default: return ; } }; // Check if notices are actually enabled before showing modal const showNotice = activeNotice && settings?.features.notices; return (
{/* Active Notice Modal */} {showNotice && activeNotice && (

{activeNotice.title}

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

{activeNotice.content} {activeNotice.link && ( )}
)} {/* Mobile Header */}

CondoPay

{activeCondo &&

{activeCondo.name}

}
{/* Sidebar Overlay for Mobile */} {isMobileMenuOpen && (
)} {/* Sidebar Navigation */} {/* Main Content Area */}
); };