import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { Settings, LogOut, Bell, MessageSquare, Search, Truck, AlertTriangle, ArrowRight, Send, CheckCircle2 } from 'lucide-react'; import { TopNav, TopNavHeading } from '@astryxdesign/core/TopNav'; import { DropdownMenu } from '@astryxdesign/core/DropdownMenu'; import { TextInput } from '@astryxdesign/core/TextInput'; import { Avatar } from '@astryxdesign/core/Avatar'; import Button from '@/components/Button'; import Logo from '@/components/Logo'; import { getStaff, getHubContext, clearSession } from '@/auth/session'; import { getNotifications, markNotificationRead, getConversations, getConversation, sendMessage, markConversationRead } from '@/api/hub'; const RED = 'var(--color-brand)'; const NOTIF_ICON = { exception: AlertTriangle, inbound: Truck, dispatch: Truck, warning: AlertTriangle, alert: Bell }; const toInitials = (name) => (name || '') .split(' ') .filter(Boolean) .map((w) => w[0]) .slice(0, 2) .join('') .toUpperCase() || 'HB'; const toConversation = (c) => { const name = c.name || c.milername || c.displayname || `Miler ${c.mileruserid ?? c.id}`; return { id: c.id ?? c.conversationid ?? c.mileruserid, name, initials: toInitials(name), lastMessage: c.lastmessage ?? c.last_message ?? c.preview ?? '', time: c.time ?? c.lasttime ?? c.updatedat ?? '', unread: Number(c.unread ?? c.unreadcount ?? 0) || 0 }; }; const toMessage = (m) => ({ sender: m.sender === 'me' ? 'me' : 'them', text: m.text ?? m.body ?? m.message ?? '', time: m.time ?? m.createdat ?? m.sentat ?? '' }); export default function Header({ isSidebarCollapsed }) { const navigate = useNavigate(); const staff = getStaff(); const hub = getHubContext(); const staffName = staff.displayname || 'Hub Staff'; const hubName = hub.hubname || 'Doormile Hub'; const handleLogout = () => { clearSession(); navigate('/login'); }; const [selectedNotif, setSelectedNotif] = useState(null); const [conversations, setConversations] = useState([]); const [activeChat, setActiveChat] = useState(null); const [chatLoading, setChatLoading] = useState(false); const [sending, setSending] = useState(false); const [typedMessage, setTypedMessage] = useState(''); const unreadMessages = conversations.reduce((sum, c) => sum + c.unread, 0); const [notifications, setNotifications] = useState([]); const [search, setSearch] = useState(''); const unread = notifications.filter((n) => !n.read).length; const loadNotifications = useCallback(async () => { try { const res = await getNotifications(); setNotifications( (res?.data || []).map((n) => ({ id: n.id, title: n.title, time: n.time, read: Boolean(n.read), type: n.type, icon: NOTIF_ICON[n.type] || Bell, desc: n.desc, stats: n.stats || [], to: n.to, actionText: n.actionText })) ); } catch { setNotifications([]); } }, []); useEffect(() => { loadNotifications(); const t = setInterval(loadNotifications, 60000); return () => clearInterval(t); }, [loadNotifications]); const loadConversations = useCallback(async () => { try { const res = await getConversations(); setConversations((res?.data || []).map(toConversation)); } catch { setConversations([]); } }, []); useEffect(() => { loadConversations(); const t = setInterval(loadConversations, 60000); return () => clearInterval(t); }, [loadConversations]); const markAllRead = async () => { const unreadIds = notifications.filter((n) => !n.read).map((n) => n.id); setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))); await Promise.allSettled(unreadIds.map((id) => markNotificationRead(id))); }; const onNotifClick = async (n) => { setNotifications((prev) => prev.map((x) => (x.id === n.id ? { ...x, read: true } : x))); if (n.desc || n.stats) setSelectedNotif(n); else if (n.to) navigate(n.to); try { await markNotificationRead(n.id); } catch {} }; const onMessageClick = async (conv) => { setTypedMessage(''); setActiveChat({ id: conv.id, name: conv.name, initials: conv.initials, messages: [] }); setChatLoading(true); try { const res = await getConversation(conv.id); const thread = res?.data || {}; setActiveChat({ id: conv.id, name: thread.name || conv.name, initials: toInitials(thread.name || conv.name), messages: (thread.messages || thread.chat || []).map(toMessage) }); if (conv.unread > 0) { await markConversationRead(conv.id).catch(() => {}); setConversations((prev) => prev.map((c) => (c.id === conv.id ? { ...c, unread: 0 } : c))); } } catch { } finally { setChatLoading(false); } }; const closeChat = () => { setActiveChat(null); setTypedMessage(''); }; const handleSendMessage = async () => { const text = typedMessage.trim(); if (!text || !activeChat || sending) return; setSending(true); const now = new Date(); const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); setActiveChat((c) => ({ ...c, messages: [...c.messages, { sender: 'me', text, time: timeStr }] })); setTypedMessage(''); try { await sendMessage(activeChat.id, text); const res = await getConversation(activeChat.id); const thread = res?.data || {}; setActiveChat((c) => c && { ...c, messages: (thread.messages || thread.chat || []).map(toMessage) }); setConversations((prev) => prev.map((c) => (c.id === activeChat.id ? { ...c, lastMessage: text, time: timeStr } : c)) ); } catch { setActiveChat((c) => c && { ...c, messages: c.messages.filter((m) => !(m.sender === 'me' && m.text === text && m.time === timeStr)) }); setTypedMessage(text); } finally { setSending(false); } }; const submitSearch = (e) => { e.preventDefault(); const q = search.trim(); if (q) navigate(`/routing?q=${encodeURIComponent(q)}`); }; return ( <> } headingHref="/dashboard" /> } endContent={
{unreadMessages > 0 && {unreadMessages}}
}} items={[ { label: 'Messages', type: 'label' }, ...conversations.map(m => ({ label: m.name, description: m.lastMessage, onClick: () => onMessageClick(m) })) ]} /> {unread > 0 && {unread}} }} items={[ { label: 'Notifications', type: 'label' }, { label: 'Mark all read', icon: , onClick: markAllRead, disabled: unread === 0 }, { type: 'divider' }, ...(notifications.length === 0 ? [{ label: 'No notifications', disabled: true }] : notifications.map((n, i) => ({ label: n.title + '\u200B'.repeat(i), description: n.time, icon: n.icon ? (() => { const Icon = n.icon; return ; })() : undefined, onClick: () => onNotifClick(n) }))) ]} /> , label: staffName }} items={[ { label: 'Settings', icon: , onClick: () => navigate('/hub-settings') }, { type: 'divider' }, { label: 'Logout', icon: , onClick: handleLogout } ]} /> } /> {selectedNotif && (

{selectedNotif.title}

{selectedNotif.desc}

{selectedNotif.stats.map((s, i) => (
{s.label}
{s.value}
))}
{selectedNotif.actionText && ( )}
)} {activeChat && (
{activeChat.initials}

{activeChat.name}

{chatLoading ? (
Loading...
) : activeChat.messages.length === 0 ? (
No messages yet.
) : ( activeChat.messages.map((m, i) => (
{m.text}
{m.time}
)) )}
setTypedMessage(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()} placeholder="Type a message..." />
)} ); }