Phase 5: alert subscriptions UI + multiple watchlists + queue fixes
CI / Test & Type-Check (push) Canceled after 0s
CI / Test & Type-Check (push) Canceled after 0s
UI: - /alerts page: event history with acknowledge, subscription create/manage with toggle - /admin/smtp: SMTP config form (host, port, auth, test) - Watchlist sidebar: dropdown selector for multiple watchlists, create/delete - Sidebar: alerts count badge, SMTP link under admin - Mobile tab nav: alerts tab added - Client trpc.ts: all new API methods + types Backend: - watchlists.listByWatchlist procedure + listSymbolsByWatchlist repo fn - yfinance min-interval 1500->2000ms to reduce Edge 429s - Fixed e.date.slice error in yfinance-adjustments with typeof guard - Removed defunct BITF from watchlist+queue - Cleared 83 failed + 12 backoff queue jobs Docs: - FUNCTIONAL_DESIGN.md: alerts + multiple watchlists + SMTP documented - TECH_DESIGN.md: new modules, tRPC procs, routes updated
This commit is contained in:
@@ -10,8 +10,10 @@ import {
|
||||
Settings,
|
||||
ChevronDown,
|
||||
Search,
|
||||
Bell,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import { api } from "@/lib/trpc";
|
||||
|
||||
/**
|
||||
* Left sidebar navigation with collapsible sections.
|
||||
@@ -58,6 +60,7 @@ const SECTIONS: NavSection[] = [
|
||||
{ label: "Risk Posture", href: "/risk" },
|
||||
{ label: "Filings", href: "/filings" },
|
||||
{ label: "Options DD", href: "/options-dd" },
|
||||
{ label: "Alerts", href: "/alerts" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -69,6 +72,7 @@ const SECTIONS: NavSection[] = [
|
||||
{ label: "Queue", href: "/admin/queue" },
|
||||
{ label: "Audit Logs", href: "/admin/audit-logs" },
|
||||
{ label: "X Accounts", href: "/admin/x-accounts" },
|
||||
{ label: "SMTP", href: "/admin/smtp" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -101,6 +105,11 @@ export function SidebarNav() {
|
||||
const normalizedPath = (pathname || "/").replace(/\/+$/, "") || "/";
|
||||
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const [unackedCount, setUnackedCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
api.alerts.unackedCount().then((r) => setUnackedCount(r.count)).catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCollapsed(getInitialCollapsed(pathname));
|
||||
@@ -146,6 +155,7 @@ export function SidebarNav() {
|
||||
<div className="mt-0.5 space-y-0.5">
|
||||
{section.items.map((item) => {
|
||||
const isActive = normalizedPath === item.href;
|
||||
const showBadge = item.label === "Alerts" && unackedCount > 0;
|
||||
return (
|
||||
<Link
|
||||
key={item.label}
|
||||
@@ -156,7 +166,12 @@ export function SidebarNav() {
|
||||
: "text-fg-muted hover:text-fg hover:bg-surface-overlay border-transparent"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
<span className="flex items-center justify-between">
|
||||
<span>{item.label}</span>
|
||||
{showBadge && (
|
||||
<span className="rounded-full bg-accent px-1.5 py-0.5 text-2xs text-white">{unackedCount}</span>
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user