import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from "react" import { useNavigate, useLocation } from "react-router-dom" import { MessageSquare, Plus, LayoutDashboard, CalendarClock, CheckSquare, Trophy, User, LogOut, Trash2, MoreVertical, HelpCircle, BookOpen, Menu, X, } from "lucide-react" import { getConversations, createConversation } from "../api" import { useAuth } from "../auth/AuthContext" import { useI18n } from "../i18n/I18nContext" import { useIsMobile } from "../useMediaQuery" import HelpGuide from "./HelpGuide" import { T } from "../theme" const Sidebar = forwardRef(function Sidebar( { activeConversationId, onSelectConversation, onNewConversation, onDeleteConversation }, ref ) { const [conversations, setConversations] = useState([]) const [menu, setMenu] = useState(null) const [showHelp, setShowHelp] = useState(false) const [drawerOpen, setDrawerOpen] = useState(false) const navigate = useNavigate() const location = useLocation() const isMobile = useIsMobile() const { logout, user } = useAuth() const { t } = useI18n() const topNav = [ { icon: LayoutDashboard, label: t("nav_dashboard"), path: "/dashboard" }, { icon: MessageSquare, label: t("nav_chat"), path: "/chat" }, { icon: CalendarClock, label: t("nav_schedule"), path: "/schedule" }, { icon: CheckSquare, label: t("nav_today"), path: "/today" }, { icon: BookOpen, label: t("nav_medicines"), path: "/medicines" }, { icon: Trophy, label: t("nav_leaderboard"), path: "/leaderboard" }, { icon: User, label: t("nav_profile"), path: "/profile" }, ] useImperativeHandle(ref, () => ({ refresh: loadConversations, updateTitle: (id, title) => setConversations((prev) => prev.map((c) => (c.id === id ? { ...c, title } : c))), })) useEffect(() => { loadConversations() }, []) useEffect(() => { loadConversations() }, [activeConversationId]) useEffect(() => { setDrawerOpen(false) }, [location.pathname]) // close on navigation useEffect(() => { const close = () => setMenu(null) document.addEventListener("click", close) return () => document.removeEventListener("click", close) }, []) const loadConversations = async () => { try { setConversations(await getConversations()) } catch (e) { console.error(e) } } const go = (path) => { navigate(path); setDrawerOpen(false) } const handleNew = async () => { if (onNewConversation) { const id = await createConversation() await loadConversations() onNewConversation(id) } else { navigate("/chat") } setDrawerOpen(false) } const handleSelect = (id) => { if (onSelectConversation) onSelectConversation(id) else navigate("/chat") setDrawerOpen(false) } const openMenu = (e, convId) => { e.stopPropagation() const r = e.currentTarget.getBoundingClientRect() setMenu({ x: Math.min(r.left, window.innerWidth - 170), y: r.bottom + 4, convId }) } const handleDelete = async () => { if (!menu) return if (onDeleteConversation) await onDeleteConversation(menu.convId) await loadConversations() setMenu(null) } const panel = ( <>
💊
DoseMate
{t("portal_subtitle")}
{topNav.map(({ icon: Icon, label, path }) => { const active = location.pathname === path return (
go(path)} style={navItem(active)}> {label}
) })}
RECENT CHATS
{conversations.map((conv) => { const active = activeConversationId === conv.id return (
handleSelect(conv.id)} style={chatRow(active)}> {conv.title || `Consultation #${conv.number ?? conv.id}`}
) })}
{ setShowHelp(true); setDrawerOpen(false) }} style={navItem(false)}> {t("help")}
{(user?.name || "U").charAt(0).toUpperCase()}
{user?.name || "User"}
{user?.email}
) const menuEl = menu && (
(e.currentTarget.style.background = "#fef2f2")} onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}> Delete chat
) // ---------- Mobile: top bar + slide-in drawer ---------- if (isMobile) { return ( <>
💊 DoseMate
{drawerOpen &&
setDrawerOpen(false)} />}
{panel}
{menuEl} {showHelp && setShowHelp(false)} />} ) } // ---------- Desktop: fixed in-flow sidebar ---------- return (
{panel} {menuEl} {showHelp && setShowHelp(false)} />}
) }) const wrap = { width: 240, height: "100%", background: T.surface, borderInlineEnd: `1px solid ${T.border}`, display: "flex", flexDirection: "column", padding: "20px 14px", flexShrink: 0, gap: 16 } const drawer = { position: "fixed", insetInlineStart: 0, top: 0, height: "100%", width: 270, maxWidth: "82vw", background: T.surface, display: "flex", flexDirection: "column", padding: "18px 14px", gap: 14, zIndex: 1600, boxShadow: T.shadowLg, transition: "transform .25s ease", overflowY: "auto" } const drawerClose = { position: "absolute", insetInlineEnd: 10, top: 10, background: "transparent", border: "none", color: T.faint, cursor: "pointer", padding: 4 } const backdrop = { position: "fixed", inset: 0, background: "rgba(15,23,42,.45)", zIndex: 1550 } const topbar = { position: "fixed", top: 0, insetInlineStart: 0, right: 0, height: 52, background: T.surface, borderBottom: `1px solid ${T.border}`, display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0 12px", zIndex: 1400 } const hamburger = { width: 38, height: 38, borderRadius: 10, background: "#f0fdf4", border: "1px solid #bbf7d0", color: T.green, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer" } const logo = { width: 38, height: 38, borderRadius: 12, background: T.gradient, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 19, boxShadow: T.shadowGreen } const newBtn = { display: "flex", alignItems: "center", justifyContent: "center", gap: 7, padding: "11px", borderRadius: 12, background: T.gradient, color: "#fff", border: "none", fontSize: 13.5, fontWeight: 600, cursor: "pointer", boxShadow: T.shadowGreen } const navItem = (active) => ({ display: "flex", alignItems: "center", gap: 11, padding: "10px 12px", borderRadius: 11, cursor: "pointer", fontSize: 13.5, fontWeight: active ? 700 : 500, background: active ? "#f0fdf4" : "transparent", color: active ? T.green : T.muted }) const sectionLabel = { fontSize: 9.5, fontWeight: 700, color: T.faint, letterSpacing: ".09em", marginBottom: 8, paddingInlineStart: 4 } const chatRow = (active) => ({ display: "flex", alignItems: "center", gap: 8, padding: "8px 8px 8px 12px", borderRadius: 9, cursor: "pointer", fontSize: 12.5, userSelect: "none", background: active ? "#f0fdf4" : "transparent", color: active ? T.green : T.muted, fontWeight: active ? 600 : 400 }) const dotsBtn = { background: "transparent", border: "none", color: T.faint, cursor: "pointer", padding: 2, borderRadius: 6, flexShrink: 0, display: "flex" } const userRow = { display: "flex", alignItems: "center", gap: 9, padding: "9px 8px", marginTop: 2 } const avatar = { width: 32, height: 32, borderRadius: "50%", background: T.gradient, color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 14, fontWeight: 700, flexShrink: 0 } const logoutBtn = { background: "transparent", border: "none", color: T.faint, cursor: "pointer", padding: 6, borderRadius: 8, display: "flex" } const menuBox = { background: "#fff", border: `1px solid ${T.border}`, borderRadius: 10, boxShadow: T.shadowMd, zIndex: 1700, overflow: "hidden", minWidth: 150 } const menuItem = { display: "flex", alignItems: "center", gap: 8, padding: "10px 14px", cursor: "pointer", color: T.danger, fontSize: 13, fontWeight: 600 } export default Sidebar