"use client"; import React, { useEffect, useState } from "react"; import { AdminHeader, AlertBanner, Modal, ConfirmDeleteModal, getClientToken } from "../_components/AdminComponents"; import { adminGetPublications, adminCreatePublication, adminUpdatePublication, adminDeletePublication, adminGetPatents, adminCreatePatent, adminUpdatePatent, adminDeletePatent, AdminPublication, AdminPatent, } from "../../../lib/adminApi"; export default function AdminPublicationsPage() { const [tab, setTab] = useState<"publications" | "patents">("publications"); const [publications, setPublications] = useState([]); const [patents, setPatents] = useState([]); const [filterCategory, setFilterCategory] = useState("all"); const [filterYear, setFilterYear] = useState("all"); const [filterPatentYear, setFilterPatentYear] = useState("all"); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [successMsg, setSuccessMsg] = useState(null); // Publication Modal State const [isPubModalOpen, setIsPubModalOpen] = useState(false); const [editingPub, setEditingPub] = useState(null); const [pubForm, setPubForm] = useState({ category: "intl-journal-conf" as "intl-journal-conf" | "domestic-journal-conf", title: "", authors: "", venue: "", volume: "", published_at: "", kci: false, doi: "", is_highlight: false, }); // Patent Modal State const [isPatentModalOpen, setIsPatentModalOpen] = useState(false); const [editingPatent, setEditingPatent] = useState(null); const [patentForm, setPatentForm] = useState({ title: "", inventors: "", application_no: "", application_at: "", registration_no: "", registration_at: "", country: "Korea", published_at: "", }); const [isSubmitting, setIsSubmitting] = useState(false); const [deletingPubId, setDeletingPubId] = useState(null); const [deletingPatentId, setDeletingPatentId] = useState(null); const loadData = async () => { try { setIsLoading(true); setError(null); const [pubList, patList] = await Promise.all([adminGetPublications(), adminGetPatents()]); setPublications(pubList); setPatents(patList); } catch (err: any) { setError(err?.message || "Failed to load publications."); } finally { setIsLoading(false); } }; useEffect(() => { loadData(); }, []); // Pub Handlers const handleOpenPubCreate = () => { setEditingPub(null); setPubForm({ category: "intl-journal-conf", title: "", authors: "", venue: "", volume: "", published_at: new Date().getFullYear().toString(), kci: false, doi: "", is_highlight: false, }); setIsPubModalOpen(true); }; const handleOpenPubEdit = (p: AdminPublication) => { setEditingPub(p); setPubForm({ category: p.category, title: p.title, authors: p.authors || "", venue: p.venue, volume: p.volume || "", published_at: p.published_at, kci: p.kci, doi: p.doi || "", is_highlight: p.is_highlight, }); setIsPubModalOpen(true); }; const handlePubSubmit = async (e: React.FormEvent) => { e.preventDefault(); const token = getClientToken(); if (!token) return; const payload = { category: pubForm.category, title: pubForm.title.trim(), authors: pubForm.authors.trim() || undefined, venue: pubForm.venue.trim(), volume: pubForm.volume.trim() || undefined, published_at: pubForm.published_at.trim(), kci: pubForm.kci, doi: pubForm.doi.trim() || undefined, is_highlight: pubForm.is_highlight, }; setIsSubmitting(true); setError(null); try { if (editingPub) { await adminUpdatePublication(token, editingPub.id, payload); setSuccessMsg(`Publication "${payload.title}" updated.`); } else { await adminCreatePublication(token, payload); setSuccessMsg(`Publication "${payload.title}" created.`); } setIsPubModalOpen(false); await loadData(); } catch (err: any) { setError(err?.message || "Failed to save publication."); } finally { setIsSubmitting(false); } }; const handleConfirmDeletePub = async () => { if (!deletingPubId) return; const token = getClientToken(); if (!token) return; setIsSubmitting(true); try { await adminDeletePublication(token, deletingPubId); setSuccessMsg("Publication deleted successfully."); setDeletingPubId(null); await loadData(); } catch (err: any) { setError(err?.message || "Failed to delete publication."); } finally { setIsSubmitting(false); } }; // Patent Handlers const handleOpenPatentCreate = () => { setEditingPatent(null); setPatentForm({ title: "", inventors: "", application_no: "", application_at: "", registration_no: "", registration_at: "", country: "Korea", published_at: new Date().getFullYear().toString(), }); setIsPatentModalOpen(true); }; const handleOpenPatentEdit = (p: AdminPatent) => { setEditingPatent(p); setPatentForm({ title: p.title, inventors: p.inventors, application_no: p.application_no, application_at: p.application_at, registration_no: p.registration_no || "", registration_at: p.registration_at || "", country: p.country, published_at: p.published_at, }); setIsPatentModalOpen(true); }; const handlePatentSubmit = async (e: React.FormEvent) => { e.preventDefault(); const token = getClientToken(); if (!token) return; const payload = { title: patentForm.title.trim(), inventors: patentForm.inventors.trim(), application_no: patentForm.application_no.trim(), application_at: patentForm.application_at.trim(), registration_no: patentForm.registration_no.trim() || undefined, registration_at: patentForm.registration_at.trim() || undefined, country: patentForm.country.trim(), published_at: patentForm.published_at.trim(), }; setIsSubmitting(true); setError(null); try { if (editingPatent) { await adminUpdatePatent(token, editingPatent.id, payload); setSuccessMsg(`Patent "${payload.title}" updated.`); } else { await adminCreatePatent(token, payload); setSuccessMsg(`Patent "${payload.title}" created.`); } setIsPatentModalOpen(false); await loadData(); } catch (err: any) { setError(err?.message || "Failed to save patent."); } finally { setIsSubmitting(false); } }; const handleConfirmDeletePatent = async () => { if (!deletingPatentId) return; const token = getClientToken(); if (!token) return; setIsSubmitting(true); try { await adminDeletePatent(token, deletingPatentId); setSuccessMsg("Patent deleted successfully."); setDeletingPatentId(null); await loadData(); } catch (err: any) { setError(err?.message || "Failed to delete patent."); } finally { setIsSubmitting(false); } }; // Extract unique sorted years from publications const pubYears = Array.from( new Set( publications .map((p) => { const match = (p.published_at || "").match(/\b(19\d\d|20\d\d)\b/); return match ? match[1] : null; }) .filter((y): y is string => Boolean(y)) ) ).sort((a, b) => b.localeCompare(a)); // Extract unique sorted years from patents const patentYears = Array.from( new Set( patents .map((p) => { const dateStr = p.published_at || p.application_at || p.registration_at || ""; const match = dateStr.match(/\b(19\d\d|20\d\d)\b/); return match ? match[1] : null; }) .filter((y): y is string => Boolean(y)) ) ).sort((a, b) => b.localeCompare(a)); const filteredPubs = publications.filter((p) => { const matchCategory = filterCategory === "all" || p.category === filterCategory; const pubYear = (p.published_at || "").match(/\b(19\d\d|20\d\d)\b/)?.[1] || ""; const matchYear = filterYear === "all" || pubYear === filterYear; return matchCategory && matchYear; }); const filteredPatents = patents.filter((p) => { if (filterPatentYear === "all") return true; const dateStr = p.published_at || p.application_at || p.registration_at || ""; const patYear = dateStr.match(/\b(19\d\d|20\d\d)\b/)?.[1] || ""; return patYear === filterPatentYear; }); return (
{tab === "publications" ? "+ Add Publication" : "+ Add Patent"} } /> {/* Tabs */}
{error && setError(null)} />} {successMsg && setSuccessMsg(null)} />} {isLoading ? (
Loading records...
) : tab === "publications" ? (
{/* Filter sub-bar */}
Category:
Year:
{(filterCategory !== "all" || filterYear !== "all") && ( )} Showing {filteredPubs.length} of {publications.length} records
{filteredPubs.length === 0 ? ( ) : ( filteredPubs.map((p) => ( )) )}
Date Title & Venue Category Actions
No publications found.
{p.published_at}
{p.title} {p.is_highlight && ( Highlight )} {p.kci && ( KCI )}
{p.venue} {p.volume && `· ${p.volume}`}
{p.authors &&
{p.authors}
}
{p.category === "intl-journal-conf" ? "International" : "Domestic"}
) : ( /* Patents Table */
{/* Filter sub-bar */}
Year:
{filterPatentYear !== "all" && ( )} Showing {filteredPatents.length} of {patents.length} records
{filteredPatents.length === 0 ? ( ) : ( filteredPatents.map((p) => ( )) )}
Date Title & Inventors App / Reg Numbers Actions
No patents found.
{p.published_at}
{p.title}
{p.inventors}
App: {p.application_no} ({p.application_at})
{p.registration_no && (
Reg: {p.registration_no} ({p.registration_at || "—"})
)}
)} {/* Pub Modal */} setIsPubModalOpen(false)} >
setPubForm({ ...pubForm, published_at: e.target.value })} placeholder="e.g. 2023-11" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPubForm({ ...pubForm, title: e.target.value })} placeholder="Full paper title" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPubForm({ ...pubForm, authors: e.target.value })} placeholder="e.g. Gildong Hong, Prof. Advisor" className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPubForm({ ...pubForm, venue: e.target.value })} placeholder="e.g. IEEE Transactions on Communications" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPubForm({ ...pubForm, volume: e.target.value })} placeholder="e.g. Vol. 70, No. 3, pp. 120-135" className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPubForm({ ...pubForm, doi: e.target.value })} placeholder="e.g. 10.1109/TCOMM.2023.123456" className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
{/* Patent Modal */} setIsPatentModalOpen(false)} >
setPatentForm({ ...patentForm, title: e.target.value })} placeholder="e.g. Method and system for optical camera communications" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPatentForm({ ...patentForm, inventors: e.target.value })} placeholder="e.g. Gildong Hong, Advisor" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPatentForm({ ...patentForm, country: e.target.value })} placeholder="e.g. Korea or USA" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPatentForm({ ...patentForm, application_no: e.target.value })} placeholder="e.g. 10-2023-0123456" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPatentForm({ ...patentForm, application_at: e.target.value })} placeholder="e.g. 2023-05-12" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPatentForm({ ...patentForm, registration_no: e.target.value })} placeholder="e.g. 10-2543210" className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPatentForm({ ...patentForm, registration_at: e.target.value })} placeholder="e.g. 2024-01-10" className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setPatentForm({ ...patentForm, published_at: e.target.value })} placeholder="e.g. 2023" required className="w-full px-3 py-2 text-sm border border-line rounded bg-ivory text-ink focus:outline-none focus:ring-1 focus:ring-cobalt" />
setDeletingPubId(null)} onConfirm={handleConfirmDeletePub} isSubmitting={isSubmitting} title="Delete Publication" description="Are you sure you want to delete this publication?" /> setDeletingPatentId(null)} onConfirm={handleConfirmDeletePatent} isSubmitting={isSubmitting} title="Delete Patent" description="Are you sure you want to delete this patent record?" />
); }