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)
This commit is contained in:
2026-08-25 16:07:44 +09:00
parent d3dc7f19d9
commit 1410dcf89d
2 changed files with 198 additions and 42 deletions
@@ -18,6 +18,8 @@ export function PublicationCategoryClient({ category }: { category: string }) {
const [publications, setPublications] = useState<Publication[]>([]);
const [patents, setPatents] = useState<Patent[]>([]);
const [selectedYear, setSelectedYear] = useState<string>("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 (
<div className="container-content py-8 sm:py-12 space-y-10 sm:space-y-14">
{/* Page Header */}
@@ -80,9 +96,36 @@ export function PublicationCategoryClient({ category }: { category: string }) {
{/* Record List Section */}
<section className="space-y-6 pt-6 md:pt-8 border-t border-line">
<div className="flex justify-between items-center border-b border-line pb-3">
<div className="flex flex-wrap justify-between items-center gap-4 border-b border-line pb-3">
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-ink-mute uppercase">Filter Year:</span>
<select
value={selectedYear}
onChange={(e) => setSelectedYear(e.target.value)}
className="px-2.5 py-1 text-xs border border-line rounded bg-paper text-ink focus:outline-none focus:ring-1 focus:ring-cobalt font-mono"
>
<option value="all">All Years ({allRecords.length})</option>
{availableYears.map((yr) => {
const yrCount = allRecords.filter((r) => (r.publishedAt || "").includes(yr)).length;
return (
<option key={yr} value={yr}>
{yr} ({yrCount})
</option>
);
})}
</select>
{selectedYear !== "all" && (
<button
onClick={() => setSelectedYear("all")}
className="text-xs text-cobalt-dark hover:underline font-mono"
>
Reset
</button>
)}
</div>
<span className="font-mono text-xs text-ink-mute uppercase">
{records.length} Records
Showing {records.length} of {allRecords.length} Records
</span>
</div>