refactor(plans): centralize route helpers

This commit is contained in:
root 2026-05-06 20:36:03 +00:00
parent 26dfb575be
commit 5cc5c8af37
6 changed files with 57 additions and 18 deletions

View File

@ -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 <Navigate to={`/plans${location.search}${location.hash}`} replace />;
return <Navigate to={`${getPlansRoute()}${location.search}${location.hash}`} replace />;
}
export function AppShell() {
@ -98,8 +99,8 @@ export function AppShell() {
<Route path="/" element={<HomeView />} />
<Route path="/portfolio" element={<PortfolioView />} />
<Route path="/research" element={<ResearchView />} />
<Route path="/plans" element={<SimpleView />} />
<Route path="/simple" element={<SimpleAliasRedirect />} />
<Route path={getPlansRoute()} element={<SimpleView />} />
<Route path={getLegacySimpleRoute()} element={<SimpleAliasRedirect />} />
<Route path="/markets" element={<MarketsView />} />
<Route path="/screener" element={<ScreenerView />} />
<Route path="/watchlist" element={<WatchlistView />} />

View File

@ -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 },

View File

@ -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() {
<PositionsTab
botState={botState}
onManageHolding={(position, action) => {
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);
}}
/>
)}

View File

@ -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' });
}
}

View File

@ -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');
});
});

View File

@ -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()}`;
}