From 5cc5c8af37fc2564366ac2518d431bb2971dde4c Mon Sep 17 00:00:00 2001 From: root Date: Wed, 6 May 2026 20:36:03 +0000 Subject: [PATCH] refactor(plans): centralize route helpers --- web/src/components/layout/AppShell.tsx | 7 ++++--- web/src/components/layout/Sidebar.tsx | 3 ++- web/src/views/PortfolioView.tsx | 13 +++++-------- web/src/views/SimpleView.tsx | 12 ++++++------ web/src/views/tradePlansRoutes.test.ts | 17 +++++++++++++++++ web/src/views/tradePlansRoutes.ts | 23 +++++++++++++++++++++++ 6 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 web/src/views/tradePlansRoutes.test.ts create mode 100644 web/src/views/tradePlansRoutes.ts diff --git a/web/src/components/layout/AppShell.tsx b/web/src/components/layout/AppShell.tsx index 3633a72..8ca03ed 100644 --- a/web/src/components/layout/AppShell.tsx +++ b/web/src/components/layout/AppShell.tsx @@ -4,6 +4,7 @@ import { Sidebar } from './Sidebar'; import { Header } from './Header'; import { RightPanel } from './RightPanel'; import { Button } from '../ui/button'; +import { getLegacySimpleRoute, getPlansRoute } from '../../views/tradePlansRoutes'; const HomeView = lazy(() => import('../../views/HomeView').then((mod) => ({ default: mod.HomeView }))); const PortfolioView = lazy(() => import('../../views/PortfolioView').then((mod) => ({ default: mod.PortfolioView }))); @@ -75,7 +76,7 @@ function NotFoundView() { function SimpleAliasRedirect() { const location = useLocation(); - return ; + return ; } export function AppShell() { @@ -98,8 +99,8 @@ export function AppShell() { } /> } /> } /> - } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/web/src/components/layout/Sidebar.tsx b/web/src/components/layout/Sidebar.tsx index ae349c1..3a48443 100644 --- a/web/src/components/layout/Sidebar.tsx +++ b/web/src/components/layout/Sidebar.tsx @@ -4,12 +4,13 @@ import { SlidersHorizontal, Star, Bell, Settings, } from 'lucide-react'; import { useAppContext } from '../../context/AppContext'; +import { getPlansRoute } from '../../views/tradePlansRoutes'; const NAV = [ { to: '/', icon: Home, label: 'Home', end: true }, { to: '/portfolio', icon: Briefcase, label: 'Portfolio', end: false }, { to: '/research', icon: FlaskConical, label: 'Research', end: false }, - { to: '/plans', icon: Target, label: 'Plans', end: false }, + { to: getPlansRoute(), icon: Target, label: 'Plans', end: false }, { to: '/markets', icon: TrendingUp, label: 'Markets', end: false }, { to: '/screener', icon: SlidersHorizontal, label: 'Screener', end: false }, { to: '/watchlist', icon: Star, label: 'Watchlist', end: false }, diff --git a/web/src/views/PortfolioView.tsx b/web/src/views/PortfolioView.tsx index 2621602..0f95545 100644 --- a/web/src/views/PortfolioView.tsx +++ b/web/src/views/PortfolioView.tsx @@ -4,6 +4,7 @@ import { useAppContext } from '../context/AppContext'; import { PositionsTab } from '../tabs/PositionsTab'; import { HistoryTab } from '../tabs/HistoryTab'; import { PageHeader } from '../components/ui/page-header'; +import { buildCreateExitPlanUrl, buildPlanDrillInUrl } from './tradePlansRoutes'; const TABS = ['Positions & Orders', 'Trade History'] as const; type Tab = typeof TABS[number]; @@ -37,14 +38,10 @@ export function PortfolioView() { { - const params = action === 'open-plan' && position.planEntryId - ? new URLSearchParams({ setupId: position.planEntryId }) - : new URLSearchParams({ - mode: 'sell', - symbol: position.symbol, - }); - if (action !== 'open-plan' && position.tradeId) params.set('tradeId', position.tradeId); - navigate(`/plans?${params.toString()}`); + const target = action === 'open-plan' && position.planEntryId + ? buildPlanDrillInUrl(position.planEntryId) + : buildCreateExitPlanUrl(position.symbol, position.tradeId); + navigate(target); }} /> )} diff --git a/web/src/views/SimpleView.tsx b/web/src/views/SimpleView.tsx index 5d2936f..f6bc258 100644 --- a/web/src/views/SimpleView.tsx +++ b/web/src/views/SimpleView.tsx @@ -705,7 +705,7 @@ export function SimpleView() { setSavedSetups(normalizeSimpleEntries(entryRows)); } catch (err: any) { if (cancelled) return; - dispatch({ type: 'set-error', value: err?.message ?? 'Failed to load Simple setups' }); + dispatch({ type: 'set-error', value: err?.message ?? 'Failed to load Trade Plans' }); } } @@ -884,13 +884,13 @@ export function SimpleView() { if (editingSetupId) { await updateManualEntry(editingSetupId, payload); - dispatch({ type: 'set-message', value: `Updated ${normalizedSymbol} Simple setup.` }); + dispatch({ type: 'set-message', value: `Updated ${normalizedSymbol} trade plan.` }); } else { await createManualEntry({ ...payload, stock_instance_id: crypto.randomUUID(), }); - dispatch({ type: 'set-message', value: `Saved ${normalizedSymbol} Simple setup.` }); + dispatch({ type: 'set-message', value: `Saved ${normalizedSymbol} trade plan.` }); } await refreshSetupList(); @@ -900,7 +900,7 @@ export function SimpleView() { marketPriceSource: draft.currentMarketPrice ? marketPriceSource : null, }); } catch (err: any) { - dispatch({ type: 'set-error', value: err?.message ?? 'Failed to save Simple setup' }); + dispatch({ type: 'set-error', value: err?.message ?? 'Failed to save Trade Plan' }); } finally { setSubmitting(false); } @@ -915,7 +915,7 @@ export function SimpleView() { } async function handleDelete(entryId: string) { - if (!window.confirm('Delete this Simple setup?')) return; + if (!window.confirm('Delete this Trade Plan?')) return; try { await deleteManualEntry(entryId); if (editingSetupId === entryId) { @@ -923,7 +923,7 @@ export function SimpleView() { } await refreshSetupList(); } catch (err: any) { - dispatch({ type: 'set-error', value: err?.message ?? 'Failed to delete Simple setup' }); + dispatch({ type: 'set-error', value: err?.message ?? 'Failed to delete Trade Plan' }); } } diff --git a/web/src/views/tradePlansRoutes.test.ts b/web/src/views/tradePlansRoutes.test.ts new file mode 100644 index 0000000..1202c0c --- /dev/null +++ b/web/src/views/tradePlansRoutes.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest'; +import { + buildCreateExitPlanUrl, + buildPlanDrillInUrl, + getLegacySimpleRoute, + getPlansRoute, +} from './tradePlansRoutes'; + +describe('tradePlansRoutes', () => { + it('builds the canonical plans routes consistently', () => { + expect(getPlansRoute()).toBe('/plans'); + expect(getLegacySimpleRoute()).toBe('/simple'); + expect(buildPlanDrillInUrl('setup-1')).toBe('/plans?setupId=setup-1'); + expect(buildCreateExitPlanUrl('MSFT', 'TRD-MSFT')).toBe('/plans?mode=sell&symbol=MSFT&tradeId=TRD-MSFT'); + expect(buildCreateExitPlanUrl('AAPL')).toBe('/plans?mode=sell&symbol=AAPL'); + }); +}); diff --git a/web/src/views/tradePlansRoutes.ts b/web/src/views/tradePlansRoutes.ts new file mode 100644 index 0000000..3ecb66a --- /dev/null +++ b/web/src/views/tradePlansRoutes.ts @@ -0,0 +1,23 @@ +const PLANS_ROUTE = '/plans'; +const LEGACY_SIMPLE_ROUTE = '/simple'; + +export function getPlansRoute() { + return PLANS_ROUTE; +} + +export function getLegacySimpleRoute() { + return LEGACY_SIMPLE_ROUTE; +} + +export function buildPlanDrillInUrl(setupId: string) { + return `${PLANS_ROUTE}?${new URLSearchParams({ setupId }).toString()}`; +} + +export function buildCreateExitPlanUrl(symbol: string, tradeId?: string | null) { + const params = new URLSearchParams({ + mode: 'sell', + symbol, + }); + if (tradeId) params.set('tradeId', tradeId); + return `${PLANS_ROUTE}?${params.toString()}`; +}