refactor(plans): centralize route helpers
This commit is contained in:
parent
26dfb575be
commit
5cc5c8af37
@ -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 />} />
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
web/src/views/tradePlansRoutes.test.ts
Normal file
17
web/src/views/tradePlansRoutes.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
23
web/src/views/tradePlansRoutes.ts
Normal file
23
web/src/views/tradePlansRoutes.ts
Normal 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()}`;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user