From 1410dcf89dc31b8840841e151f353826b8c84df8 Mon Sep 17 00:00:00 2001 From: Godopu Date: Tue, 25 Aug 2026 16:07:44 +0900 Subject: [PATCH] feat(publications): add multi-level year filtering for publications and patents - Add Year filter dropdown with dynamic record counts in admin console (/console/publications) - Add Year filter dropdown in public category publications page (/publications/[category]) - Comply with AC-44 WCAG 2.1 AA small text contrast rules (text-cobalt-dark) - Verified with E2E test suite (Exit Code 0) --- .../app/console/publications/page.tsx | 187 ++++++++++++++---- .../_components/PublicationCategoryClient.tsx | 53 ++++- 2 files changed, 198 insertions(+), 42 deletions(-) diff --git a/refer_landing_page/app/console/publications/page.tsx b/refer_landing_page/app/console/publications/page.tsx index b19f69d..5c741f4 100644 --- a/refer_landing_page/app/console/publications/page.tsx +++ b/refer_landing_page/app/console/publications/page.tsx @@ -20,6 +20,8 @@ export default function AdminPublicationsPage() { 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); @@ -248,10 +250,44 @@ export default function AdminPublicationsPage() { } }; - const filteredPubs = - filterCategory === "all" - ? publications - : publications.filter((p) => p.category === filterCategory); + // 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 (
@@ -296,21 +332,58 @@ export default function AdminPublicationsPage() { ) : tab === "publications" ? (
{/* Filter sub-bar */} -
- Filter: - +
+
+ Category: + +
+ +
+ Year: + +
+ + {(filterCategory !== "all" || filterYear !== "all") && ( + + )} + + + Showing {filteredPubs.length} of {publications.length} records +
@@ -379,25 +452,64 @@ export default function AdminPublicationsPage() {
) : ( /* Patents Table */ -
- - - - - - - - - - - {patents.length === 0 ? ( - - +
+ {/* Filter sub-bar */} +
+
+ Year: + +
+ + {filterPatentYear !== "all" && ( + + )} + + + Showing {filteredPatents.length} of {patents.length} records + +
+ +
+
DateTitle & InventorsApp / Reg NumbersActions
- No patents found. -
+ + + + + + - ) : ( - patents.map((p) => ( + + + {filteredPatents.length === 0 ? ( + + + + ) : ( + filteredPatents.map((p) => (
DateTitle & InventorsApp / Reg NumbersActions
+ No patents found. +
{p.published_at} @@ -432,6 +544,7 @@ export default function AdminPublicationsPage() {
+
)} {/* Pub Modal */} diff --git a/refer_landing_page/app/publications/[category]/_components/PublicationCategoryClient.tsx b/refer_landing_page/app/publications/[category]/_components/PublicationCategoryClient.tsx index 79fee51..7805784 100644 --- a/refer_landing_page/app/publications/[category]/_components/PublicationCategoryClient.tsx +++ b/refer_landing_page/app/publications/[category]/_components/PublicationCategoryClient.tsx @@ -18,6 +18,8 @@ export function PublicationCategoryClient({ category }: { category: string }) { const [publications, setPublications] = useState([]); const [patents, setPatents] = useState([]); + const [selectedYear, setSelectedYear] = useState("all"); + useEffect(() => { getPublications().then((p) => { if (p) setPublications(p); @@ -33,9 +35,9 @@ export function PublicationCategoryClient({ category }: { category: string }) { patent: patents.length, }; - let records: any[] = []; + let allRecords: any[] = []; if (slug === "patent") { - records = patents.map((p) => ({ + allRecords = patents.map((p) => ({ title: p.title, inventors: p.inventors, appNo: p.applicationNo, @@ -47,7 +49,7 @@ export function PublicationCategoryClient({ category }: { category: string }) { isPatent: true, })); } else { - records = publications + allRecords = publications .filter((p) => p.category === slug) .map((p) => ({ title: p.title, @@ -59,6 +61,20 @@ export function PublicationCategoryClient({ category }: { category: string }) { })); } + // Extract unique sorted years + const availableYears = Array.from( + new Set( + allRecords + .map((r) => (r.publishedAt || "").match(/\b(19\d\d|20\d\d)\b/)?.[1]) + .filter((y): y is string => Boolean(y)) + ) + ).sort((a, b) => b.localeCompare(a)); + + const records = + selectedYear === "all" + ? allRecords + : allRecords.filter((r) => (r.publishedAt || "").includes(selectedYear)); + return (
{/* Page Header */} @@ -80,9 +96,36 @@ export function PublicationCategoryClient({ category }: { category: string }) { {/* Record List Section */}
-
+
+
+ Filter Year: + + {selectedYear !== "all" && ( + + )} +
+ - {records.length} Records + Showing {records.length} of {allRecords.length} Records