feat: admin date-time calendar controller, dpp ui cleanup, 4-subject jee+neet tracker, calendar filter, and clean new account state

This commit is contained in:
2026-10-23 12:23:36 +05:30
parent 8a5ea57feb
commit 19edf83684
10 changed files with 1143 additions and 200 deletions

View File

@@ -10,6 +10,9 @@ import { Activity, Clock, PlayCircle, Search, Lock, CheckSquare } from "lucide-r
import { useActiveUser } from "@/hooks/useActiveUser";
import { PremiumGate } from "@/components/PremiumGate";
import { useMemo } from "react";
import { CalendarFilter, getLocalDateString } from "@/components/CalendarFilter";
interface TestData {
id: string;
title: string;
@@ -91,21 +94,51 @@ export default function CBTPage() {
loadTests();
}, [userId, selectedPattern]);
const scheduledDates = useMemo(() => {
return exams
.filter((e) => !!e.scheduled_date)
.map((e) => getLocalDateString(e.scheduled_date))
.filter(Boolean)
}, [exams]);
const filteredExams = exams.filter(exam => {
const matchesSearch = exam.title.toLowerCase().includes(search.toLowerCase());
if (!matchesSearch) return false;
if (dateFilter) {
if (!exam.scheduled_date) return false;
const scheduledDate = new Date(exam.scheduled_date).toISOString().split('T')[0];
const scheduledDate = getLocalDateString(exam.scheduled_date);
if (scheduledDate !== dateFilter) return false;
}
return true;
});
const formatDateTime = (dateStr: string) => {
return new Date(dateStr).toLocaleString("en-US", {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}
const formatDate = (dateStr: string) => {
return new Date(dateStr).toLocaleDateString("en-US", { year: 'numeric', month: 'short', day: 'numeric' });
return new Date(dateStr).toLocaleDateString("en-US", {
month: 'short',
day: 'numeric',
year: 'numeric'
});
}
const formatTime = (dateStr: string) => {
return new Date(dateStr).toLocaleTimeString("en-US", {
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}
// Get the highest score for each test if attempted multiple times
@@ -143,15 +176,13 @@ export default function CBTPage() {
Real exam environment simulations to conquer {selectedPattern === "NEET" ? "NEET" : (targetExam || "JEE")}.
</p>
</div>
<div className="flex flex-col sm:flex-row gap-3">
<div className="relative w-full sm:w-48">
<input
type="date"
value={dateFilter}
onChange={(e) => setDateFilter(e.target.value)}
className="w-full px-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-[var(--cyan-ai)] outline-none text-foreground dark:[color-scheme:dark] [color-scheme:light]"
/>
</div>
<div className="flex flex-col sm:flex-row items-center gap-3">
<CalendarFilter
selectedDate={dateFilter}
onSelectDate={setDateFilter}
availableDates={scheduledDates}
placeholder="Filter by Exam Date"
/>
<div className="relative w-full sm:w-64">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" size={16} />
<input
@@ -241,14 +272,32 @@ export default function CBTPage() {
</h3>
<div className="text-sm text-muted-foreground space-y-1 mb-6">
<div className="font-medium text-foreground">
{isUpcoming ? `Available on ${formatDate(exam.scheduled_date!)}` : isExpired ? `Expired on ${formatDate(exam.end_date!)}` : 'Available Now'}
{isUpcoming && exam.scheduled_date ? (
<span className="text-amber-500 font-semibold">
Opens: {formatDateTime(exam.scheduled_date)}
</span>
) : isExpired && exam.end_date ? (
<span className="text-destructive font-semibold">
Expired on {formatDateTime(exam.end_date)}
</span>
) : exam.end_date ? (
<span className="text-emerald-500 font-semibold">
Live Now • Closes {formatDateTime(exam.end_date)}
</span>
) : exam.scheduled_date ? (
<span className="text-emerald-500 font-semibold">
Available since {formatDateTime(exam.scheduled_date)}
</span>
) : (
<span className="text-emerald-500 font-semibold">Available Now</span>
)}
</div>
<div>{exam.total_time} Min • {exam.difficulty} Difficulty</div>
</div>
{isUpcoming ? (
<Button disabled className="w-full gap-2 rounded-xl bg-secondary text-muted-foreground font-bold">
<Clock size={16} /> Starts Soon
<Button disabled className="w-full gap-2 rounded-xl bg-amber-500/10 text-amber-500 border border-amber-500/30 font-bold">
<Clock size={16} /> Opens at {exam.scheduled_date ? formatTime(exam.scheduled_date) : "Scheduled Time"}
</Button>
) : isLocked ? (
<Button