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 (
<>