- {stdDocsCount}
+ {stats.stdDocsCount}
Standardization Contributions
@@ -82,7 +89,7 @@ export default function HomePage() {
- {patentCount}
+ {stats.patentCount}
Patent
@@ -101,7 +108,7 @@ export default function HomePage() {
{/* Research Project PageView Component */}
-
+
{/* 14 Research Areas Grid */}
@@ -118,12 +125,12 @@ export default function HomePage() {
- {RESEARCH_AREAS.length} fundamental networking protocols and AI system domains
+ {researchAreas.length} fundamental networking protocols and AI system domains
- {RESEARCH_AREAS.map((area, idx) => {
+ {researchAreas.map((area, idx) => {
const no = String(idx + 1).padStart(2, "0");
const isOdd = (idx + 1) % 2 !== 0;
diff --git a/refer_landing_page/app/publications/[category]/page.tsx b/refer_landing_page/app/publications/[category]/page.tsx
index a927504..4ad2a28 100644
--- a/refer_landing_page/app/publications/[category]/page.tsx
+++ b/refer_landing_page/app/publications/[category]/page.tsx
@@ -2,7 +2,8 @@ import React from "react";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { CategoryNav } from "../_components/CategoryNav";
-import { publications, patents } from "../../../content/publications";
+import { publications as staticPubs, patents as staticPatents } from "../../../content/publications";
+import { getPublications, getPatents } from "../../../lib/api";
import { PUB_CATEGORIES } from "../../../content/categories";
import { PubCategory } from "../../../content/types";
@@ -30,7 +31,7 @@ export function generateStaticParams() {
}));
}
-export default function PublicationCategoryPage({ params }: PageProps) {
+export default async function PublicationCategoryPage({ params }: PageProps) {
const { category } = params;
const currentCat = PUB_CATEGORIES.find((c) => c.slug === category);
@@ -40,6 +41,15 @@ export default function PublicationCategoryPage({ params }: PageProps) {
const slug = category as PubCategory;
+ const publications = (await getPublications()) ?? staticPubs;
+ const patents = (await getPatents()) ?? staticPatents;
+
+ const counts = {
+ "intl-journal-conf": publications.filter((p) => p.category === "intl-journal-conf").length,
+ "domestic-journal-conf": publications.filter((p) => p.category === "domestic-journal-conf").length,
+ patent: patents.length,
+ };
+
let records: any[] = [];
if (slug === "patent") {
records = patents.map((p) => ({
@@ -82,7 +92,7 @@ export default function PublicationCategoryPage({ params }: PageProps) {
{/* Category Nav Cards */}
{/* Record List Section */}
diff --git a/refer_landing_page/app/publications/_components/CategoryNav.tsx b/refer_landing_page/app/publications/_components/CategoryNav.tsx
index 8c27613..b85ed39 100644
--- a/refer_landing_page/app/publications/_components/CategoryNav.tsx
+++ b/refer_landing_page/app/publications/_components/CategoryNav.tsx
@@ -1,17 +1,27 @@
import React from "react";
import Link from "next/link";
import { PUB_CATEGORIES } from "../../../content/categories";
-import { publications, patents } from "../../../content/publications";
+import { publications as staticPubs, patents as staticPatents } from "../../../content/publications";
import { PubCategory } from "../../../content/types";
+export interface CategoryCounts {
+ "intl-journal-conf"?: number;
+ "domestic-journal-conf"?: number;
+ patent?: number;
+}
+
interface CategoryNavProps {
currentCategory?: PubCategory;
+ counts?: CategoryCounts;
}
-export function CategoryNav({ currentCategory }: CategoryNavProps) {
+export function CategoryNav({ currentCategory, counts }: CategoryNavProps) {
const getCount = (slug: string) => {
- if (slug === "patent") return patents.length;
- return publications.filter((p) => p.category === slug).length;
+ if (counts?.[slug as keyof CategoryCounts] !== undefined) {
+ return counts[slug as keyof CategoryCounts]!;
+ }
+ if (slug === "patent") return staticPatents.length;
+ return staticPubs.filter((p) => p.category === slug).length;
};
return (
diff --git a/refer_landing_page/app/publications/page.tsx b/refer_landing_page/app/publications/page.tsx
index 3c23ce7..c75b58a 100644
--- a/refer_landing_page/app/publications/page.tsx
+++ b/refer_landing_page/app/publications/page.tsx
@@ -1,13 +1,23 @@
import type { Metadata } from "next";
import { CategoryNav } from "./_components/CategoryNav";
-import { publications } from "../../content/publications";
+import { publications as staticPubs, patents as staticPatents } from "../../content/publications";
+import { getPublications, getPatents } from "../../lib/api";
export const metadata: Metadata = {
title: "Publications",
description: "AI Agent Networking Lab (ANL) research publications and patents overview",
};
-export default function PublicationsIndexPage() {
+export default async function PublicationsIndexPage() {
+ const publications = (await getPublications()) ?? staticPubs;
+ const patents = (await getPatents()) ?? staticPatents;
+
+ const counts = {
+ "intl-journal-conf": publications.filter((p) => p.category === "intl-journal-conf").length,
+ "domestic-journal-conf": publications.filter((p) => p.category === "domestic-journal-conf").length,
+ patent: patents.length,
+ };
+
// Selected representative publications (curated via database isHighlight flag)
const highlights = publications.filter((p) => p.isHighlight);
@@ -22,7 +32,7 @@ export default function PublicationsIndexPage() {
{/* Category Summary Cards */}
{/* Highlights Section */}
diff --git a/refer_landing_page/app/standardization/page.tsx b/refer_landing_page/app/standardization/page.tsx
index 9c2b491..77c05d6 100644
--- a/refer_landing_page/app/standardization/page.tsx
+++ b/refer_landing_page/app/standardization/page.tsx
@@ -1,13 +1,15 @@
import React from "react";
import type { Metadata } from "next";
-import { standardsBodies } from "../../content/standards";
+import { standardsBodies as staticStandards } from "../../content/standards";
+import { getStandardsBodies } from "../../lib/api";
export const metadata: Metadata = {
title: "Standardization",
description: "AI Agent Networking Lab (ANL) international standardization research and contributions",
};
-export default function StandardizationPage() {
+export default async function StandardizationPage() {
+ const standardsBodies = (await getStandardsBodies()) ?? staticStandards;
return (
{/* Page Header */}
diff --git a/refer_landing_page/lib/api.ts b/refer_landing_page/lib/api.ts
new file mode 100644
index 0000000..d839c5d
--- /dev/null
+++ b/refer_landing_page/lib/api.ts
@@ -0,0 +1,317 @@
+import type {
+ Member,
+ Alumnus,
+ Publication,
+ Patent,
+ Semester,
+ StandardsBody,
+ ResearchProject,
+ PubCategory,
+ DocStatus,
+} from "../content/types";
+
+const API_BASE_URL = process.env.API_BASE_URL ?? "http://localhost:8080/api/v1";
+const FETCH_TIMEOUT_MS = 3000;
+
+interface APIResponse
{
+ data: T;
+}
+
+// Raw backend response schemas (snake_case)
+interface BackendResearchProject {
+ id: number;
+ slug: string;
+ title: string;
+ abstract: string;
+ keywords: string[];
+ organization?: string | null;
+ standards_org?: string | null;
+ period?: string | null;
+ funder?: string | null;
+}
+
+interface BackendResearchArea {
+ id: number;
+ name_en: string;
+ name_kr?: string | null;
+ display_order: number;
+}
+
+interface BackendStatsSummary {
+ intl_publications: number;
+ standardization_docs: number;
+ patents: number;
+}
+
+interface BackendMember {
+ id: number;
+ name_ko: string;
+ name_en: string;
+ degree: string;
+ affiliation?: string | null;
+ email?: string | null;
+ is_advisor: boolean;
+ display_order: number;
+}
+
+interface BackendAlumnus {
+ id: number;
+ name_ko: string;
+ name_en: string;
+ degree: string;
+ graduated_at: string;
+ major?: string | null;
+ current_position?: string | null;
+}
+
+interface BackendPublication {
+ id: number;
+ category: string;
+ title: string;
+ authors?: string | null;
+ venue: string;
+ volume?: string | null;
+ published_at: string;
+ kci: boolean;
+ doi?: string | null;
+ is_highlight: boolean;
+}
+
+interface BackendPatent {
+ id: number;
+ title: string;
+ inventors: string;
+ application_no: string;
+ application_at: string;
+ registration_no?: string | null;
+ registration_at?: string | null;
+ country: string;
+ published_at: string;
+}
+
+interface BackendCourse {
+ id: number;
+ semester_id: number;
+ code: string;
+ name_en: string;
+ name_kr?: string | null;
+ note?: string | null;
+ display_order: number;
+}
+
+interface BackendSemester {
+ id: number;
+ year: number;
+ term: string;
+ courses: BackendCourse[];
+}
+
+interface BackendStandardDoc {
+ id: number;
+ body_id: number;
+ wg: string;
+ project_name: string;
+ title: string;
+ doc_ref: string;
+ status: string;
+ published_at?: string | null;
+}
+
+interface BackendStandardProject {
+ wg: string;
+ name: string;
+ documents: BackendStandardDoc[];
+}
+
+interface BackendStandardsBody {
+ id: number;
+ org: string;
+ full_name: string;
+ scope: string;
+ period: string;
+ role?: string | null;
+ display_order: number;
+ projects: BackendStandardProject[];
+}
+
+/**
+ * Generic fetch helper with bounded timeout and safe JSON handling.
+ * Returns data payload or null on any failure (network, timeout, non-2xx, parse error).
+ */
+async function apiGet(path: string): Promise {
+ try {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
+ const res = await fetch(`${API_BASE_URL}${path}`, {
+ signal: controller.signal,
+ });
+ clearTimeout(timeout);
+
+ if (!res.ok) {
+ return null;
+ }
+
+ const json = (await res.json()) as APIResponse;
+ return json?.data ?? null;
+ } catch {
+ return null;
+ }
+}
+
+// --- Home Domain ---
+
+export async function getResearchProjects(): Promise {
+ const raw = await apiGet("/research-projects");
+ if (!raw) return null;
+ return raw.map((p) => ({
+ slug: p.slug,
+ title: p.title,
+ abstract: p.abstract,
+ keywords: p.keywords,
+ standardsTrack: p.organization ?? undefined,
+ standardsOrg: p.standards_org ?? undefined,
+ period: p.period ?? undefined,
+ funder: p.funder ?? undefined,
+ deliverables: [],
+ }));
+}
+
+export async function getResearchAreaNames(): Promise {
+ const raw = await apiGet("/research-areas");
+ if (!raw) return null;
+ return raw.map((a) => a.name_en);
+}
+
+export async function getStatsSummary(): Promise<{
+ intlPubsCount: number;
+ stdDocsCount: number;
+ patentCount: number;
+} | null> {
+ const raw = await apiGet("/stats/summary");
+ if (!raw) return null;
+ return {
+ intlPubsCount: raw.intl_publications,
+ stdDocsCount: raw.standardization_docs,
+ patentCount: raw.patents,
+ };
+}
+
+// --- Members Domain ---
+
+export async function getMembers(): Promise {
+ const raw = await apiGet("/members");
+ if (!raw) return null;
+ return raw.map((m) => ({
+ ko: m.name_ko,
+ en: m.name_en,
+ degree: m.degree as Member["degree"],
+ affiliation: m.affiliation ?? undefined,
+ }));
+}
+
+export async function getAlumni(): Promise {
+ const raw = await apiGet("/alumni");
+ if (!raw) return null;
+ return raw.map((a) => ({
+ ko: a.name_ko,
+ en: a.name_en,
+ degree: a.degree as Alumnus["degree"],
+ graduatedAt: a.graduated_at,
+ major: a.major ?? undefined,
+ currentPosition: a.current_position ?? undefined,
+ }));
+}
+
+// --- Publications & Patents Domain ---
+
+export async function getPublications(
+ category?: "intl-journal-conf" | "domestic-journal-conf"
+): Promise {
+ const raw = await apiGet(
+ category ? `/publications?category=${category}` : "/publications"
+ );
+ if (!raw) return null;
+ return raw.map((p) => ({
+ category: p.category as PubCategory,
+ title: p.title,
+ authors: p.authors ?? undefined,
+ venue: p.venue,
+ volume: p.volume ?? undefined,
+ publishedAt: p.published_at,
+ kci: p.kci,
+ doi: p.doi ?? undefined,
+ isHighlight: p.is_highlight,
+ }));
+}
+
+export async function getHighlights(): Promise {
+ const raw = await apiGet("/publications/highlights");
+ if (!raw) return null;
+ return raw.map((p) => ({
+ category: p.category as PubCategory,
+ title: p.title,
+ authors: p.authors ?? undefined,
+ venue: p.venue,
+ volume: p.volume ?? undefined,
+ publishedAt: p.published_at,
+ kci: p.kci,
+ doi: p.doi ?? undefined,
+ isHighlight: p.is_highlight,
+ }));
+}
+
+export async function getPatents(): Promise {
+ const raw = await apiGet("/patents");
+ if (!raw) return null;
+ return raw.map((p) => ({
+ category: "patent",
+ title: p.title,
+ inventors: p.inventors,
+ applicationNo: p.application_no,
+ applicationAt: p.application_at,
+ registrationNo: p.registration_no ?? undefined,
+ registrationAt: p.registration_at ?? undefined,
+ country: p.country,
+ publishedAt: p.published_at,
+ }));
+}
+
+// --- Lectures Domain ---
+
+export async function getSemesters(): Promise {
+ const raw = await apiGet("/semesters");
+ if (!raw) return null;
+ return raw.map((s) => ({
+ term: s.term as Semester["term"],
+ year: s.year,
+ courses: s.courses.map((c) => ({
+ code: c.code,
+ en: c.name_en,
+ note: c.note ?? undefined,
+ })),
+ }));
+}
+
+// --- Standardization Domain ---
+
+export async function getStandardsBodies(): Promise {
+ const raw = await apiGet("/standards-bodies");
+ if (!raw) return null;
+ return raw.map((b) => ({
+ org: b.org,
+ full: b.full_name,
+ scope: b.scope as StandardsBody["scope"],
+ period: b.period,
+ role: b.role ?? undefined,
+ projects: b.projects.map((p) => ({
+ wg: p.wg,
+ name: p.name,
+ documents: p.documents.map((d) => ({
+ title: d.title,
+ ref: d.doc_ref,
+ status: d.status as DocStatus,
+ publishedAt: d.published_at ?? undefined,
+ })),
+ })),
+ }));
+}