Files
WealthySmart/frontend/src/components/app-sidebar.tsx
Carlos Escalante d9039c76b9 Replace hand-rolled shell with shadcn sidebar + breadcrumb header
AppSidebar (icon-collapsible, sync warning badge, brand header),
NavUser footer (avatar + privacy/theme/logout dropdown), and a thin
header with SidebarTrigger, breadcrumbs, and quick toggles. Mobile
drawer and cmd+B come from the sidebar primitives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 10:46:34 -06:00

101 lines
3.3 KiB
TypeScript

import { Wallet } from "lucide-react";
import { Link, useLocation } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { getSyncStatus } from "@/lib/api";
import { isNavActive, navSections } from "@/lib/navigation";
import { NavUser } from "@/components/nav-user";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
useSidebar,
} from "@/components/ui/sidebar";
export function AppSidebar(props: React.ComponentProps<typeof Sidebar>) {
const { pathname } = useLocation();
const { setOpenMobile } = useSidebar();
// Surface stalled ingestion sources as a dot on the Sincronización item.
const syncQ = useQuery({
queryKey: ["sync-status"],
queryFn: () => getSyncStatus().then((r) => r.data),
staleTime: 5 * 60_000,
});
const syncWarnings = syncQ.data?.warnings ?? 0;
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
size="lg"
render={<Link to="/" aria-label="Ir a Inicio" />}
>
<div className="bg-primary text-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<Wallet className="size-4" strokeWidth={2.5} />
</div>
<span
className="text-base font-bold tracking-tight"
style={{ fontFamily: "var(--font-heading)" }}
>
Wealthy<span className="text-primary">Smart</span>
</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
{navSections.map((section) => (
<SidebarGroup key={section.label}>
<SidebarGroupLabel>{section.label}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{section.items.map(({ to, icon: Icon, label }) => (
<SidebarMenuItem key={to}>
<SidebarMenuButton
isActive={isNavActive(to, pathname)}
tooltip={label}
render={
<Link to={to} onClick={() => setOpenMobile(false)} />
}
>
<Icon />
<span>{label}</span>
</SidebarMenuButton>
{to === "/sync" && syncWarnings > 0 && (
<SidebarMenuBadge>
<span
className="size-2 rounded-full bg-amber-500"
title={`${syncWarnings} fuente(s) atrasada(s)`}
aria-label={`${syncWarnings} fuentes de datos atrasadas`}
/>
</SidebarMenuBadge>
)}
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarFooter>
<NavUser />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}