chore: Secure routes with auth middleware, update text and fix minor bugs
This commit is contained in:
@@ -1,37 +1,40 @@
|
||||
"use client"
|
||||
|
||||
import { TrendingUp, TrendingDown, Brain, Zap } from "lucide-react"
|
||||
import { TrendingUp, TrendingDown, Brain, Zap, Target, Clock } from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import Link from "next/link"
|
||||
import { useState } from "react"
|
||||
|
||||
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
const BAR_HEIGHTS = [55, 70, 45, 90, 80, 60, 75] // % heights
|
||||
const QUESTIONS = [42, 58, 35, 71, 65, 48, 60]
|
||||
const ACCURACY = [82, 78, 91, 88, 85, 79, 93]
|
||||
const HOURS = [2.5, 3.5, 2, 4.5, 4, 3, 3.8]
|
||||
|
||||
export function WeeklyProgressReport() {
|
||||
const [metric, setMetric] = useState<"questions" | "accuracy" | "hours">("accuracy")
|
||||
const [hovered, setHovered] = useState<number | null>(null)
|
||||
|
||||
const DATA = metric === "questions" ? QUESTIONS : metric === "accuracy" ? ACCURACY : HOURS
|
||||
const maxVal = Math.max(...DATA)
|
||||
const avgVal = Math.round((DATA.reduce((a, b) => a + b, 0) / DATA.length) * 10) / 10
|
||||
const trend = DATA[DATA.length - 1] - DATA[0]
|
||||
const trendUp = trend >= 0
|
||||
|
||||
const UNIT = metric === "accuracy" ? "%" : metric === "hours" ? "h" : ""
|
||||
const avgAccuracy = Math.round(ACCURACY.reduce((a, b) => a + b, 0) / ACCURACY.length)
|
||||
const trend = ACCURACY[ACCURACY.length - 1] - ACCURACY[0]
|
||||
const maxBar = Math.max(...BAR_HEIGHTS)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex justify-between items-center pb-3">
|
||||
<h2 className="text-base font-bold text-primary flex items-center gap-2">
|
||||
📊 This Week at a Glance
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card className="border-border bg-card shadow-[0_4px_24px_rgba(0,0,0,0.3)] flex-1 flex flex-col group hover:border-[#00F0FF]/30 transition-colors">
|
||||
<CardContent className="p-4 flex flex-col h-full gap-4">
|
||||
<CardContent className="p-5 flex flex-col gap-4 h-full">
|
||||
{/* Summary chips */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-green-500/10 text-green-500 border border-green-500/20">
|
||||
<TrendingUp size={12} />
|
||||
{avgAccuracy}% avg accuracy
|
||||
</div>
|
||||
<div className={`flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full border ${trend >= 0 ? "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20" : "bg-red-500/10 text-red-500 border-red-500/20"}`}>
|
||||
{trend >= 0 ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
|
||||
{trend >= 0 ? "+" : ""}{trend}% vs Mon
|
||||
<div className={`flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full border ${trendUp ? "bg-[#00F0FF]/10 text-[#00F0FF] border-[#00F0FF]/20" : "bg-red-500/10 text-red-500 border-red-500/20"}`}>
|
||||
{trendUp ? <TrendingUp size={12} /> : <TrendingDown size={12} />}
|
||||
{trendUp ? "+" : ""}{trend}{UNIT} vs Mon
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full bg-yellow-500/10 text-yellow-500 border border-yellow-500/20">
|
||||
<Zap size={12} />
|
||||
@@ -39,32 +42,97 @@ export function WeeklyProgressReport() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bar Chart */}
|
||||
<div className="flex items-end gap-2 h-24 flex-1">
|
||||
{DAYS.map((day, i) => (
|
||||
<div key={day} className="flex flex-col items-center gap-1 flex-1">
|
||||
<div className="relative w-full flex items-end justify-center" style={{ height: "80px" }}>
|
||||
<div
|
||||
className={`w-full rounded-t-md transition-all duration-700 ${
|
||||
i === 6
|
||||
? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]"
|
||||
: "bg-gradient-to-t from-white/10 to-white/20"
|
||||
}`}
|
||||
style={{ height: `${(BAR_HEIGHTS[i] / maxBar) * 80}px` }}
|
||||
title={`${ACCURACY[i]}% accuracy`}
|
||||
/>
|
||||
{/* Metric Toggle */}
|
||||
<div className="flex gap-1.5">
|
||||
{(["accuracy", "questions", "hours"] as const).map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMetric(m)}
|
||||
className={`flex-1 py-1.5 text-xs font-bold rounded-lg border transition-all capitalize ${metric === m ? "bg-[#00F0FF]/10 border-[#00F0FF]/20 text-[#00F0FF]" : "bg-white/5 border-transparent text-muted-foreground hover:bg-white/10"}`}
|
||||
>
|
||||
{m === "accuracy" ? "Accuracy" : m === "questions" ? "Questions" : "Study Hrs"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bar Chart with Y-axis */}
|
||||
<div className="flex gap-2 flex-1 min-h-[120px]">
|
||||
{/* Y-axis */}
|
||||
<div className="flex flex-col justify-between pb-5 text-right pr-1">
|
||||
{[100, 75, 50, 25, 0].map(v => (
|
||||
<span key={v} className="text-[10px] text-muted-foreground font-semibold leading-none">
|
||||
{metric === "hours" ? (v / 100 * 5).toFixed(1) : `${v}${metric === "accuracy" ? "%" : ""}`}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col gap-0.5">
|
||||
{/* Grid + Bars */}
|
||||
<div className="relative flex-1 min-h-[120px]">
|
||||
{[25, 50, 75].map(pct => (
|
||||
<div key={pct} className="absolute w-full border-t border-border/15" style={{ bottom: `${pct}%` }} />
|
||||
))}
|
||||
<div className="absolute inset-0 flex items-end gap-1.5">
|
||||
{DAYS.map((day, i) => (
|
||||
<div
|
||||
key={day}
|
||||
className="flex-1 flex flex-col justify-end items-center h-full relative cursor-pointer"
|
||||
onMouseEnter={() => setHovered(i)}
|
||||
onMouseLeave={() => setHovered(null)}
|
||||
>
|
||||
{/* Tooltip */}
|
||||
{hovered === i && (
|
||||
<div className="absolute bottom-full mb-1.5 left-1/2 -translate-x-1/2 bg-background border border-border rounded-lg px-2.5 py-1.5 text-xs font-bold whitespace-nowrap shadow-lg z-10 pointer-events-none">
|
||||
<div>{day}</div>
|
||||
<div className="text-[#00F0FF]">{DATA[i]}{UNIT}</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`w-full rounded-t-md transition-all duration-700 ${
|
||||
i === 6
|
||||
? "bg-gradient-to-t from-[#00F0FF]/60 to-[#00F0FF]"
|
||||
: hovered === i
|
||||
? "bg-gradient-to-t from-[#00F0FF]/40 to-[#00F0FF]/80"
|
||||
: "bg-gradient-to-t from-white/10 to-white/25"
|
||||
}`}
|
||||
style={{ height: `${(DATA[i] / maxVal) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-[9px] text-muted-foreground font-semibold">{day}</span>
|
||||
</div>
|
||||
{/* X labels */}
|
||||
<div className="flex gap-1.5 pt-1">
|
||||
{DAYS.map((day) => (
|
||||
<div key={day} className="flex-1 text-center">
|
||||
<span className="text-[10px] text-muted-foreground font-bold">{day}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick stats row */}
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{[
|
||||
{ label: "Avg Accuracy", value: `${avgAccuracy}%`, icon: <Target size={12} className="text-green-400" /> },
|
||||
{ label: "Questions / Day", value: `${Math.round(QUESTIONS.reduce((a, b) => a + b, 0) / 7)}`, icon: <Zap size={12} className="text-[#00F0FF]" /> },
|
||||
{ label: "Study Hrs / Day", value: `${(HOURS.reduce((a, b) => a + b, 0) / 7).toFixed(1)}h`, icon: <Clock size={12} className="text-amber-400" /> },
|
||||
].map(stat => (
|
||||
<div key={stat.label} className="p-2.5 rounded-xl bg-white/5 border border-border/30 text-center">
|
||||
<div className="flex items-center justify-center gap-1 mb-0.5">{stat.icon}</div>
|
||||
<div className="text-base font-black">{stat.value}</div>
|
||||
<div className="text-[10px] text-muted-foreground font-semibold">{stat.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* AI insight */}
|
||||
<div className="px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-md text-xs text-[#00F0FF] font-medium flex items-start gap-3">
|
||||
<Brain size={16} className="shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span><strong className="text-white">You keep making sign errors in Thermodynamics.</strong> In your last 3 attempts, you've used the wrong sign for work done (W = -PΔV). Review the First Law of Thermodynamics — we've queued 3 practice questions for tomorrow.</span>
|
||||
<span className="text-[#00F0FF]/80">💡 <strong className="text-white">Tip:</strong> You perform best between 7-9 PM. Schedule difficult topics then.</span>
|
||||
<div className="px-3 py-2.5 bg-[#00F0FF]/10 border border-[#00F0FF]/20 rounded-xl text-xs text-[#00F0FF] font-medium flex items-start gap-2.5">
|
||||
<Brain size={15} className="shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<span className="font-black text-white">Sign errors in Thermodynamics</span> — detected in your last 3 attempts (W = −PΔV misapplied). We've queued 3 targeted practice questions for tomorrow.{" "}
|
||||
<span className="text-[#00F0FF]/80"><strong className="text-white">Tip:</strong> You peak at 7–9 PM. Schedule the hardest topics then.</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user