Tool 4 - SR Chart Generator: - PRISMA 2020 flow diagram with Chinese/English toggle (SVG) - Baseline characteristics table (Table 1) - Dual data source: project pipeline API + Excel upload - SVG/PNG export support - Backend: ChartingService with Prisma aggregation - Frontend: PrismaFlowDiagram, BaselineTable, DataSourceSelector Tool 5 - Meta Analysis Engine: - 3 data types: HR (metagen), dichotomous (metabin), continuous (metacont) - Random and fixed effects models - Multiple effect measures: HR / OR / RR - Forest plot + funnel plot (base64 PNG from R) - Heterogeneity statistics: I2, Q, p-value, Tau2 - Data input via Excel upload or project pipeline - R Docker image updated with meta package (13 tools total) - E2E test: 36/36 passed - Key fix: exp() back-transformation for log-scale ratio measures Also includes: - IIT CRA Agent V3.0 routing and AI chat page integration - Updated ASL module status guide (v2.3) - Updated system status guide (v6.3) - Updated R statistics engine guide (v1.4) Tested: Frontend renders correctly, backend APIs functional, E2E tests passed Made-with: Cursor
111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { Table, Typography, Empty } from 'antd';
|
|
import type { BaselineRow } from '../../utils/chartingExcelUtils';
|
|
|
|
const { Title } = Typography;
|
|
|
|
interface Props {
|
|
data: BaselineRow[];
|
|
}
|
|
|
|
const KNOWN_FIELD_LABELS: Record<string, string> = {
|
|
Study_ID: 'Study ID',
|
|
study_id: 'Study ID',
|
|
Intervention_Name: 'Intervention',
|
|
intervention_name: 'Intervention',
|
|
Control_Name: 'Control',
|
|
control_name: 'Control',
|
|
Intervention_N: 'Intervention N',
|
|
intervention_n: 'Intervention N',
|
|
Control_N: 'Control N',
|
|
control_n: 'Control N',
|
|
Age_Mean_SD: 'Age (Mean ± SD)',
|
|
age_mean_sd: 'Age (Mean ± SD)',
|
|
Male_Percent: 'Male %',
|
|
male_percent: 'Male %',
|
|
study_design: 'Study Design',
|
|
Study_Design: 'Study Design',
|
|
};
|
|
|
|
function humanize(key: string): string {
|
|
if (KNOWN_FIELD_LABELS[key]) return KNOWN_FIELD_LABELS[key];
|
|
return key
|
|
.replace(/_/g, ' ')
|
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
}
|
|
|
|
const BaselineTable: React.FC<Props> = ({ data }) => {
|
|
const columns = useMemo(() => {
|
|
if (data.length === 0) return [];
|
|
|
|
const allKeys = new Set<string>();
|
|
data.forEach((row) => {
|
|
Object.keys(row).forEach((k) => allKeys.add(k));
|
|
});
|
|
|
|
const priorityOrder = [
|
|
'Study_ID', 'study_id',
|
|
'Intervention_Name', 'intervention_name',
|
|
'Control_Name', 'control_name',
|
|
'Intervention_N', 'intervention_n',
|
|
'Control_N', 'control_n',
|
|
'Age_Mean_SD', 'age_mean_sd',
|
|
'Male_Percent', 'male_percent',
|
|
];
|
|
|
|
const orderedKeys: string[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
for (const pk of priorityOrder) {
|
|
if (allKeys.has(pk) && !seen.has(pk)) {
|
|
orderedKeys.push(pk);
|
|
seen.add(pk);
|
|
}
|
|
}
|
|
for (const k of allKeys) {
|
|
if (!seen.has(k)) {
|
|
orderedKeys.push(k);
|
|
seen.add(k);
|
|
}
|
|
}
|
|
|
|
return orderedKeys.map((key) => ({
|
|
title: humanize(key),
|
|
dataIndex: key,
|
|
key,
|
|
ellipsis: true,
|
|
render: (val: any) => {
|
|
if (val === null || val === undefined) return '-';
|
|
if (typeof val === 'object') return JSON.stringify(val);
|
|
return String(val);
|
|
},
|
|
}));
|
|
}, [data]);
|
|
|
|
if (data.length === 0) {
|
|
return <Empty description="暂无基线数据" />;
|
|
}
|
|
|
|
const dataSource = data.map((row, i) => ({ ...row, _rowKey: `row-${i}` }));
|
|
|
|
return (
|
|
<div className="w-full">
|
|
<Title level={5} className="text-center mb-4">
|
|
Table 1. 纳入研究的基线特征 (Baseline Characteristics of Included Studies)
|
|
</Title>
|
|
<Table
|
|
columns={columns}
|
|
dataSource={dataSource}
|
|
rowKey="_rowKey"
|
|
pagination={false}
|
|
bordered
|
|
size="small"
|
|
scroll={{ x: 'max-content' }}
|
|
className="baseline-academic-table"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BaselineTable;
|