197 lines
7.8 KiB
TypeScript
197 lines
7.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
interface Registration {
|
|
id: string;
|
|
status: string;
|
|
paymentStatus: string;
|
|
createdAt: string;
|
|
user: {
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
};
|
|
webinar: {
|
|
title: string;
|
|
dateTime: string;
|
|
price: number;
|
|
};
|
|
}
|
|
|
|
export default function AdminRegistrationsPage() {
|
|
const [registrations, setRegistrations] = useState<Registration[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [filter, setFilter] = useState<string>("ALL");
|
|
|
|
useEffect(() => {
|
|
fetchRegistrations();
|
|
}, []);
|
|
|
|
const fetchRegistrations = async () => {
|
|
try {
|
|
const response = await fetch("/api/registrations");
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
setRegistrations(data.registrations || []);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch registrations:", error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const filteredRegistrations =
|
|
filter === "ALL"
|
|
? registrations
|
|
: registrations.filter((reg) => reg.status === filter);
|
|
|
|
const stats = {
|
|
total: registrations.length,
|
|
confirmed: registrations.filter((r) => r.status === "CONFIRMED").length,
|
|
pending: registrations.filter((r) => r.status === "PENDING").length,
|
|
cancelled: registrations.filter((r) => r.status === "CANCELLED").length,
|
|
};
|
|
|
|
return (
|
|
<main className="max-w-7xl mx-auto px-6 py-16">
|
|
<div className="mb-8">
|
|
<h1 className="text-4xl font-bold mb-4 bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
|
|
Registrations Management
|
|
</h1>
|
|
<p className="text-lg text-gray-600 dark:text-gray-400">
|
|
View and manage webinar registrations
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid md:grid-cols-4 gap-6 mb-8">
|
|
<div className="card p-6">
|
|
<div className="text-3xl font-bold text-primary mb-2">{stats.total}</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">Total Registrations</div>
|
|
</div>
|
|
<div className="card p-6">
|
|
<div className="text-3xl font-bold text-green-600 mb-2">{stats.confirmed}</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">Confirmed</div>
|
|
</div>
|
|
<div className="card p-6">
|
|
<div className="text-3xl font-bold text-yellow-600 mb-2">{stats.pending}</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">Pending</div>
|
|
</div>
|
|
<div className="card p-6">
|
|
<div className="text-3xl font-bold text-red-600 mb-2">{stats.cancelled}</div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">Cancelled</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="card p-6 mb-6">
|
|
<div className="flex gap-2">
|
|
{["ALL", "CONFIRMED", "PENDING", "CANCELLED"].map((status) => (
|
|
<button
|
|
key={status}
|
|
onClick={() => setFilter(status)}
|
|
className={`px-4 py-2 rounded-lg font-semibold transition-all ${
|
|
filter === status
|
|
? "bg-primary text-white"
|
|
: "bg-gray-100 dark:bg-slate-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-slate-700"
|
|
}`}
|
|
>
|
|
{status}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="text-center py-12">
|
|
<div className="inline-block w-8 h-8 border-4 border-primary border-r-transparent rounded-full animate-spin"></div>
|
|
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading registrations...</p>
|
|
</div>
|
|
) : (
|
|
<div className="card overflow-hidden">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 dark:bg-slate-800">
|
|
<tr>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 dark:text-gray-300 uppercase">
|
|
User
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 dark:text-gray-300 uppercase">
|
|
Webinar
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 dark:text-gray-300 uppercase">
|
|
Status
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 dark:text-gray-300 uppercase">
|
|
Payment
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-gray-700 dark:text-gray-300 uppercase">
|
|
Date
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200 dark:divide-slate-700">
|
|
{filteredRegistrations.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={5} className="px-6 py-12 text-center text-gray-500">
|
|
No registrations found
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
filteredRegistrations.map((reg) => (
|
|
<tr key={reg.id} className="hover:bg-gray-50 dark:hover:bg-slate-800/50">
|
|
<td className="px-6 py-4">
|
|
<div className="font-semibold text-gray-900 dark:text-white">
|
|
{reg.user.firstName} {reg.user.lastName}
|
|
</div>
|
|
<div className="text-sm text-gray-500">{reg.user.email}</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="font-semibold text-gray-900 dark:text-white">
|
|
{reg.webinar.title}
|
|
</div>
|
|
<div className="text-sm text-gray-500">
|
|
{new Date(reg.webinar.dateTime).toLocaleDateString()}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span
|
|
className={`inline-flex px-3 py-1 rounded-full text-xs font-semibold ${
|
|
reg.status === "CONFIRMED"
|
|
? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
|
: reg.status === "PENDING"
|
|
? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"
|
|
: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"
|
|
}`}
|
|
>
|
|
{reg.status}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span
|
|
className={`inline-flex px-3 py-1 rounded-full text-xs font-semibold ${
|
|
reg.paymentStatus === "COMPLETED"
|
|
? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
|
: reg.paymentStatus === "FREE"
|
|
? "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
|
: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300"
|
|
}`}
|
|
>
|
|
{reg.paymentStatus}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-gray-600 dark:text-gray-400">
|
|
{new Date(reg.createdAt).toLocaleDateString()}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</main>
|
|
);
|
|
}
|