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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user