Merge branch 't3code/expand-research-management-plan'

# Conflicts:
#	app/analysis/page.tsx
#	app/watchlist/page.tsx
#	components/shell/app-shell.tsx
#	lib/api.ts
#	lib/query/options.ts
#	lib/server/api/app.ts
#	lib/server/db/index.test.ts
#	lib/server/db/index.ts
#	lib/server/db/schema.ts
#	lib/server/repos/research-journal.ts
#	lib/types.ts
This commit is contained in:
2026-03-07 20:39:49 -05:00
38 changed files with 5533 additions and 427 deletions

View File

@@ -2,7 +2,7 @@
import { useQueryClient } from '@tanstack/react-query';
import type { LucideIcon } from 'lucide-react';
import { Activity, BookOpenText, ChartCandlestick, Eye, Landmark, LineChart, LogOut, Menu, Search } from 'lucide-react';
import { Activity, BarChart3, BookOpenText, ChartCandlestick, Eye, Landmark, LineChart, LogOut, Menu, NotebookTabs, Search } from 'lucide-react';
import Link from 'next/link';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
@@ -11,6 +11,7 @@ import { TaskDetailModal } from '@/components/notifications/task-detail-modal';
import { TaskNotificationsTrigger } from '@/components/notifications/task-notifications-trigger';
import {
companyAnalysisQueryOptions,
companyFinancialStatementsQueryOptions,
filingsQueryOptions,
holdingsQueryOptions,
latestPortfolioInsightQueryOptions,
@@ -18,6 +19,7 @@ import {
recentTasksQueryOptions,
watchlistQueryOptions
} from '@/lib/query/options';
import { buildGraphingHref } from '@/lib/graphing/catalog';
import type { ActiveContext, NavGroup, NavItem } from '@/lib/types';
import { Button } from '@/components/ui/button';
import { useTaskNotificationsCenter } from '@/hooks/use-task-notifications-center';
@@ -56,6 +58,26 @@ const NAV_ITEMS: NavConfigItem[] = [
preserveTicker: true,
mobilePrimary: true
},
{
id: 'research',
href: '/research',
label: 'Research',
icon: NotebookTabs,
group: 'research',
matchMode: 'exact',
preserveTicker: true,
mobilePrimary: true
},
{
id: 'graphing',
href: '/graphing',
label: 'Graphing',
icon: BarChart3,
group: 'research',
matchMode: 'exact',
preserveTicker: true,
mobilePrimary: false
},
{
id: 'financials',
href: '/financials',
@@ -127,6 +149,10 @@ function toTickerHref(baseHref: string, activeTicker: string | null) {
}
function resolveNavHref(item: NavItem, context: ActiveContext) {
if (item.href === '/graphing') {
return buildGraphingHref(context.activeTicker);
}
if (!item.preserveTicker) {
return item.href;
}
@@ -144,6 +170,8 @@ function isItemActive(item: NavItem, pathname: string) {
function buildDefaultBreadcrumbs(pathname: string, activeTicker: string | null) {
const analysisHref = toTickerHref('/analysis', activeTicker);
const researchHref = toTickerHref('/research', activeTicker);
const graphingHref = buildGraphingHref(activeTicker);
const financialsHref = toTickerHref('/financials', activeTicker);
const filingsHref = toTickerHref('/filings', activeTicker);
@@ -163,6 +191,13 @@ function buildDefaultBreadcrumbs(pathname: string, activeTicker: string | null)
return [{ label: 'Analysis' }];
}
if (pathname.startsWith('/research')) {
return [
{ label: 'Analysis', href: analysisHref },
{ label: 'Research', href: researchHref }
];
}
if (pathname.startsWith('/financials')) {
return [
{ label: 'Analysis', href: analysisHref },
@@ -170,6 +205,14 @@ function buildDefaultBreadcrumbs(pathname: string, activeTicker: string | null)
];
}
if (pathname.startsWith('/graphing')) {
return [
{ label: 'Analysis', href: analysisHref },
{ label: 'Graphing', href: graphingHref },
{ label: activeTicker ?? 'Compare Set' }
];
}
if (pathname.startsWith('/filings')) {
return [
{ label: 'Analysis', href: analysisHref },
@@ -298,6 +341,20 @@ export function AppShell({ title, subtitle, actions, activeTicker, breadcrumbs,
return;
}
if (href.startsWith('/graphing')) {
if (context.activeTicker) {
void queryClient.prefetchQuery(companyFinancialStatementsQueryOptions({
ticker: context.activeTicker,
surfaceKind: 'income_statement',
cadence: 'annual',
includeDimensions: false,
includeFacts: false,
limit: 16
}));
}
return;
}
if (href.startsWith('/filings')) {
void queryClient.prefetchQuery(filingsQueryOptions({
ticker: context.activeTicker ?? undefined,
@@ -341,7 +398,7 @@ export function AppShell({ title, subtitle, actions, activeTicker, breadcrumbs,
};
const runPrefetch = () => {
const prioritized = navEntries.filter((entry) => entry.id === 'analysis' || entry.id === 'filings' || entry.id === 'portfolio');
const prioritized = navEntries.filter((entry) => entry.id === 'analysis' || entry.id === 'graphing' || entry.id === 'filings' || entry.id === 'portfolio');
for (const entry of prioritized) {
prefetchForHref(entry.href);
}