Files
AIclinicalresearch/frontend/src/pages/rvw/components/MethodologyReport.tsx
HaHafeng 179afa2c6b feat(rvw): Complete RVW module development Phase 1-3
Summary:
- Migrate backend to modules/rvw with v2 API routes (/api/v2/rvw)
- Add new database fields: selectedAgents, editorialScore, methodologyStatus, picoExtract, isArchived
- Create frontend module in frontend-v2/src/modules/rvw
- Implement Dashboard with task list, filtering, batch operations
- Implement ReportDetail with dual tabs (editorial/methodology)
- Implement AgentModal for intelligent agent selection
- Register RVW module in moduleRegistry.ts
- Add navigation entry in TopNavigation
- Update documentation for RVW module status (v3.0)
- Update system status document (v2.9)

Features:
- User can select agents: editorial, methodology, or both
- Support batch task execution
- Task status filtering
- Replace console.log with logger service
- Maintain v1 API backward compatibility

Tested: Frontend and backend verified locally
Status: 85% complete (Phase 1-3 done)
2026-01-07 22:39:08 +08:00

114 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 方法学评估报告组件
*/
import { XCircle, AlertTriangle, CheckCircle } from 'lucide-react';
import type { MethodologyReviewResult } from '../types';
import ScoreRing from './ScoreRing';
interface MethodologyReportProps {
data: MethodologyReviewResult;
}
export default function MethodologyReport({ data }: MethodologyReportProps) {
const getSeverityStyle = (severity: 'major' | 'minor') => {
return severity === 'major'
? { border: 'border-red-200', bg: 'bg-red-50', icon: <XCircle className="w-4 h-4 text-red-500" />, label: '严重' }
: { border: 'border-amber-200', bg: 'bg-amber-50', icon: <AlertTriangle className="w-4 h-4 text-amber-500" />, label: '轻微' };
};
const getOverallStatus = () => {
if (data.overall_score >= 80) return { label: '通过', color: 'text-green-700', bg: 'bg-green-50' };
if (data.overall_score >= 60) return { label: '存疑', color: 'text-amber-700', bg: 'bg-amber-50' };
return { label: '不通过', color: 'text-red-700', bg: 'bg-red-50' };
};
const status = getOverallStatus();
return (
<div className="space-y-6 fade-in">
{/* 总分卡片 */}
<div className={`bg-white p-6 rounded-2xl shadow-sm border flex items-center gap-8 ${
data.overall_score >= 80 ? 'border-green-200' :
data.overall_score >= 60 ? 'border-amber-200' : 'border-red-200'
}`}>
<ScoreRing score={data.overall_score} size="medium" />
<div className="flex-1">
<h3 className="font-bold text-lg text-slate-800 flex items-center gap-2">
<span className={`text-sm px-2 py-0.5 rounded ${status.bg} ${status.color}`}>
{status.label}
</span>
</h3>
<p className="text-slate-600 text-sm mt-1">{data.summary}</p>
</div>
</div>
{/* 分项详情 */}
{data.parts.map((part, partIndex) => (
<div key={partIndex} className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div className="p-4 bg-slate-50 border-b border-gray-200 flex justify-between items-center">
<span className="font-bold text-sm text-slate-700">{part.part}</span>
<div className="flex items-center gap-2">
<span className="text-xs text-slate-500">{part.score}</span>
{part.issues.length === 0 ? (
<span className="tag tag-green flex items-center gap-1">
<CheckCircle className="w-3 h-3" />
</span>
) : (
<span className="tag tag-amber">
{part.issues.length}
</span>
)}
</div>
</div>
{part.issues.length > 0 ? (
<div className="divide-y divide-gray-100">
{part.issues.map((issue, issueIndex) => {
const severity = getSeverityStyle(issue.severity);
return (
<div key={issueIndex} className={`p-5 ${severity.bg}`}>
<div className="flex gap-3">
<div className="mt-0.5">{severity.icon}</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="font-bold text-sm text-slate-800">{issue.type}</span>
<span className={`text-xs px-1.5 py-0.5 rounded ${
issue.severity === 'major' ? 'bg-red-100 text-red-700' : 'bg-amber-100 text-amber-700'
}`}>
{severity.label}
</span>
</div>
<p className="text-sm text-slate-600">{issue.description}</p>
{issue.location && (
<p className="text-xs text-slate-400 mt-1">
{issue.location}
</p>
)}
{issue.suggestion && (
<div className="mt-3 bg-white border border-gray-200 p-3 rounded-lg">
<p className="text-xs text-slate-600">
<strong></strong>{issue.suggestion}
</p>
</div>
)}
</div>
</div>
</div>
);
})}
</div>
) : (
<div className="p-6 text-center text-slate-500 text-sm">
<CheckCircle className="w-8 h-8 text-green-400 mx-auto mb-2" />
</div>
)}
</div>
))}
</div>
);
}