Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/web/src/components/layout/ConsoleChrome.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export const appShell = style({
minHeight: '100vh',
gap: 0,
padding: 0,
transition: 'grid-template-columns 0.25s ease',
});

export const appShellCollapsed = style({
gridTemplateColumns: '60px 1fr',
});

globalStyle(`${appShell}::before`, {
Expand Down Expand Up @@ -86,6 +91,37 @@ globalStyle(`${sidebar}::before`, {
pointerEvents: 'none',
});

export const sidebarCollapsed = style({
width: '60px',
padding: '24px 10px',
alignItems: 'center',
});

export const sidebarToggle = style({
position: 'absolute',
top: '24px',
right: '-13px',
zIndex: 10,
width: '26px',
height: '26px',
borderRadius: '50%',
border: '1px solid var(--line-strong)',
background: 'var(--panel-2)',
color: 'var(--muted)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '12px',
transition: 'border-color 160ms ease, color 160ms ease, background 160ms ease',
flexShrink: 0,
':hover': {
borderColor: 'var(--accent-live)',
color: 'var(--accent-live)',
background: 'rgba(0, 212, 255, 0.08)',
},
});

export const brand = style({
display: 'flex',
gap: '12px',
Expand Down
84 changes: 70 additions & 14 deletions apps/web/src/components/layout/ConsoleChrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { useTradingSystem } from '../../store/trading-system/TradingSystemProvider.tsx';
import {
appShell,
appShellCollapsed,
brand,
brandMark,
brandName,
Expand All @@ -30,7 +31,9 @@ import {
navStack,
sidebar,
sidebarBlock,
sidebarCollapsed,
sidebarLabel,
sidebarToggle,
toolbarActions,
toolbarCopy,
toolbarKicker,
Expand Down Expand Up @@ -275,30 +278,72 @@ export function TopMeta({ items }: { items: TopMetaItem[] }) {
);
}

function Sidebar() {
function Sidebar({ collapsed, onToggle }: { collapsed: boolean; onToggle: () => void }) {
const { locale } = useLocale();
const routes = listSidebarRoutes();

return (
<aside className={sidebar}>
<div className={brand}>
<div className={brandMark} />
<div>
<div className={brandName}>{copy[locale].product}</div>
<div className={brandSub}>{copy[locale].tagline}</div>
<aside
className={`${sidebar}${collapsed ? ` ${sidebarCollapsed}` : ''}`}
style={{ position: 'relative' }}
>
<button
type="button"
className={sidebarToggle}
onClick={onToggle}
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{collapsed ? '›' : '‹'}
</button>

{!collapsed && (
<div className={brand}>
<div className={brandMark} />
<div>
<div className={brandName}>{copy[locale].product}</div>
<div className={brandSub}>{copy[locale].tagline}</div>
</div>
</div>
</div>
)}

<div className={sidebarBlock}>
<div className={sidebarLabel}>{copy[locale].labels.tacticalRoutes}</div>
{collapsed && (
<div
style={{
marginBottom: '24px',
paddingBottom: '18px',
borderBottom: '1px solid var(--line)',
display: 'flex',
justifyContent: 'center',
}}
>
<div className={brandMark} />
</div>
)}

<div className={sidebarBlock} style={collapsed ? { padding: '10px 6px' } : undefined}>
{!collapsed && <div className={sidebarLabel}>{copy[locale].labels.tacticalRoutes}</div>}
<nav className={navStack}>
{routes.map((route) => (
<NavLink
key={route.path}
to={route.path}
className={({ isActive }) => `nav-link${isActive ? ' active' : ''}`}
className={({ isActive }) =>
`nav-link${isActive ? ' active' : ''}${collapsed ? ' nav-link-icon-only' : ''}`
}
title={collapsed ? copy[locale].nav[route.id] : undefined}
style={
collapsed
? {
padding: '10px',
textAlign: 'center',
fontSize: '10px',
letterSpacing: '0.05em',
}
: undefined
}
>
{copy[locale].nav[route.id]}
{collapsed ? copy[locale].nav[route.id].slice(0, 2) : copy[locale].nav[route.id]}
</NavLink>
))}
</nav>
Expand Down Expand Up @@ -468,14 +513,25 @@ export function EmptyState({ icon, message, detail }: EmptyStateProps) {
export function Layout() {
const location = useLocation();
const { locale } = useLocale();
const [collapsed, setCollapsed] = useState(() => {
return window.localStorage.getItem('quantpilot-sidebar-collapsed') === 'true';
});

const handleToggle = () => {
setCollapsed((prev) => {
const next = !prev;
window.localStorage.setItem('quantpilot-sidebar-collapsed', String(next));
return next;
});
};

useEffect(() => {
document.title = getConsoleDocumentTitle(locale, location.pathname);
}, [locale, location.pathname]);

return (
<div className={appShell}>
<Sidebar />
<div className={`${appShell}${collapsed ? ` ${appShellCollapsed}` : ''}`}>
<Sidebar collapsed={collapsed} onToggle={handleToggle} />
<main className={mainPanel}>
<GlobalToolbar />
<Outlet />
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/modules/console/console.i18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export const copy = {
nav: {
dashboard: '仪表盘',
market: '市场监控',
trading: '交易终端',
strategies: '策略工作台',
'strategy-detail': '策略详情',
backtest: '回测中心',
risk: '风控面板',
agent: 'Agent 对话台',
Expand Down Expand Up @@ -133,6 +135,7 @@ export const copy = {
'市场监控',
'跟踪股票池价格、强弱排序和行情接入状态,为策略与 Agent 提供市场上下文。',
],
trading: ['交易终端', '在一个界面内完成行情监控、图表分析和下单操作。'],
strategies: [
'策略工作台',
'查看策略候选、信号分布、股票池评分和研究视图,形成可执行的策略工作流。',
Expand Down Expand Up @@ -197,7 +200,9 @@ export const copy = {
nav: {
dashboard: 'Dashboard',
market: 'Market',
trading: 'Trading',
strategies: 'Strategies',
'strategy-detail': 'Strategy Detail',
backtest: 'Backtest',
risk: 'Risk',
agent: 'Agent',
Expand Down Expand Up @@ -314,6 +319,10 @@ export const copy = {
'Market Monitor',
'Track universe pricing, relative strength, and market data connectivity for downstream analysis.',
],
trading: [
'Trading Terminal',
'Monitor the market, read charts, and place orders from one unified trading interface.',
],
strategies: [
'Strategy Workspace',
'Review strategy candidates, signal mix, universe scores, and research context in one workspace.',
Expand Down Expand Up @@ -355,6 +364,7 @@ export const copy = {
desk: {
dashboard: 'Command Deck',
market: 'Market Desk',
trading: 'Trading Desk',
strategies: 'Strategy Desk',
backtest: 'Research Desk',
risk: 'Risk Desk',
Expand Down
24 changes: 24 additions & 0 deletions apps/web/src/modules/console/console.routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,22 @@ const SettingsPage = lazy(() =>
const NotificationsPage = lazy(() => import('../../pages/notifications/NotificationsPage.tsx'));
const BacktestPage = lazy(() => import('../../pages/backtest/BacktestPage.tsx'));
const StrategiesPage = lazy(() => import('../../pages/strategies/StrategiesPage.tsx'));
const StrategyDetailPage = lazy(() =>
import('../../pages/strategies/StrategyDetailPage.tsx').then((m) => ({
default: m.StrategyDetailPage,
}))
);
const RiskPage = lazy(() => import('../../pages/risk/RiskPage.tsx'));
const TradingPage = lazy(() =>
import('../../pages/trading/TradingPage.tsx').then((m) => ({ default: m.TradingPage }))
);

type ConsoleNavKey =
| 'dashboard'
| 'market'
| 'trading'
| 'strategies'
| 'strategy-detail'
| 'backtest'
| 'risk'
| 'execution'
Expand Down Expand Up @@ -66,6 +76,13 @@ export const CONSOLE_ROUTES: ConsoleRouteDefinition[] = [
includeInSidebar: true,
element: renderLazyRoute(<MarketPage />),
},
{
id: 'trading',
pageKey: 'trading',
path: '/trading',
includeInSidebar: true,
element: renderLazyRoute(<TradingPage />),
},
{
id: 'strategies',
pageKey: 'strategies',
Expand All @@ -74,6 +91,13 @@ export const CONSOLE_ROUTES: ConsoleRouteDefinition[] = [
includeInSidebar: true,
element: renderLazyRoute(<StrategiesPage />),
},
{
id: 'strategy-detail',
pageKey: 'strategies',
path: '/strategies/:id',
includeInSidebar: false,
element: renderLazyRoute(<StrategyDetailPage />),
},
{
id: 'backtest',
pageKey: 'backtest',
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/modules/research/StrategyCatalogRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function StrategyCatalogRow(props: {
onPromote: (item: StrategyCatalogItem) => void;
onArchiveToggle: (item: StrategyCatalogItem) => void;
onInspect: (strategyId: string) => void;
onViewDetail?: (strategyId: string) => void;
}) {
const { locale, item, nextStage, canWriteStrategy, saving, promotingId, selectedStrategyId } =
props;
Expand Down Expand Up @@ -99,6 +100,15 @@ export function StrategyCatalogRow(props: {
? '查看'
: 'Inspect'}
</button>
{props.onViewDetail ? (
<button
type="button"
className="inline-action inline-action-approve"
onClick={() => props.onViewDetail!(item.id)}
>
{locale === 'zh' ? '详情页' : 'Detail'}
</button>
) : null}
</div>
}
/>
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/pages/strategies/StrategiesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,7 @@ function StrategiesPage() {
onPromote={handlePromoteStrategy}
onArchiveToggle={handleArchiveStrategy}
onInspect={setSelectedStrategyId}
onViewDetail={(id) => navigate(`/strategies/${id}`)}
/>
))}
{visibleArchivedStrategies.length ? (
Expand All @@ -775,6 +776,7 @@ function StrategiesPage() {
onPromote={handlePromoteStrategy}
onArchiveToggle={handleArchiveStrategy}
onInspect={setSelectedStrategyId}
onViewDetail={(id) => navigate(`/strategies/${id}`)}
/>
))}
</ResearchTerminalPanel>
Expand Down
Loading
Loading