import { useState, useEffect, useMemo } from 'react'; import { Bell, Settings, User, Plus, Home, FileText, CheckCircle, LogOut, PanelLeft, PanelLeftClose, List, Share2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { useAuth } from '@/contexts/AuthContext'; import { ReLogo } from '@/assets'; import notificationApi, { Notification } from '@/services/notificationApi'; import { getSocket, joinUserRoom } from '@/utils/socket'; import { formatDistanceToNow } from 'date-fns'; interface PageLayoutProps { children: React.ReactNode; currentPage?: string; onNavigate?: (page: string) => void; onNewRequest?: () => void; onLogout?: () => void; } export function PageLayout({ children, currentPage = 'dashboard', onNavigate, onNewRequest, onLogout }: PageLayoutProps) { const [sidebarOpen, setSidebarOpen] = useState(false); const [showLogoutDialog, setShowLogoutDialog] = useState(false); const [notifications, setNotifications] = useState([]); const [unreadCount, setUnreadCount] = useState(0); const [notificationsOpen, setNotificationsOpen] = useState(false); const { user } = useAuth(); // Get user initials for avatar const getUserInitials = () => { try { if (user?.displayName && typeof user.displayName === 'string') { const names = user.displayName.split(' ').filter(Boolean); if (names.length >= 2) { return `${names[0]?.[0] || ''}${names[names.length - 1]?.[0] || ''}`.toUpperCase(); } return user.displayName.substring(0, 2).toUpperCase(); } if (user?.email && typeof user.email === 'string') { return user.email.substring(0, 2).toUpperCase(); } return 'U'; } catch (error) { console.error('[PageLayout] Error getting user initials:', error); return 'U'; } }; const menuItems = useMemo(() => { const items = [ { id: 'dashboard', label: 'Dashboard', icon: Home }, // Add "All Requests" for all users (admin sees org-level, regular users see their participant requests) { id: 'requests', label: 'All Requests', icon: List }, ]; // Add remaining menu items items.push( { id: 'my-requests', label: 'My Requests', icon: User }, { id: 'open-requests', label: 'Open Requests', icon: FileText }, { id: 'closed-requests', label: 'Closed Requests', icon: CheckCircle }, { id: 'shared-summaries', label: 'Shared Summary', icon: Share2 } ); return items; }, []); const toggleSidebar = () => { setSidebarOpen(!sidebarOpen); }; const handleNotificationClick = async (notification: Notification) => { try { // Mark as read if (!notification.isRead) { await notificationApi.markAsRead(notification.notificationId); setNotifications(prev => prev.map(n => n.notificationId === notification.notificationId ? { ...n, isRead: true } : n) ); setUnreadCount(prev => Math.max(0, prev - 1)); } // Navigate to the request if URL provided if (notification.actionUrl && onNavigate) { // Extract request number from URL (e.g., /request/REQ-2025-12345) const requestNumber = notification.metadata?.requestNumber; if (requestNumber) { // Determine which tab to open based on notification type let navigationUrl = `request/${requestNumber}`; // Work note related notifications should open Work Notes tab if (notification.notificationType === 'mention' || notification.notificationType === 'comment' || notification.notificationType === 'worknote') { navigationUrl += '?tab=worknotes'; } // Navigate to request detail page onNavigate(navigationUrl); } } setNotificationsOpen(false); } catch (error) { console.error('[PageLayout] Error handling notification click:', error); } }; const handleMarkAllAsRead = async () => { try { await notificationApi.markAllAsRead(); setNotifications(prev => prev.map(n => ({ ...n, isRead: true }))); setUnreadCount(0); } catch (error) { console.error('[PageLayout] Error marking all as read:', error); } }; // Fetch notifications and setup real-time updates useEffect(() => { const userId = (user as any)?.userId; if (!userId) return; let mounted = true; // Fetch initial notifications (only 4 for dropdown preview) const fetchNotifications = async () => { try { const result = await notificationApi.list({ page: 1, limit: 4, unreadOnly: false }); if (!mounted) return; const notifs = result.data?.notifications || []; setNotifications(notifs); setUnreadCount(result.data?.unreadCount || 0); } catch (error) { console.error('[PageLayout] Failed to fetch notifications:', error); } }; fetchNotifications(); // Setup socket for real-time notifications const socket = getSocket(); // Uses getSocketBaseUrl() helper internally if (socket) { // Join user's personal notification room joinUserRoom(socket, userId); // Listen for new notifications const handleNewNotification = (data: { notification: Notification }) => { if (!mounted) return; setNotifications(prev => [data.notification, ...prev].slice(0, 4)); // Keep latest 4 for dropdown setUnreadCount(prev => prev + 1); }; socket.on('notification:new', handleNewNotification); return () => { mounted = false; socket.off('notification:new', handleNewNotification); }; } return () => { mounted = false; }; }, [user]); // Handle responsive behavior: sidebar open on desktop, closed on mobile useEffect(() => { const handleResize = () => { // 768px is the md breakpoint in Tailwind if (window.innerWidth >= 768) { setSidebarOpen(true); // Always open on desktop } else { setSidebarOpen(false); // Closed by default on mobile } }; // Set initial state handleResize(); // Add event listener window.addEventListener('resize', handleResize); // Cleanup return () => window.removeEventListener('resize', handleResize); }, []); return (
{/* Mobile Overlay */} {sidebarOpen && (
setSidebarOpen(false)} /> )} {/* Sidebar - Hidden on mobile by default, toggleable on desktop */} {/* Main Content Area */}
{/* Header */}
{/* Search bar commented out */} {/*
*/}

Notifications

{unreadCount > 0 && ( )}
{notifications.length === 0 ? (

No notifications yet

) : (
{notifications.map((notif) => (
handleNotificationClick(notif)} >
{!notif.isRead && (
)}

{notif.title}

{notif.message}

{formatDistanceToNow(new Date(notif.createdAt), { addSuffix: true })}

))}
)}
{notifications.length > 0 && (
)} {getUserInitials()} onNavigate?.('profile')}> Profile onNavigate?.('settings')}> Settings setShowLogoutDialog(true)} className="text-red-600 focus:text-red-600" > Logout
{/* Main Content */}
{children}
{/* Logout Confirmation Dialog */} Confirm Logout Are you sure you want to logout? You will need to sign in again to access your account. setShowLogoutDialog(false)}> Cancel { setShowLogoutDialog(false); if (onLogout) { try { await onLogout(); } catch (error) { console.error('🔴 Error calling onLogout:', error); } } else { console.error('🔴 ERROR: onLogout is undefined!'); } }} className="bg-red-600 hover:bg-red-700 text-white focus:ring-red-600" > Logout
); }