FetalCLIP / frontend /src /components /GAResultsCard.tsx
Numan Saeed
feat: Add visual improvements and aesthetic enhancements
bedc7e7
import type { GestationalAgeResponse } from '../lib/api';
interface GAResultsCardProps {
results: GestationalAgeResponse | null;
isLoading: boolean;
}
// Map view/biometry key to display label
const BIOMETRY_LABELS: Record<string, string> = {
head_circumference: 'Head Circumference',
abdominal_circumference: 'Abdominal Circumference',
femur_length: 'Femur Length',
};
export function GAResultsCard({ results, isLoading }: GAResultsCardProps) {
if (isLoading) {
return (
<div className="space-y-3">
<div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
<div className="h-3 w-24 shimmer-premium rounded mb-2" />
<div className="h-8 w-40 shimmer-premium rounded" />
</div>
<div className="bg-white border border-dark-border rounded-xl p-4 shadow-card">
<div className="h-3 w-40 shimmer-premium rounded mb-3" />
<div className="grid grid-cols-3 gap-2">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-16 shimmer-premium rounded-lg" style={{ animationDelay: `${i * 100}ms` }} />
))}
</div>
</div>
</div>
);
}
if (!results) {
return (
<div className="bg-white border border-dark-border rounded-xl p-8 text-center shadow-card">
<p className="text-text-muted text-sm">
Upload a fetal ultrasound and click "Estimate Age"
</p>
</div>
);
}
const { gestational_age } = results;
// Get biometry data dynamically based on what the backend returned
const biometryData = results.head_circumference
|| results.abdominal_circumference
|| results.femur_length;
// Determine which biometry type we have
let biometryLabel = 'Biometry';
if (results.head_circumference) biometryLabel = BIOMETRY_LABELS.head_circumference;
else if (results.abdominal_circumference) biometryLabel = BIOMETRY_LABELS.abdominal_circumference;
else if (results.femur_length) biometryLabel = BIOMETRY_LABELS.femur_length;
return (
<div className="space-y-3 animate-scale-in">
{/* Gestational Age */}
<div className="bg-gradient-to-r from-nvidia-green/10 to-nvidia-green/5 border border-nvidia-green/20 rounded-xl p-4 shadow-card">
<p className="text-[10px] uppercase tracking-wider text-text-muted mb-1">
Gestational Age
</p>
<div className="text-2xl font-bold text-nvidia-green">
{gestational_age.weeks} weeks, {gestational_age.days} days
</div>
<p className="text-text-muted text-xs mt-1">
Total: {gestational_age.total_days} days
</p>
</div>
{/* Biometry Percentiles - Dynamic based on view */}
{biometryData && (
<div className="bg-white border border-dark-border rounded-xl p-4 shadow-card card-interactive">
<p className="text-[10px] uppercase tracking-wider text-text-muted mb-3">
{biometryLabel} Percentiles
</p>
<div className="grid grid-cols-3 gap-3">
<div className="bg-dark-input rounded-xl p-3 text-center">
<p className="text-[10px] text-text-muted mb-1">2.5th</p>
<p className="text-base font-semibold text-text-primary">
{biometryData.p2_5} mm
</p>
</div>
<div className="bg-nvidia-green/10 rounded-xl p-3 text-center border-2 border-nvidia-green">
<p className="text-[10px] text-nvidia-green mb-1 font-medium">50th</p>
<p className="text-base font-bold text-nvidia-green">
{biometryData.p50} mm
</p>
</div>
<div className="bg-dark-input rounded-xl p-3 text-center">
<p className="text-[10px] text-text-muted mb-1">97.5th</p>
<p className="text-base font-semibold text-text-primary">
{biometryData.p97_5} mm
</p>
</div>
</div>
</div>
)}
</div>
);
}