Fundamental_Analysis/frontend/archive/v1_report/ReportHeader.tsx
Lv, Qi 0cb31e363e Refactor E2E tests and improve error handling in Orchestrator
- Fix `simple_test_analysis` template in E2E test setup to align with Orchestrator's data fetch logic.
- Implement and verify additional E2E scenarios:
    - Scenario C: Partial Provider Failure (verified error propagation fix in Orchestrator).
    - Scenario D: Invalid Symbol input.
    - Scenario E: Analysis Module failure.
- Update `WorkflowStateMachine::handle_report_failed` to correctly scope error broadcasting to the specific task instead of failing effectively silently or broadly.
- Update testing strategy documentation to reflect completed Phase 4 testing.
- Skip Scenario B (Orchestrator Restart) as persistence is not yet implemented (decision made to defer persistence).
2025-11-21 20:44:32 +08:00

165 lines
5.6 KiB
TypeScript

import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
interface ReportHeaderProps {
unifiedSymbol: string;
displayMarket: string;
isLoading: boolean;
financials: any;
snapshot: any;
snapshotLoading: boolean;
triggering: boolean;
hasRunningTask: boolean;
isAnalysisRunning: boolean;
onStartAnalysis: () => void;
onStopAnalysis: () => void;
onContinueAnalysis: () => void;
// Template props
templateSets: any;
selectedTemplateId: string;
onSelectTemplate: (id: string) => void;
}
export function ReportHeader({
unifiedSymbol,
displayMarket,
isLoading,
financials,
snapshot,
snapshotLoading,
triggering,
hasRunningTask,
isAnalysisRunning,
onStartAnalysis,
onStopAnalysis,
onContinueAnalysis,
templateSets,
selectedTemplateId,
onSelectTemplate,
}: ReportHeaderProps) {
return (
<>
<Card className="flex-1">
<CardHeader>
<CardTitle className="text-xl"></CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-20">:</span>
<span className="font-medium">{unifiedSymbol}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-20">:</span>
<span className="font-medium">{displayMarket}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-20">:</span>
<span className="font-medium">
{isLoading ? (
<span className="flex items-center gap-1">
<Spinner className="size-3" />
<span className="text-muted-foreground">...</span>
</span>
) : financials?.name ? (
financials.name
) : (
<span className="text-muted-foreground">-</span>
)}
</span>
</div>
</CardContent>
</Card>
<Card className="w-80 flex-shrink-0">
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-3">
<div className="space-y-1">
<label className="text-xs font-medium"></label>
<Select value={selectedTemplateId} onValueChange={onSelectTemplate} disabled={triggering}>
<SelectTrigger>
<SelectValue placeholder="选择分析模板" />
</SelectTrigger>
<SelectContent>
{templateSets && Object.entries(templateSets).map(([id, set]: [string, any]) => (
<SelectItem key={id} value={id}>
{set.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap gap-2">
<Button
onClick={onStartAnalysis}
disabled={triggering || !selectedTemplateId}
className="flex-1"
>
{triggering ? '触发中…' : '触发分析'}
</Button>
<Button variant="destructive" onClick={onStopAnalysis} disabled={!hasRunningTask}>
</Button>
<Button variant="outline" onClick={onContinueAnalysis} disabled={isAnalysisRunning}>
</Button>
</div>
</CardContent>
</Card>
<Card className="w-64 flex-shrink-0">
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="text-sm">
<div className="grid grid-cols-2 gap-x-6 gap-y-3">
<SnapshotItem
label="PB"
value={snapshot?.pb != null ? `${Number(snapshot.pb).toFixed(2)}` : undefined}
loading={snapshotLoading}
/>
<SnapshotItem
label="股价"
value={snapshot?.close != null ? `${Number(snapshot.close).toFixed(2)}` : undefined}
loading={snapshotLoading}
/>
<SnapshotItem
label="PE"
value={snapshot?.pe != null ? `${Number(snapshot.pe).toFixed(2)}` : undefined}
loading={snapshotLoading}
/>
<SnapshotItem
label="市值"
value={snapshot?.total_mv != null ? `${Math.round((snapshot.total_mv as number) / 10000).toLocaleString('zh-CN')} 亿元` : undefined}
loading={snapshotLoading}
/>
</div>
</CardContent>
</Card>
</>
);
}
function SnapshotItem({ label, value, loading }: { label: string; value?: string; loading: boolean }) {
return (
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-8">{label}:</span>
<span className="font-medium">
{loading ? (
<span className="flex items-center gap-1">
<Spinner className="size-3" />
</span>
) : value ? (
value
) : (
<span className="text-muted-foreground">-</span>
)}
</span>
</div>
);
}