MindCheck Bot
Update UI score display to 1-4 scale
13681f1
Raw
History Blame Contribute Delete
14.8 kB
"use client";
import Link from "next/link";
import { Suspense, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
type SeverityInfo = {
label: string;
color: string;
description: string;
};
type ResultData = {
scores: number[];
total_score: number;
severity: SeverityInfo;
filler_count: number;
negative_words: string[];
positive_words: string[];
item_names: string[];
answers: string[];
questions: string[];
itemShortNames: string[];
date: string;
fallback?: boolean;
};
const SEVERITY_STYLES: Record<string, { bg: string; text: string; border: string }> = {
Minimal: { bg: "bg-green-50", text: "text-green-700", border: "border-green-200" },
Mild: { bg: "bg-amber-50", text: "text-amber-700", border: "border-amber-200" },
Moderate: { bg: "bg-orange-50", text: "text-orange-700", border: "border-orange-200" },
"Moderately Severe": { bg: "bg-red-50", text: "text-red-600", border: "border-red-200" },
Severe: { bg: "bg-red-100", text: "text-red-800", border: "border-red-300" },
};
const BAR_COLORS = [
"bg-primary",
"bg-primary-container",
"bg-[#F57C00]",
"bg-error",
];
function getBarColor(score: number): string {
const idx = Math.min(Math.round(score), 3);
return BAR_COLORS[idx];
}
const SHORT_NAMES = [
"Loss of Interest",
"Feeling Depressed",
"Sleep Issues",
"Fatigue",
"Appetite Changes",
"Self-Criticism",
"Concentration",
"Psychomotor",
];
function ResultContent() {
const searchParams = useSearchParams();
const [result, setResult] = useState<ResultData | null>(null);
useEffect(() => {
const historyId = searchParams.get("id");
if (historyId !== null) {
/* Load from history */
const historyRaw = localStorage.getItem("mindcheck_history");
if (historyRaw) {
try {
const history = JSON.parse(historyRaw) as ResultData[];
const idx = parseInt(historyId, 10);
if (history[idx]) setResult(history[idx]);
} catch { /* ignore */ }
}
} else {
/* Load latest result */
const raw = localStorage.getItem("mindcheck_latest_result");
if (raw) {
try {
setResult(JSON.parse(raw));
} catch { /* ignore */ }
}
}
}, [searchParams]);
const handleSave = () => {
if (!result) return;
const historyRaw = localStorage.getItem("mindcheck_history");
const history: ResultData[] = historyRaw ? JSON.parse(historyRaw) : [];
history.unshift(result);
localStorage.setItem("mindcheck_history", JSON.stringify(history));
alert("Results saved to history!");
};
if (!result) {
return (
<>
<header className="bg-surface border-b border-outline-variant w-full top-0 sticky z-50">
<div className="flex justify-between items-center h-16 px-gutter max-w-container-max mx-auto">
<Link href="/" className="text-lg md:text-hero-lg md:font-hero-lg font-bold text-primary">MindCheck</Link>
<div className="font-section-heading text-section-heading hidden md:block">Screening Results</div>
<Link href="/chat" className="text-sm px-3 py-2 md:px-[20px] md:py-[12px] bg-transparent border border-primary text-primary rounded hover:bg-surface-container transition-colors">
Start Screening
</Link>
</div>
</header>
<main className="flex-grow flex flex-col items-center justify-center px-gutter py-section-padding max-w-container-max mx-auto w-full">
<span className="material-symbols-outlined text-outline-variant text-[64px] mb-4" data-icon="assignment_late">assignment_late</span>
<h1 className="font-section-heading text-section-heading text-on-surface mb-2">No Results Available</h1>
<p className="text-on-surface-variant mb-6">Complete a screening first to see your results.</p>
<Link href="/chat" className="bg-primary-container text-on-primary px-5 py-3 rounded hover:opacity-90 transition-opacity">
Start Screening
</Link>
</main>
</>
);
}
const sevStyle = SEVERITY_STYLES[result.severity.label] || SEVERITY_STYLES.Moderate;
const maxScore = 24;
return (
<>
{/* TopNavBar */}
<header className="bg-surface border-b border-outline-variant w-full top-0 sticky z-50">
<div className="flex justify-between items-center h-16 px-gutter max-w-container-max mx-auto">
<Link href="/" className="text-lg md:text-hero-lg md:font-hero-lg font-bold text-primary">MindCheck</Link>
<div className="font-section-heading text-section-heading hidden md:block">Screening Results</div>
<Link href="/chat" className="text-sm px-3 py-2 md:px-[20px] md:py-[12px] bg-transparent border border-primary text-primary rounded hover:bg-surface-container transition-colors">
Restart Screening
</Link>
</div>
</header>
{/* Main Content */}
<main className="flex-grow w-full max-w-container-max mx-auto px-gutter py-section-padding">
<div className="grid grid-cols-1 md:grid-cols-12 gap-gutter">
{/* Left Column: Core Results */}
<div className="md:col-span-7 flex flex-col gap-stack-lg">
{/* Score Section */}
<section className="flex flex-col gap-stack-md" style={{ animation: "fadeSlideIn 0.5s ease-out" }}>
<h1 className="font-section-heading text-section-heading text-on-surface">Your Result</h1>
<div className="flex items-center gap-4 mt-2">
<div className="flex items-baseline">
<span className="text-[40px] md:text-[56px] font-bold text-primary leading-none tracking-tight">
{Math.round(result.total_score)}
</span>
<span className="text-2xl text-on-surface-variant ml-1 font-medium">/{maxScore}</span>
</div>
<div className={`px-4 py-1.5 rounded-[20px] font-caption-sm text-caption-sm border-[0.5px] ${sevStyle.bg} ${sevStyle.text} ${sevStyle.border}`}>
{result.severity.label}
</div>
</div>
<p className="text-on-surface-variant max-w-md">{result.severity.description}</p>
{result.fallback && (
<div className="flex items-center gap-2 text-[#F57C00] bg-orange-50 border border-orange-200 rounded p-3 mt-2">
<span className="material-symbols-outlined text-[18px]" data-icon="warning">warning</span>
<span className="font-caption-sm text-caption-sm">Scored using keyword matching (model server unavailable)</span>
</div>
)}
<p className="text-on-surface-variant text-xs opacity-60">
Screened on {new Date(result.date).toLocaleDateString("en-US", { day: "numeric", month: "long", year: "numeric", hour: "2-digit", minute: "2-digit" })}
</p>
</section>
{/* Bar Chart Section */}
<section
className="bg-surface-lowest border-[0.5px] border-outline-variant rounded-lg p-4 md:p-section-padding bg-white"
style={{ animation: "fadeSlideIn 0.6s ease-out 0.1s both" }}
>
<h2 className="font-body-md text-body-md font-semibold mb-6">Score per question</h2>
<div className="flex flex-col gap-4">
{result.scores.map((score, i) => {
const name = result.itemShortNames?.[i] || SHORT_NAMES[i] || `Q${i + 1}`;
const displayScore = score + 1;
const pct = Math.min((displayScore / 4) * 100, 100);
return (
<div key={i} className="flex items-center gap-4 group">
<div className="w-[100px] md:w-[140px] truncate text-on-surface-variant text-sm" title={name}>
{name}
</div>
<div className="flex-grow h-2.5 bg-surface-container rounded-full overflow-hidden flex">
<div
className={`h-full rounded-full transition-all duration-1000 ease-out ${getBarColor(score)}`}
style={{ width: `${pct}%`, transitionDelay: `${i * 80}ms` }}
/>
</div>
<div className={`w-6 text-right font-medium ${displayScore === 1 ? "text-outline" : ""}`}>
{displayScore.toFixed(1)}
</div>
</div>
);
})}
</div>
</section>
{/* Disclaimer */}
<div className="bg-surface-container-low border-[0.5px] border-outline-variant rounded-lg p-4 flex gap-4 items-start">
<span className="material-symbols-outlined text-primary mt-0.5" data-icon="info">info</span>
<p className="font-caption-sm text-caption-sm text-on-surface-variant">
MindCheck is not a replacement for professional medical diagnosis. Please contact mental health professionals if you are in an emergency.
</p>
</div>
</div>
{/* Right Column: Metrics & Actions */}
<div className="md:col-span-5 flex flex-col gap-stack-lg mt-8 md:mt-0">
{/* 2x2 Metric Grid */}
<div
className="grid grid-cols-2 gap-4"
style={{ animation: "fadeSlideIn 0.5s ease-out 0.2s both" }}
>
<div className="bg-white border-[0.5px] border-outline-variant rounded-lg p-4 flex flex-col gap-2 hover:shadow-sm transition-shadow">
<span className="font-caption-sm text-caption-sm text-on-surface-variant">Total Score</span>
<span className="font-section-heading text-section-heading text-on-surface">{Math.round(result.total_score)}</span>
</div>
<div className="bg-white border-[0.5px] border-outline-variant rounded-lg p-4 flex flex-col gap-2 hover:shadow-sm transition-shadow">
<span className="font-caption-sm text-caption-sm text-on-surface-variant">Category</span>
<span className={`font-section-heading text-section-heading ${sevStyle.text}`}>{result.severity.label}</span>
</div>
<div className="bg-white border-[0.5px] border-outline-variant rounded-lg p-4 flex flex-col gap-2 hover:shadow-sm transition-shadow">
<span className="font-caption-sm text-caption-sm text-on-surface-variant">Negative Words</span>
<span className="font-section-heading text-section-heading text-on-surface">{result.negative_words.length}</span>
{result.negative_words.length > 0 && (
<div className="flex flex-wrap gap-1 mt-1">
{result.negative_words.slice(0, 5).map((w) => (
<span key={w} className="text-[10px] px-1.5 py-0.5 bg-red-50 text-red-600 rounded border border-red-100">{w}</span>
))}
{result.negative_words.length > 5 && (
<span className="text-[10px] px-1.5 py-0.5 text-on-surface-variant">+{result.negative_words.length - 5} more</span>
)}
</div>
)}
</div>
<div className="bg-white border-[0.5px] border-outline-variant rounded-lg p-4 flex flex-col gap-2 hover:shadow-sm transition-shadow">
<span className="font-caption-sm text-caption-sm text-on-surface-variant">Filler Words</span>
<span className="font-section-heading text-section-heading text-on-surface">{result.filler_count}</span>
</div>
</div>
{/* Recommendation Card */}
<div
className="bg-white border-[0.5px] border-outline-variant border-l-4 border-l-primary rounded-lg p-4 md:p-section-padding flex flex-col gap-stack-sm"
style={{ animation: "fadeSlideIn 0.5s ease-out 0.3s both" }}
>
<h3 className="font-body-md text-body-md font-semibold">Recommended Next Steps</h3>
<p className="text-on-surface-variant font-caption-sm text-caption-sm">
{result.total_score <= 4
? "Your results suggest minimal symptoms. Continue monitoring your well-being and maintain healthy habits."
: result.total_score <= 9
? "Your results suggest mild symptoms. Consider speaking with a trusted friend or family member, and monitor your progress."
: result.total_score <= 14
? "Based on these results, we recommend scheduling a consultation with a mental health professional for further evaluation."
: "Based on these results, we strongly recommend seeking professional help immediately. Please contact a psychologist, psychiatrist, or your nearest mental health service."}
</p>
</div>
{/* Action Buttons */}
<div
className="flex flex-col sm:flex-row gap-4 mt-auto"
style={{ animation: "fadeSlideIn 0.5s ease-out 0.4s both" }}
>
<button
onClick={handleSave}
className="flex-1 px-[20px] py-[12px] bg-transparent border border-primary text-primary rounded hover:bg-surface-container transition-all duration-200 text-center justify-center flex items-center gap-2 active:scale-95"
>
<span className="material-symbols-outlined text-[20px]" data-icon="download">download</span>
Save Results
</button>
<Link href="/history" className="flex-1 px-[20px] py-[12px] bg-primary text-on-primary rounded hover:opacity-90 transition-all duration-200 text-center justify-center flex items-center active:scale-95">
View History
</Link>
</div>
</div>
</div>
</main>
<style jsx global>{`
@keyframes fadeSlideIn {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`}</style>
</>
);
}
export default function ResultPage() {
return (
<Suspense fallback={
<div className="flex-grow flex items-center justify-center">
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
</div>
}>
<ResultContent />
</Suspense>
);
}