feat(members,header): overhaul members page with CV dialog and english-ify navigation
- Rewrite Members page to English: LAB MEMBERS, Prof. Seok-Joo Koh, Active Researchers, and Graduates - Add ProfessorDetailsDialog component with comprehensive CV modal - Standardize degree course label mappings (Ph. D., M. S.) - Translate mobile navigation panel and header aria-labels to English - Update E2E test suite assertions for the updated members structure
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
"use client";
|
||||
|
||||
import React, { useRef } from "react";
|
||||
|
||||
// CUSTOMIZATION HOOK: Detailed career & background data for Prof. Seok-Joo Koh
|
||||
const EDUCATION = [
|
||||
"KAIST, Management Science, B. S., 1992. 2.",
|
||||
"KAIST, Management Science, M. S., 1994. 2.",
|
||||
"KAIST, Industrial Engineering, Ph. D., 1998. 8.",
|
||||
];
|
||||
|
||||
const CAREER = [
|
||||
{
|
||||
period: "2024. 3 - Present",
|
||||
role: "Dean",
|
||||
org: "College of IT Engineering (CITE)",
|
||||
inst: "Kyungpook National University (KNU)",
|
||||
},
|
||||
{
|
||||
period: "2015. 12 - Present",
|
||||
role: "Director",
|
||||
org: "Institute of Software Education (SWEDU)",
|
||||
inst: "Kyungpook National University (KNU)",
|
||||
},
|
||||
{
|
||||
period: "2004. 3 - Present",
|
||||
role: "Professor",
|
||||
org: "School of Computer Science and Engineering (CSE), College of IT Engineering (CITE)",
|
||||
inst: "Kyungpook National University (KNU)",
|
||||
},
|
||||
{
|
||||
period: "1998. 8 - 2004. 2",
|
||||
role: "Senior Researcher",
|
||||
org: "Protocol Engineering Center (PEC)",
|
||||
inst: "Electronics Telecommunications Research Institute (ETRI)",
|
||||
},
|
||||
];
|
||||
|
||||
const STANDARDIZATION = [
|
||||
"ISO/IEC JTC1 SC6/WG7: Reliable/Mobile Multicasting (1999 ~ 2019): Project Editor",
|
||||
"ISO/IEC JTC1 SC41/WG5: Internet-of-Things (IoT) Applications (2020 ~ Present): Project Editor",
|
||||
"ITU-T SG13: Mobility Management (1999 ~ 2015): Project Editor",
|
||||
"ITU-T SG20: VLC-based IoT (2018 ~ 2020): Project Editor",
|
||||
"IEC TC100: Multimedia Systems and Equipment (2018 ~ Present): Project Editor, TA15 TAM, WG11 Convenor",
|
||||
];
|
||||
|
||||
const MEMBERSHIPS = [
|
||||
"KIPS (Korea Information Processing Society)",
|
||||
"KICS (Korean Institute of Communications and Information Sciences)",
|
||||
"KIISE (Korean Institute of Information Scientists and Engineers)",
|
||||
"IEEE (Institute of Electrical and Electronics Engineers)",
|
||||
];
|
||||
|
||||
export function ProfessorDetailsDialog() {
|
||||
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||
|
||||
const handleOpen = () => {
|
||||
dialogRef.current?.showModal();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dialogRef.current?.close();
|
||||
};
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
|
||||
if (e.target === dialogRef.current) {
|
||||
dialogRef.current?.close();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpen}
|
||||
className="inline-flex items-center gap-1 font-mono text-xs font-semibold text-cobalt-dark hover:underline focus:outline-none focus:ring-1 focus:ring-cobalt-dark rounded"
|
||||
aria-haspopup="dialog"
|
||||
>
|
||||
<span>Details →</span>
|
||||
</button>
|
||||
|
||||
<dialog
|
||||
ref={dialogRef}
|
||||
aria-labelledby="prof-dialog-title"
|
||||
onClick={handleBackdropClick}
|
||||
className="bg-paper text-ink border border-line rounded-lg p-0 max-w-2xl w-[92vw] max-h-[85vh] max-h-[85dvh] backdrop:bg-ink/50 backdrop:backdrop-blur-sm open:flex open:flex-col shadow-2xl m-auto"
|
||||
>
|
||||
{/* Modal Header */}
|
||||
<div className="flex items-center justify-between border-b border-line p-6 bg-paper sticky top-0 z-10">
|
||||
<div>
|
||||
<span className="font-mono text-xs text-cobalt-dark uppercase tracking-wider font-bold">
|
||||
Curriculum Vitae
|
||||
</span>
|
||||
<h2
|
||||
id="prof-dialog-title"
|
||||
className="font-serif text-2xl font-normal text-ink mt-0.5"
|
||||
>
|
||||
Prof. Seok-Joo Koh
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClose}
|
||||
aria-label="Close dialog"
|
||||
className="p-1.5 rounded border border-line hover:bg-ivory text-ink-mute hover:text-ink transition-colors font-mono text-xs"
|
||||
>
|
||||
✕ Close
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Modal Scrollable Content */}
|
||||
<div className="p-6 sm:p-8 space-y-8 overflow-y-auto overscroll-contain text-sm">
|
||||
{/* Education */}
|
||||
<section className="space-y-3">
|
||||
<h3 className="font-mono text-xs font-bold uppercase tracking-wider text-cobalt-dark border-b border-line pb-1.5">
|
||||
Education
|
||||
</h3>
|
||||
<ul className="space-y-2 text-ink/90 font-sans">
|
||||
{EDUCATION.map((edu, idx) => (
|
||||
<li key={idx} className="flex items-start gap-2.5">
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full bg-cobalt-dark mt-2 shrink-0"></span>
|
||||
<span>{edu}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Career */}
|
||||
<section className="space-y-3">
|
||||
<h3 className="font-mono text-xs font-bold uppercase tracking-wider text-cobalt-dark border-b border-line pb-1.5">
|
||||
Career
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{CAREER.map((car, idx) => (
|
||||
<div key={idx} className="border-l-2 border-line pl-3 space-y-0.5">
|
||||
<div className="font-mono text-xs text-ink-mute">{car.period}</div>
|
||||
<div className="font-medium text-ink">
|
||||
{car.role} · <span className="text-ink-mute font-normal">{car.org}</span>
|
||||
</div>
|
||||
<div className="text-xs text-ink-soft">{car.inst}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* International Standardization */}
|
||||
<section className="space-y-3">
|
||||
<h3 className="font-mono text-xs font-bold uppercase tracking-wider text-cobalt-dark border-b border-line pb-1.5">
|
||||
International Standardization
|
||||
</h3>
|
||||
<ul className="space-y-2.5 text-ink/90 font-sans">
|
||||
{STANDARDIZATION.map((std, idx) => (
|
||||
<li key={idx} className="flex items-start gap-2.5 leading-relaxed">
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full bg-cobalt-dark mt-2 shrink-0"></span>
|
||||
<span>{std}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{/* Membership on Academic Society */}
|
||||
<section className="space-y-3">
|
||||
<h3 className="font-mono text-xs font-bold uppercase tracking-wider text-cobalt-dark border-b border-line pb-1.5">
|
||||
Membership on Academic Society
|
||||
</h3>
|
||||
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-ink/90 font-sans">
|
||||
{MEMBERSHIPS.map((mem, idx) => (
|
||||
<li key={idx} className="flex items-start gap-2">
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full bg-cobalt-dark mt-2 shrink-0"></span>
|
||||
<span>{mem}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,74 +1,73 @@
|
||||
import React from "react";
|
||||
import { activeMembers, alumni } from "../../content/members";
|
||||
import { ProfessorDetailsDialog } from "./_components/ProfessorDetailsDialog";
|
||||
|
||||
const DEGREE_LABEL: Record<string, string> = {
|
||||
Professor: "Professor",
|
||||
PhD: "Ph. D.",
|
||||
PhDCandidate: "Ph. D. Candidate",
|
||||
PhDStudent: "Ph. D. Student",
|
||||
MSCandidate: "M. S. Candidate",
|
||||
MSStudent: "M. S. Student",
|
||||
MS: "M. S.",
|
||||
};
|
||||
|
||||
export default function MembersPage() {
|
||||
const professor = activeMembers.find((m) => m.degree === "Professor");
|
||||
const phdStudents = activeMembers.filter(
|
||||
(m) => m.degree === "PhD" || m.degree === "PhDCandidate" || m.degree === "PhDStudent"
|
||||
(m) =>
|
||||
m.degree === "PhD" ||
|
||||
m.degree === "PhDCandidate" ||
|
||||
m.degree === "PhDStudent"
|
||||
);
|
||||
const msStudents = activeMembers.filter(
|
||||
(m) => m.degree === "MSCandidate" || m.degree === "MSStudent"
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="container-content space-y-12 md:space-y-16 pt-10 md:pt-16 pb-16 md:pb-24">
|
||||
{/* Header */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-px w-8 bg-cobalt-dark"></span>
|
||||
<span className="font-mono text-xs tracking-widest uppercase text-cobalt-dark font-bold">
|
||||
People & Researchers
|
||||
</span>
|
||||
</div>
|
||||
<div className="container-content py-8 sm:py-12 space-y-12 sm:space-y-16">
|
||||
{/* Page Header */}
|
||||
<section>
|
||||
<h1 className="font-serif text-4xl sm:text-5xl font-normal text-ink">
|
||||
연구실 구성원
|
||||
LAB MEMBERS
|
||||
</h1>
|
||||
<p className="text-ink-mute text-base max-w-2xl">
|
||||
지도교수와 재학 연구원 16명, 그리고 IT 산업체 및 학계에서 활약하고 있는 60명의 졸업생 인벤토리입니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Professor Section */}
|
||||
<section className="space-y-6 pt-6 md:pt-10 border-t border-line">
|
||||
<div className="font-mono text-xs text-cobalt-dark uppercase tracking-widest font-bold">
|
||||
Faculty
|
||||
</div>
|
||||
|
||||
<div className="bg-paper p-8 rounded-lg border border-line grid grid-cols-1 lg:grid-cols-12 gap-8 items-center">
|
||||
<div className="lg:col-span-8 space-y-4">
|
||||
<div className="flex items-baseline gap-3">
|
||||
<h2 className="font-serif text-3xl font-normal text-ink">
|
||||
{professor?.ko || "고석주"} 교수
|
||||
</h2>
|
||||
<span className="font-mono text-sm text-ink-mute">
|
||||
{professor?.en || "Seok-Joo Koh"}, Professor
|
||||
</span>
|
||||
<h2 className="font-serif text-3xl font-normal text-ink">
|
||||
Prof. Seok-Joo Koh
|
||||
</h2>
|
||||
<div className="text-sm text-ink-mute leading-relaxed space-y-2">
|
||||
<p>
|
||||
Professor at the School of Computer Science and Engineering, Kyungpook National University, and Dean of the College of IT Engineering. He directs the AI Agent Networking Laboratory (ANL), leading research on QUIC/gRPC transport, A2A/ACP protocols, MCP & SKILL architectures, and multi-agent orchestration, and serves as Project Editor in ISO/IEC JTC1, ITU-T, and IEC TC100.
|
||||
</p>
|
||||
<div>
|
||||
<ProfessorDetailsDialog />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-ink-mute leading-relaxed">
|
||||
경북대학교 컴퓨터학부 교수 · AI 에이전트 네트워크 연구실 (ANL) 지도교수.
|
||||
QUIC/gRPC 통신, A2A/ACP 프로토콜, MCP & SKILL 아키텍처 및 에이전트 오케스트레이션 연구를 총괄하고 있습니다.
|
||||
</p>
|
||||
<div className="pt-4 border-t border-line/60 flex flex-wrap gap-4 text-xs font-mono">
|
||||
<a
|
||||
href="mailto:sjkoh@knu.ac.kr"
|
||||
className="text-cobalt-dark hover:underline"
|
||||
className="text-cobalt-dark hover:underline font-bold"
|
||||
>
|
||||
sjkoh@knu.ac.kr
|
||||
</a>
|
||||
<span className="text-ink-mute">•</span>
|
||||
<span className="text-ink-mute">|</span>
|
||||
<a
|
||||
href="https://github.com/sjkoh12"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-cobalt-dark hover:underline"
|
||||
rel="noopener noreferrer"
|
||||
className="text-ink hover:text-cobalt-dark hover:underline"
|
||||
>
|
||||
GitHub @sjkoh12 ↗
|
||||
GitHub Profile ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:col-span-4 bg-ivory p-6 rounded border border-line text-xs space-y-2 font-mono">
|
||||
<div className="text-ink font-bold border-b border-line pb-1">
|
||||
Affiliation & Office
|
||||
Affiliation & Office
|
||||
</div>
|
||||
<p className="text-ink-mute">Kyungpook National University</p>
|
||||
<p className="text-ink-mute">IT Building #5 Room 527</p>
|
||||
@@ -81,11 +80,8 @@ export default function MembersPage() {
|
||||
<section className="space-y-8 pt-6 md:pt-10 border-t border-line">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<div className="font-mono text-xs text-cobalt-dark uppercase tracking-widest font-bold">
|
||||
Active Researchers ({activeMembers.length} Members)
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl font-normal text-ink mt-1">
|
||||
재학 연구원
|
||||
<h2 className="font-serif text-3xl font-normal text-cobalt-dark">
|
||||
Active Researchers
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,7 +102,7 @@ export default function MembersPage() {
|
||||
{mem.ko}
|
||||
</span>
|
||||
<span className="font-mono text-xs text-cobalt-dark">
|
||||
{mem.degree}
|
||||
{DEGREE_LABEL[mem.degree] || mem.degree}
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-mono text-xs text-ink-mute">{mem.en}</div>
|
||||
@@ -136,7 +132,7 @@ export default function MembersPage() {
|
||||
{mem.ko}
|
||||
</span>
|
||||
<span className="font-mono text-xs text-cobalt-dark">
|
||||
{mem.degree}
|
||||
{DEGREE_LABEL[mem.degree] || mem.degree}
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-mono text-xs text-ink-mute">{mem.en}</div>
|
||||
@@ -155,11 +151,8 @@ export default function MembersPage() {
|
||||
<section className="space-y-6 pt-6 md:pt-10 border-t border-line">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<div className="font-mono text-xs text-ink-mute uppercase tracking-widest font-bold">
|
||||
Alumni Inventory ({alumni.length} Graduates)
|
||||
</div>
|
||||
<h2 className="font-serif text-3xl font-normal text-ink mt-1">
|
||||
졸업생 목록
|
||||
<h2 className="font-serif text-3xl font-normal text-ink">
|
||||
Graduates
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,15 +161,15 @@ export default function MembersPage() {
|
||||
<summary className="flex justify-between items-center cursor-pointer list-none select-none">
|
||||
<div className="space-y-1">
|
||||
<span className="font-serif text-xl font-normal text-ink">
|
||||
전체 졸업생 {alumni.length}명 열람하기
|
||||
Browse all graduates
|
||||
</span>
|
||||
<p className="text-xs text-ink-mute font-mono">
|
||||
2007년부터 2026년까지 수여된 박사(Ph.D.) 및 석사(M.S.) 졸업생 이력
|
||||
Ph. D. and M. S. graduates conferred from 2007 to 2026
|
||||
</p>
|
||||
</div>
|
||||
<span className="font-mono text-xs text-vermillion-dark font-bold border border-vermillion-dark px-3 py-1.5 rounded group-open:bg-vermillion-dark group-open:text-white dark:group-open:text-ivory transition-colors">
|
||||
<span className="group-open:hidden">전체 보기 ▾</span>
|
||||
<span className="hidden group-open:inline">접기 ▴</span>
|
||||
<span className="group-open:hidden">Open ▾</span>
|
||||
<span className="hidden group-open:inline">Close ▴</span>
|
||||
</span>
|
||||
</summary>
|
||||
|
||||
@@ -191,7 +184,7 @@ export default function MembersPage() {
|
||||
{alum.ko} ({alum.en})
|
||||
</span>
|
||||
<span className="font-mono text-xs text-ink-mute">
|
||||
{alum.degree}
|
||||
{DEGREE_LABEL[alum.degree] || alum.degree}
|
||||
</span>
|
||||
</div>
|
||||
<div className="font-mono text-xs text-ink-mute flex justify-between">
|
||||
|
||||
@@ -12,11 +12,11 @@ const NAV_SETTLE_MS = 300; // ms — 라우트 전환 직후 방향 판정 유
|
||||
|
||||
// CUSTOMIZATION HOOK: edit nav labels / routes here.
|
||||
const navItems = [
|
||||
{ href: "/", ko: "홈", en: "Home", no: "01" },
|
||||
{ href: "/members", ko: "구성원", en: "Members", no: "02" },
|
||||
{ href: "/publications", ko: "논문", en: "Publications", no: "03" },
|
||||
{ href: "/lectures", ko: "강의", en: "Lectures", no: "04" },
|
||||
{ href: "/standardization", ko: "표준화", en: "Standardization", no: "05" },
|
||||
{ href: "/", en: "Home", no: "01" },
|
||||
{ href: "/members", en: "Members", no: "02" },
|
||||
{ href: "/publications", en: "Publications", no: "03" },
|
||||
{ href: "/lectures", en: "Lectures", no: "04" },
|
||||
{ href: "/standardization", en: "Standardization", no: "05" },
|
||||
];
|
||||
|
||||
export default function Header() {
|
||||
@@ -170,7 +170,7 @@ export default function Header() {
|
||||
|
||||
{/* Desktop nav — small caps, wide tracking (visible at >= 1240px) */}
|
||||
<div className="hidden items-center gap-4 desktop:flex">
|
||||
<nav className="flex items-center gap-2" aria-label="주 메뉴">
|
||||
<nav className="flex items-center gap-2" aria-label="Main Navigation">
|
||||
{navItems.map((item) => {
|
||||
const active = isActive(item.href);
|
||||
return (
|
||||
@@ -206,7 +206,7 @@ export default function Header() {
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center border border-ink p-2 text-ink transition hover:bg-ink hover:text-ivory"
|
||||
aria-label={open ? "메뉴 닫기" : "메뉴 열기"}
|
||||
aria-label={open ? "Close menu" : "Open menu"}
|
||||
aria-expanded={open}
|
||||
aria-controls="mobile-menu"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
@@ -227,7 +227,7 @@ export default function Header() {
|
||||
<nav
|
||||
id="mobile-menu"
|
||||
className="mobile-menu-panel border-t border-ink bg-ivory desktop:hidden"
|
||||
aria-label="모바일 메뉴"
|
||||
aria-label="Mobile Navigation"
|
||||
>
|
||||
<ul className="container-content flex flex-col py-2">
|
||||
{navItems.map((item) => {
|
||||
@@ -252,14 +252,14 @@ export default function Header() {
|
||||
>
|
||||
{item.no}
|
||||
</span>
|
||||
<span className="headline-ko text-lg">{item.ko}</span>
|
||||
<span className="font-sans text-base">{item.en}</span>
|
||||
</span>
|
||||
<span
|
||||
className={`text-[0.7rem] font-semibold uppercase tracking-[0.18em] ${
|
||||
active ? "text-white dark:text-ivory" : "text-ink-mute"
|
||||
className={`text-xs font-mono ${
|
||||
active ? "text-white/80 dark:text-ivory/80" : "text-ink-mute"
|
||||
}`}
|
||||
>
|
||||
{item.en}
|
||||
→
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -54,12 +54,16 @@ class TestANLHomepageE2E(unittest.TestCase):
|
||||
html = self.read_html("members.html")
|
||||
|
||||
# Professor profile
|
||||
self.assertIn("고석주", html)
|
||||
self.assertIn("Prof. Seok-Joo Koh", html)
|
||||
self.assertIn("Seok-Joo Koh", html)
|
||||
self.assertIn("sjkoh@knu.ac.kr", html)
|
||||
|
||||
# Members content sanity checks
|
||||
self.assertIn("Members", html)
|
||||
# Sections & Degree formatting checks
|
||||
self.assertIn("LAB MEMBERS", html)
|
||||
self.assertIn("Active Researchers", html)
|
||||
self.assertIn("Graduates", html)
|
||||
self.assertIn("Ph. D. Candidate", html)
|
||||
self.assertIn("Project Editor", html)
|
||||
|
||||
def test_03_publications_category_routes(self):
|
||||
"""TC-T1-F5: Verify Publications categories SSG static pages."""
|
||||
|
||||
Reference in New Issue
Block a user