Files
muyingbaogaojiedu/前端-孕期报告解读-figma/src/app/pages/GrowthCurvePage.tsx
shutiaozhao127-boop cfdeeb5e41 chore: 初始化项目结构并添加基础配置文件
添加后端API和前端项目的初始目录结构,包括应用层模块、工具模块和模型定义
配置前后端的.dockerignore文件以排除开发环境文件
添加前端样式文件,包括字体、Tailwind CSS和主题配置
初始化Python虚拟环境中的类型标记文件
2026-04-01 16:55:24 +08:00

624 lines
28 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 { useState, useEffect } from "react";
import { useNavigate } from "react-router";
import { motion } from "motion/react";
import {
ChevronLeft,
TrendingUp,
Info,
Download,
Share2,
Loader2,
Heart,
} from "lucide-react";
import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Badge } from "../components/ui/badge";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
Area,
AreaChart,
} from "recharts";
import { getGrowthCurve } from "../services/api";
import { useUser } from "../context/UserContext";
// 宝宝预期大小数据(按孕周,基于WHO标准)
// 4-19周:身长为头臀长(CRL);20周起:身长为头到脚全身长
const BABY_EXPECTED_DATA: Record<number, { emoji: string; fruit: string; length: string; weight: string; highlight: string }> = {
4: { emoji: "🫘", fruit: "罂粟种子", length: "0.1cm", weight: "<1g", highlight: "心脏开始跳动" },
5: { emoji: "🫘", fruit: "芝麻粒", length: "0.2cm", weight: "<1g", highlight: "神经管开始发育" },
6: { emoji: "🫘", fruit: "小扁豆", length: "0.6cm", weight: "<1g", highlight: "小手小脚在萌芽" },
7: { emoji: "🫐", fruit: "蓝莓", length: "1.3cm", weight: "<1g", highlight: "五官开始形成" },
8: { emoji: "🫐", fruit: "覆盆子", length: "1.6cm", weight: "1g", highlight: "手指脚趾开始分化" },
9: { emoji: "🍇", fruit: "葡萄", length: "2.3cm", weight: "2g", highlight: "可以微微活动了" },
10: { emoji: "🍊", fruit: "金橘", length: "3.1cm", weight: "4g", highlight: "所有器官已初步形成" },
11: { emoji: "🫒", fruit: "无花果", length: "4.1cm", weight: "7g", highlight: "能做出吞咽动作" },
12: { emoji: "🍋", fruit: "青柠", length: "5.4cm", weight: "14g", highlight: "进入稳定的孕中期" },
13: { emoji: "🍋", fruit: "柠檬", length: "7.4cm", weight: "23g", highlight: "指纹开始形成" },
14: { emoji: "🍑", fruit: "桃子", length: "8.7cm", weight: "43g", highlight: "能做出表情了" },
16: { emoji: "🥑", fruit: "牛油果", length: "11.6cm", weight: "100g", highlight: "开始能听到声音" },
18: { emoji: "🫑", fruit: "甜椒", length: "14.2cm", weight: "190g", highlight: "妈妈可能感到胎动了" },
20: { emoji: "🍌", fruit: "香蕉", length: "25.6cm", weight: "300g", highlight: "味觉开始发育" },
22: { emoji: "🥭", fruit: "木瓜", length: "27.8cm", weight: "430g", highlight: "眉毛和眼睫毛长出" },
24: { emoji: "🌽", fruit: "玉米", length: "30cm", weight: "600g", highlight: "肺部开始发育" },
26: { emoji: "🥦", fruit: "菜花", length: "35.6cm", weight: "760g", highlight: "能睁开眼睛了" },
28: { emoji: "🍆", fruit: "茄子", length: "37.6cm", weight: "1000g", highlight: "开始有规律的作息" },
30: { emoji: "🥬", fruit: "卷心菜", length: "39.9cm", weight: "1320g", highlight: "大脑快速发育中" },
32: { emoji: "🥥", fruit: "椰子", length: "42.4cm", weight: "1700g", highlight: "皮下脂肪增多" },
34: { emoji: "🍈", fruit: "哈密瓜", length: "45cm", weight: "2150g", highlight: "肺部接近成熟" },
36: { emoji: "🍈", fruit: "蜜瓜", length: "47.4cm", weight: "2620g", highlight: "头部开始入盆" },
38: { emoji: "🎃", fruit: "南瓜", length: "49.8cm", weight: "3080g", highlight: "各器官发育成熟" },
40: { emoji: "🍉", fruit: "小西瓜", length: "51.2cm", weight: "3460g", highlight: "随时准备来到这个世界" },
};
function getBabyExpectedData(week: number) {
const weeks = Object.keys(BABY_EXPECTED_DATA).map(Number).sort((a, b) => a - b);
let closest = weeks[0];
for (const w of weeks) {
if (w <= week) closest = w;
else break;
}
return BABY_EXPECTED_DATA[closest];
}
// 生成孕周里程碑时间线数据
function getGrowthMilestones(currentWeek: number) {
const milestones = [
{ week: 8, label: "心跳可检测", emoji: "💓" },
{ week: 12, label: "首次B超", emoji: "🔍" },
{ week: 16, label: "开始感知声音", emoji: "👂" },
{ week: 20, label: "大排畸检查", emoji: "🏥" },
{ week: 24, label: "肺部发育", emoji: "🫁" },
{ week: 28, label: "眼睛能睁开", emoji: "👀" },
{ week: 32, label: "快速增重", emoji: "📈" },
{ week: 36, label: "接近足月", emoji: "🎯" },
{ week: 40, label: "预产期到来", emoji: "🎉" },
];
return milestones.map(m => ({ ...m, passed: currentWeek >= m.week }));
}
export function GrowthCurvePage() {
const navigate = useNavigate();
const { user } = useUser();
const currentWeek = user.currentWeek || 0;
const [selectedMetric, setSelectedMetric] = useState("bpd");
const [loading, setLoading] = useState(true);
// Standard reference data (no fake values)
const standardData: Record<string, { name: string; unit: string; description: string; data: { week: number; value?: number; standard: number; lower: number; upper: number }[] }> = {
bpd: {
name: "双顶径 (BPD)",
unit: "mm",
description: "胎儿头部左右两侧之间最宽部位的长度",
data: [
{ week: 12, standard: 20, lower: 18, upper: 22 },
{ week: 16, standard: 35, lower: 32, upper: 38 },
{ week: 20, standard: 47, lower: 43, upper: 51 },
{ week: 24, standard: 60, lower: 56, upper: 64 },
{ week: 28, standard: 71, lower: 67, upper: 75 },
{ week: 32, standard: 81, lower: 77, upper: 85 },
{ week: 36, standard: 89, lower: 85, upper: 93 },
],
},
ac: {
name: "腹围 (AC)",
unit: "mm",
description: "胎儿腹部一周的长度",
data: [
{ week: 12, standard: 60, lower: 54, upper: 66 },
{ week: 16, standard: 102, lower: 94, upper: 110 },
{ week: 20, standard: 148, lower: 138, upper: 158 },
{ week: 24, standard: 192, lower: 180, upper: 204 },
{ week: 28, standard: 235, lower: 221, upper: 249 },
{ week: 32, standard: 279, lower: 263, upper: 295 },
{ week: 36, standard: 318, lower: 300, upper: 336 },
],
},
fl: {
name: "股骨长 (FL)",
unit: "mm",
description: "胎儿大腿骨的长度",
data: [
{ week: 12, standard: 8, lower: 7, upper: 9 },
{ week: 16, standard: 22, lower: 20, upper: 24 },
{ week: 20, standard: 34, lower: 31, upper: 37 },
{ week: 24, standard: 45, lower: 42, upper: 48 },
{ week: 28, standard: 54, lower: 51, upper: 57 },
{ week: 32, standard: 63, lower: 60, upper: 66 },
{ week: 36, standard: 70, lower: 67, upper: 73 },
],
},
};
const [growthData, setGrowthData] = useState(standardData);
useEffect(() => {
getGrowthCurve()
.then((data) => {
if (data.data_points && data.data_points.length > 0) {
// Merge API data points into standard data
const updated = { ...standardData };
for (const metric of ["bpd", "ac", "fl"] as const) {
const apiPoints = data.data_points.filter((p) => p[metric] != null);
if (apiPoints.length > 0) {
updated[metric] = {
...updated[metric],
data: updated[metric].data.map((d) => {
const match = apiPoints.find((p) => p.week === d.week);
return match && match[metric] != null
? { ...d, value: match[metric]! }
: d;
}),
};
}
}
setGrowthData(updated);
}
})
.catch(() => {/* keep standard data */})
.finally(() => setLoading(false));
}, []);
const currentData = growthData[selectedMetric];
// 检查是否有真实数据
const hasRealData = currentData.data.some(d => d.value != null);
// 生成动态评估结果
const generateAssessment = () => {
if (!hasRealData) {
return "暂无生长数据。上传B超报告后,系统会自动记录宝宝的生长数据并生成评估结果。";
}
const realDataPoints = currentData.data.filter(d => d.value != null);
const latestPoint = realDataPoints[realDataPoints.length - 1];
if (!latestPoint) {
return "暂无生长数据。上传B超报告后,系统会自动记录宝宝的生长数据并生成评估结果。";
}
const { value, lower, upper, week, standard } = latestPoint;
const metricName = currentData.name.split(" ")[0];
if (value! >= lower && value! <= upper) {
const deviation = Math.abs(value! - standard);
const deviationPercent = ((deviation / standard) * 100).toFixed(1);
if (deviation < standard * 0.05) {
return `宝宝的${metricName}发育非常好!孕${week}周的测量值为${value}${currentData.unit},与标准值${standard}${currentData.unit}非常接近,在正常范围内(${lower}-${upper}${currentData.unit})。继续保持健康的生活方式,按时产检。`;
} else {
return `宝宝的${metricName}发育良好!孕${week}周的测量值为${value}${currentData.unit},在正常范围内(${lower}-${upper}${currentData.unit}),与标准值${standard}${currentData.unit}相差约${deviationPercent}%。继续保持健康的生活方式,按时产检。`;
}
} else if (value! < lower) {
const gap = lower - value!;
return `宝宝的${metricName}略偏小。孕${week}周的测量值为${value}${currentData.unit},低于正常范围下限(${lower}${currentData.unit})约${gap}${currentData.unit}。建议加强营养摄入,多吃富含蛋白质的食物,并及时咨询产科医生,进行进一步评估。`;
} else {
const gap = value! - upper;
return `宝宝的${metricName}略偏大。孕${week}周的测量值为${value}${currentData.unit},高于正常范围上限(${upper}${currentData.unit})约${gap}${currentData.unit}。建议控制饮食,避免高糖高脂食物,并及时咨询产科医生,进行进一步评估。`;
}
};
const CustomTooltip = ({ active, payload }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-white p-3 rounded-xl shadow-sm border border-gray-100">
<p className="font-semibold text-gray-800 mb-2">
孕{payload[0].payload.week}周
</p>
<p className="text-sm text-gray-500">
标准值: {payload[0].payload.standard} {currentData.unit}
</p>
<p className="text-xs text-gray-400 mt-1">
正常范围: {payload[0].payload.lower}-{payload[0].payload.upper}{" "}
{currentData.unit}
</p>
</div>
);
}
return null;
};
return (
<div className="min-h-dvh bg-morandi-50">
{/* 顶部导航 */}
<div className="bg-white border-b border-gray-100 sticky top-0 z-10">
<div className="mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<button
onClick={() => navigate(-1)}
className="w-10 h-10 rounded-full hover:bg-gray-100 flex items-center justify-center"
>
<ChevronLeft className="w-6 h-6 text-gray-600" />
</button>
<div className="flex items-center gap-2">
<div className="w-10 h-10 bg-gradient-to-br from-morandi-400 to-morandi-400 rounded-full flex items-center justify-center">
<TrendingUp className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="font-semibold text-gray-800">胎儿生长曲线</h1>
<p className="text-xs text-gray-500">基于B超数据自动生成</p>
</div>
</div>
</div>
</div>
</div>
<div className="mx-auto p-4 space-y-6">
{/* 无B超数据时展示宝宝预期大小 */}
{!hasRealData && currentWeek > 0 && (() => {
const babyData = getBabyExpectedData(currentWeek);
const milestones = getGrowthMilestones(currentWeek);
const progress = Math.min(100, Math.max(0, ((currentWeek - 4) / 36) * 100));
if (!babyData) return null;
return (
<>
{/* 宝宝当前大小 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<Card className="bg-white rounded-2xl p-6 shadow-sm border-0 overflow-hidden relative">
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-morandi-100 to-transparent rounded-bl-full opacity-60" />
<div className="relative">
<h2 className="font-semibold text-gray-800 mb-4 flex items-center gap-2">
<Heart className="w-5 h-5 text-morandi-rose-400" />
宝宝现在有多大?
</h2>
<div className="text-center py-4">
<motion.div
initial={{ scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ type: "spring", duration: 0.8 }}
className="text-7xl mb-4"
>
{babyData.emoji}
</motion.div>
<motion.p
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="text-lg text-gray-700 font-medium mb-1"
>
孕{currentWeek}周,宝宝像一个<span className="text-morandi-500 font-semibold">{babyData.fruit}</span>
</motion.p>
<p className="text-sm text-gray-400">{babyData.highlight}</p>
</div>
{/* 预期数据 */}
<div className="grid grid-cols-2 gap-3 mt-4">
<div className="bg-morandi-50 rounded-xl p-4 text-center">
<p className="text-xs text-gray-400 mb-1">预期身长</p>
<p className="text-xl font-semibold text-morandi-600">{babyData.length}</p>
</div>
<div className="bg-morandi-50 rounded-xl p-4 text-center">
<p className="text-xs text-gray-400 mb-1">预期体重</p>
<p className="text-xl font-semibold text-morandi-600">{babyData.weight}</p>
</div>
</div>
{/* 孕期进度 */}
<div className="mt-5">
<div className="flex items-center justify-between mb-2">
<span className="text-xs text-gray-400">孕期进度</span>
<span className="text-xs text-morandi-500 font-medium">{Math.round(progress)}%</span>
</div>
<div className="h-2.5 bg-morandi-50 rounded-full overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-morandi-300 to-morandi-500 rounded-full"
initial={{ width: 0 }}
animate={{ width: `${progress}%` }}
transition={{ duration: 1.2, ease: "easeOut" }}
/>
</div>
<div className="flex justify-between mt-1">
<span className="text-[10px] text-gray-300">4周</span>
<span className="text-[10px] text-gray-300">40周</span>
</div>
</div>
</div>
</Card>
</motion.div>
{/* 成长里程碑时间线 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.15 }}
>
<Card className="bg-white rounded-2xl p-6 shadow-sm border-0">
<h3 className="font-semibold text-gray-800 mb-4">成长里程碑</h3>
<div className="space-y-0">
{milestones.map((m, i) => (
<div key={m.week} className="flex items-start gap-3">
{/* 时间线连接线 */}
<div className="flex flex-col items-center">
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm flex-shrink-0 ${
m.passed
? "bg-morandi-400 text-white"
: currentWeek >= m.week - 2
? "bg-morandi-100 text-morandi-500 ring-2 ring-morandi-300"
: "bg-gray-100 text-gray-400"
}`}>
{m.emoji}
</div>
{i < milestones.length - 1 && (
<div className={`w-0.5 h-6 ${m.passed ? "bg-morandi-300" : "bg-gray-200"}`} />
)}
</div>
<div className="pt-1.5">
<p className={`text-sm font-medium ${m.passed ? "text-gray-700" : "text-gray-400"}`}>
{m.label}
</p>
<p className={`text-xs ${m.passed ? "text-morandi-500" : "text-gray-300"}`}>
孕{m.week}周
</p>
</div>
</div>
))}
</div>
</Card>
</motion.div>
{/* 上传B超提示 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.25 }}
>
<Card className="bg-gradient-to-r from-morandi-sage-50 to-morandi-50 border-morandi-200 rounded-2xl p-4">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-morandi-sage-500 mt-0.5 flex-shrink-0" />
<div>
<h3 className="font-semibold text-gray-700 text-sm mb-1">查看精准发育数据</h3>
<p className="text-xs text-gray-500 leading-relaxed mb-3">
上传B超报告后,系统会自动提取胎儿双顶径、腹围、股骨长等数据,生成个性化的生长曲线,让你更直观地了解宝宝发育情况。
</p>
<Button
onClick={() => navigate("/camera")}
className="bg-gradient-to-r from-morandi-400 to-morandi-500"
size="sm"
>
上传B超报告
</Button>
</div>
</div>
</Card>
</motion.div>
</>
);
})()}
{/* 指标选择 - 只在有B超数据时显示 */}
{hasRealData && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<div className="flex gap-3 overflow-x-auto pb-2">
<button
onClick={() => setSelectedMetric("bpd")}
className={`flex-shrink-0 px-6 py-3 rounded-2xl font-semibold text-sm transition-all ${
selectedMetric === "bpd"
? "bg-gradient-to-r from-morandi-400 to-morandi-400 text-white shadow-sm"
: "bg-white text-gray-600 hover:bg-gray-50"
}`}
>
双顶径
</button>
<button
onClick={() => setSelectedMetric("ac")}
className={`flex-shrink-0 px-6 py-3 rounded-2xl font-semibold text-sm transition-all ${
selectedMetric === "ac"
? "bg-gradient-to-r from-morandi-400 to-morandi-400 text-white shadow-sm"
: "bg-white text-gray-600 hover:bg-gray-50"
}`}
>
腹围
</button>
<button
onClick={() => setSelectedMetric("fl")}
className={`flex-shrink-0 px-6 py-3 rounded-2xl font-semibold text-sm transition-all ${
selectedMetric === "fl"
? "bg-gradient-to-r from-morandi-rose-400 to-morandi-rose-300 text-white shadow-sm"
: "bg-white text-gray-600 hover:bg-gray-50"
}`}
>
股骨长
</button>
</div>
</motion.div>
)}
{/* 有B超数据时显示指标详情、曲线图、数据表格 */}
{hasRealData && (
<>
{/* 指标说明 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<Card className="bg-morandi-slate-50 border-morandi-200 rounded-2xl p-4">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-morandi-slate-500 mt-0.5 flex-shrink-0" />
<div>
<h3 className="font-semibold text-gray-800 mb-1">
{currentData.name}
</h3>
<p className="text-sm text-gray-600">
{currentData.description}
</p>
</div>
</div>
</Card>
</motion.div>
{/* 生长曲线图 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
<Card className="bg-white rounded-2xl p-6 shadow-sm border-0">
<div className="flex items-center justify-between mb-6">
<h2 className="font-semibold text-gray-800">生长趋势</h2>
<div className="flex items-center gap-4 text-sm">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-gray-300 rounded-full"></div>
<span className="text-gray-600">标准值</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-morandi-slate-200 rounded-full"></div>
<span className="text-gray-600">正常范围</span>
</div>
</div>
</div>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={currentData.data}>
<defs>
<linearGradient id="colorRange" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#93c5fd" stopOpacity={0.3} />
<stop offset="95%" stopColor="#93c5fd" stopOpacity={0.1} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
<XAxis
dataKey="week"
label={{ value: "孕周", position: "insideBottom", offset: -5 }}
stroke="#9ca3af"
/>
<YAxis
label={{
value: `${currentData.unit}`,
angle: -90,
position: "insideLeft",
}}
stroke="#9ca3af"
/>
<Tooltip content={<CustomTooltip />} />
<Area
type="monotone"
dataKey="upper"
stroke="none"
fill="url(#colorRange)"
/>
<Area
type="monotone"
dataKey="lower"
stroke="none"
fill="#ffffff"
/>
<Line
type="monotone"
dataKey="standard"
stroke="#d1d5db"
strokeWidth={2}
dot={false}
strokeDasharray="5 5"
/>
{/* 完全移除实际值线条 */}
</AreaChart>
</ResponsiveContainer>
</Card>
</motion.div>
{/* 数据表格 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<Card className="bg-white rounded-2xl p-6 shadow-sm border-0">
<h3 className="font-semibold text-gray-800 mb-4">详细数据</h3>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-3 text-sm font-semibold text-gray-600">
孕周
</th>
<th className="text-center py-3 text-sm font-semibold text-gray-600">
实际值
</th>
<th className="text-center py-3 text-sm font-semibold text-gray-600">
标准值
</th>
<th className="text-center py-3 text-sm font-semibold text-gray-600">
正常范围
</th>
<th className="text-center py-3 text-sm font-semibold text-gray-600">
状态
</th>
</tr>
</thead>
<tbody>
{currentData.data.map((row, index) => {
const isNormal =
row.value != null && row.value >= row.lower && row.value <= row.upper;
return (
<tr key={index} className="border-b border-gray-100">
<td className="py-3 text-sm text-gray-700">
{row.week}周
</td>
<td className="text-center py-3 text-sm font-semibold text-morandi-500">
{row.value != null ? `${row.value} ${currentData.unit}` : '-'}
</td>
<td className="text-center py-3 text-sm text-gray-600">
{row.standard} {currentData.unit}
</td>
<td className="text-center py-3 text-sm text-gray-500">
{row.lower}-{row.upper} {currentData.unit}
</td>
<td className="text-center py-3">
{row.value != null ? (
<Badge
className={`${
isNormal
? "bg-morandi-sage-100 text-morandi-sage-600"
: "bg-morandi-rose-100 text-morandi-600"
} hover:bg-current`}
>
{isNormal ? "正常" : "关注"}
</Badge>
) : (
<span className="text-gray-400 text-xs">-</span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</Card>
</motion.div>
{/* 医生建议 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<Card className="bg-gradient-to-r from-morandi-sage-100 to-morandi-sage-100 border-morandi-sage-200 rounded-2xl p-4">
<h3 className="font-semibold text-gray-800 mb-2 flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-morandi-sage-500" />
评估结果
</h3>
<p className="text-sm text-gray-700 leading-relaxed">
{generateAssessment()}
</p>
</Card>
</motion.div>
</>
)}
</div>
</div>
);
}