Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ backend/__pycache__/
*.csv
*.db
.env
.env.local
.vscode/
*.log
server/uploads/*
Expand Down
52 changes: 49 additions & 3 deletions src/app/history/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useEffect, useState } from "react";
import { Skeleton } from "@/components/ui/Skeleton";
import Link from "next/link";

interface Record {
Expand All @@ -12,6 +13,53 @@ interface Record {
riskLevel: string;
}

function HistoryTableSkeleton() {
return (
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-slate-50 border-b border-slate-100">
{["Date & Time", "File Reference", "Diagnosis", "Confidence", "Action"].map((col) => (
<th key={col} className="px-8 py-5 text-[10px] font-black uppercase tracking-widest text-slate-400">
{col}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-slate-50 bg-white">
{Array.from({ length: 7 }).map((_, i) => (
<tr key={i}>
{/* Date & Time */}
<td className="px-8 py-6">
<Skeleton className="h-3.5 w-36" />
</td>
{/* File Reference */}
<td className="px-8 py-6">
<Skeleton className="h-3.5 w-44" />
</td>
{/* Diagnosis badge */}
<td className="px-8 py-6">
<Skeleton className="h-6 w-20 rounded-full" />
</td>
{/* Confidence bar + number */}
<td className="px-8 py-6">
<div className="flex items-center gap-3">
<Skeleton className="h-1.5 w-20 rounded-full" />
<Skeleton className="h-3 w-8" />
</div>
</td>
{/* Action button */}
<td className="px-8 py-6 flex justify-end">
<Skeleton className="h-3.5 w-20" />
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}

export default function HistoryPage() {
const [records, setRecords] = useState<Record[]>([]);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -49,9 +97,7 @@ export default function HistoryPage() {

<div className="clinical-card overflow-hidden">
{loading ? (
<div className="p-20 text-center text-slate-400 font-bold">
Syncing with Clinical Database...
</div>
<HistoryTableSkeleton />
) : records.length === 0 ? (
<div className="p-20 text-center space-y-6">
<p className="text-slate-400 font-medium text-lg">No records found in the database.</p>
Expand Down
123 changes: 113 additions & 10 deletions src/app/result/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use client";

import { useState } from "react";
import { useState, useEffect } from "react";
import Link from "next/link";
import { Skeleton } from "@/components/ui/Skeleton";

interface AnalysisResult {
_id?: string;
Expand All @@ -15,15 +16,118 @@ interface AnalysisResult {
heatmap?: string;
}

function ResultSkeleton() {
return (
<div className="max-w-6xl mx-auto space-y-10">
{/* Report Header */}
<div className="flex flex-col md:flex-row items-center justify-between gap-8">
<div className="space-y-3">
<Skeleton className="h-10 w-64" />
<Skeleton className="h-4 w-48" />
</div>
<div className="flex gap-4">
<Skeleton className="h-12 w-36 rounded-xl" />
<Skeleton className="h-12 w-36 rounded-xl" />
</div>
</div>

<div className="grid grid-cols-1 lg:grid-cols-4 gap-10">
{/* Main card — lg:col-span-3 */}
<div className="lg:col-span-3 clinical-card p-1">
<div className="bg-white rounded-[12px] p-10 space-y-12">

{/* Diagnosis status + right panel */}
<div className="flex flex-col md:flex-row justify-between items-start gap-12">
{/* Left: diagnosis title + confidence bar */}
<div className="space-y-4 flex-1">
<Skeleton className="h-3 w-32" /> {/* "Diagnosis Status" label */}
<Skeleton className="h-14 w-48" /> {/* Big diagnosis word */}
<div className="pt-6 space-y-3">
<div className="flex justify-between">
<Skeleton className="h-3 w-32" />
<Skeleton className="h-3 w-10" />
</div>
<Skeleton className="h-3 w-full rounded-full" /> {/* confidence bar */}
</div>
</div>

{/* Right: severity card + scan preview */}
<div className="w-full md:w-72 grid grid-cols-1 gap-6">
<div className="p-6 bg-slate-50 rounded-2xl border border-slate-100 space-y-2">
<Skeleton className="h-3 w-28" />
<Skeleton className="h-9 w-16" />
</div>
<Skeleton className="h-40 w-full rounded-2xl" /> {/* scan preview */}
</div>
</div>

{/* Technical Observations */}
<div className="border-t border-slate-50 pt-10 space-y-8">
<Skeleton className="h-6 w-52" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
{/* Observation rows */}
<div className="space-y-6">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex justify-between items-center py-4 border-b border-slate-50">
<Skeleton className="h-3 w-28" />
<Skeleton className="h-3 w-20" />
</div>
))}
</div>
{/* Heatmap placeholder */}
<Skeleton className="h-64 w-full rounded-3xl" />
</div>
</div>

{/* Explanation section */}
<div className="border-t border-slate-50 pt-10 space-y-6">
<Skeleton className="h-6 w-64" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="p-5 rounded-2xl bg-slate-50 border border-slate-100 space-y-3">
<Skeleton className="h-4 w-36" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-5/6" />
<Skeleton className="h-3 w-4/6" />
</div>
))}
</div>
</div>
</div>
</div>

{/* Recommendations sidebar — lg:col-span-1 */}
<div className="lg:col-span-1 clinical-card p-1">
<div className="bg-slate-900 rounded-[12px] p-8 space-y-10">
<Skeleton className="h-14 w-14 rounded-2xl bg-slate-700" />
<div className="space-y-3">
<Skeleton className="h-6 w-40 bg-slate-700" />
<Skeleton className="h-3 w-full bg-slate-700" />
<Skeleton className="h-3 w-5/6 bg-slate-700" />
</div>
<div className="space-y-4 pt-4">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="flex items-start gap-4">
<Skeleton className="h-2 w-2 mt-1.5 rounded-full bg-slate-700 shrink-0" />
<Skeleton className="h-3 w-full bg-slate-700" />
</div>
))}
</div>
<Skeleton className="h-12 w-full rounded-xl bg-slate-700 mt-8" />
</div>
</div>
</div>
</div>
);
}

export default function ResultPage() {
const [result] = useState<AnalysisResult | null>(() => {
if (typeof window === "undefined") {
return null;
}
const [result, setResult] = useState<AnalysisResult | null>(null);

const data = sessionStorage.getItem("lastAnalysis");
return data ? JSON.parse(data) : null;
});
useEffect(() => {
const data = sessionStorage.getItem("lastAnalysis");
setResult(data ? JSON.parse(data) : null);
}, []);

const handleExportPDF = () => {
if (result?._id) {
Expand All @@ -35,8 +139,7 @@ export default function ResultPage() {
if (!result) {
return (
<main className="flex-grow flex flex-col items-center justify-center p-6">
<p className="text-slate-500 font-bold">Loading analysis results...</p>
<Link href="/upload" className="mt-4 clinical-link">Go back to upload</Link>
<ResultSkeleton />
</main>
);
}
Expand Down
11 changes: 11 additions & 0 deletions src/components/ui/Skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// src/components/ui/Skeleton.tsx

interface SkeletonProps {
className?: string;
}

export function Skeleton({ className = "" }: SkeletonProps) {
return (
<div className={`animate-pulse rounded-md bg-slate-100 ${className}`} />
);
}