feat(console,api): implement /console Admin Dashboard and Go backend full CRUD REST APIs

- Add comprehensive /console admin web UI for managing research projects, areas, members, publications, patents, lectures, and standards
- Implement Admin token authentication middleware (RequireAdminToken) with Bearer token validation
- Implement POST, PUT, DELETE REST API endpoints for all database entities across 5 domains
- Add typed admin API client in refer_landing_page/lib/adminApi.ts with localStorage session persistence
- Enhance E2E test runner to manage backend server lifecycle during automated tests
- Pass all 12 quality gates cleanly with unanimous reviewer PASS verdicts
This commit is contained in:
2026-08-24 22:21:12 +09:00
parent dadd14feb1
commit 3f452468d6
32 changed files with 5466 additions and 279 deletions
+187
View File
@@ -0,0 +1,187 @@
"use client";
import React, { useEffect, useState } from "react";
import Link from "next/link";
import { AdminHeader, AlertBanner } from "./_components/AdminComponents";
import {
adminGetStatsSummary,
adminGetResearchProjects,
adminGetResearchAreas,
adminGetMembers,
adminGetAlumni,
adminGetPublications,
adminGetPatents,
adminGetSemesters,
adminGetStandardsBodies,
AdminStatsSummary,
} from "../../lib/adminApi";
export default function AdminDashboardPage() {
const [stats, setStats] = useState<AdminStatsSummary | null>(null);
const [counts, setCounts] = useState<{
projects: number;
areas: number;
members: number;
alumni: number;
intlPubs: number;
domPubs: number;
patents: number;
semesters: number;
courses: number;
bodies: number;
docs: number;
}>({
projects: 0,
areas: 0,
members: 0,
alumni: 0,
intlPubs: 0,
domPubs: 0,
patents: 0,
semesters: 0,
courses: 0,
bodies: 0,
docs: 0,
});
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function loadData() {
try {
setIsLoading(true);
const [
statsData,
projects,
areas,
members,
alumni,
intlPubs,
domPubs,
patents,
semesters,
bodies,
] = await Promise.all([
adminGetStatsSummary().catch(() => null),
adminGetResearchProjects().catch(() => []),
adminGetResearchAreas().catch(() => []),
adminGetMembers().catch(() => []),
adminGetAlumni().catch(() => []),
adminGetPublications("intl-journal-conf").catch(() => []),
adminGetPublications("domestic-journal-conf").catch(() => []),
adminGetPatents().catch(() => []),
adminGetSemesters().catch(() => []),
adminGetStandardsBodies().catch(() => []),
]);
if (statsData) setStats(statsData);
const totalCourses = semesters.reduce((acc, s) => acc + (s.courses?.length || 0), 0);
const totalDocs = bodies.reduce(
(acc, b) => acc + (b.projects?.reduce((pAcc, p) => pAcc + (p.documents?.length || 0), 0) || 0),
0
);
setCounts({
projects: projects.length,
areas: areas.length,
members: members.length,
alumni: alumni.length,
intlPubs: intlPubs.length,
domPubs: domPubs.length,
patents: patents.length,
semesters: semesters.length,
courses: totalCourses,
bodies: bodies.length,
docs: totalDocs,
});
} catch (err: any) {
setError(err?.message || "Failed to load dashboard data from backend API.");
} finally {
setIsLoading(false);
}
}
loadData();
}, []);
const cards = [
{
title: "Research Projects",
count: counts.projects,
sub: `${counts.areas} Research Areas`,
href: "/console/research-projects",
color: "border-cobalt/30 hover:border-cobalt",
},
{
title: "Members & Alumni",
count: counts.members,
sub: `${counts.alumni} Alumni Records`,
href: "/console/members",
color: "border-emerald-500/30 hover:border-emerald-500",
},
{
title: "Publications & Patents",
count: counts.intlPubs + counts.domPubs,
sub: `${counts.intlPubs} Intl / ${counts.domPubs} Dom / ${counts.patents} Patents`,
href: "/console/publications",
color: "border-blue-500/30 hover:border-blue-500",
},
{
title: "Lectures & Courses",
count: counts.semesters,
sub: `${counts.courses} Total Courses across Semesters`,
href: "/console/lectures",
color: "border-amber-500/30 hover:border-amber-500",
},
{
title: "Standardization",
count: counts.bodies,
sub: `${counts.docs} Standard Documents`,
href: "/console/standardization",
color: "border-purple-500/30 hover:border-purple-500",
},
{
title: "Research Areas",
count: counts.areas,
sub: "Active Laboratory Focus Areas",
href: "/console/research-areas",
color: "border-indigo-500/30 hover:border-indigo-500",
},
];
return (
<div>
<AdminHeader
title="Administrative Overview"
description="Live status of all ANL laboratory records and datasets stored in the backend."
/>
{error && <AlertBanner type="error" message={error} onClose={() => setError(null)} />}
{isLoading ? (
<div className="py-12 text-center text-sm text-ink/70">Loading dashboard statistics...</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{cards.map((card) => (
<Link
key={card.title}
href={card.href}
className={`p-6 bg-paper rounded-lg border ${card.color} transition-all shadow-sm flex flex-col justify-between`}
>
<div>
<h2 className="text-base font-semibold text-ink">{card.title}</h2>
<div className="text-3xl font-extrabold text-cobalt mt-3">{card.count}</div>
<p className="text-xs text-ink/70 mt-1">{card.sub}</p>
</div>
<div className="mt-4 pt-4 border-t border-line text-xs font-medium text-cobalt flex items-center justify-between">
<span>Manage Records</span>
<span>&rarr;</span>
</div>
</Link>
))}
</div>
)}
</div>
);
}