Files
website/components/publication-stats.tsx
2025-09-22 11:18:41 +02:00

117 lines
4.1 KiB
TypeScript

// app/components/publication-stats.tsx
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import * as cheerio from "cheerio";
import { PublicationChartClient } from "./publication-chart"; // <-- IMPORT THE NEW CLIENT COMPONENT
// Define the structure for our scraped data (can be shared or defined here)
interface ScholarStats {
citations: { all: number };
hIndex: { all: number };
i10Index: { all: number };
citationsPerYear: { year: number; count: number }[];
}
// Function to fetch and parse data from Google Scholar (no changes needed here)
async function getScholarStats(): Promise<ScholarStats> {
const url = "https://scholar.google.de/citations?user=NODAd94AAAAJ&hl=en";
try {
const response = await fetch(url, {
next: { revalidate: 86400 }, // Revalidate once a day
});
if (!response.ok) throw new Error("Failed to fetch Google Scholar page");
const html = await response.text();
const $ = cheerio.load(html);
const citations = parseInt($("#gsc_rsb_st > tbody > tr:nth-child(1) > td:nth-child(2)").text() || "0");
const hIndex = parseInt($("#gsc_rsb_st > tbody > tr:nth-child(2) > td:nth-child(2)").text() || "0");
const i10Index = parseInt($("#gsc_rsb_st > tbody > tr:nth-child(3) > td:nth-child(2)").text() || "0");
const citationsPerYear: { year: number; count: number }[] = [];
const yearElements = $(".gsc_g_t");
const citationElements = $(".gsc_g_a");
yearElements.each((index, element) => {
const year = parseInt($(element).text());
const count = parseInt($(citationElements[index]).find('.gsc_g_al').text() || "0");
if (!isNaN(year) && !isNaN(count)) {
citationsPerYear.push({ year, count });
}
});
return {
citations: { all: citations },
hIndex: { all: hIndex },
i10Index: { all: i10Index },
citationsPerYear: citationsPerYear,
};
} catch (error) {
console.error("Error scraping Google Scholar:", error);
return { citations: { all: 0 }, hIndex: { all: 0 }, i10Index: { all: 0 }, citationsPerYear: [] };
}
}
// The main component that renders the statistics
export async function PublicationStats() {
const stats = await getScholarStats();
const currentYear = new Date().getFullYear();
const lastFiveYearsData = stats.citationsPerYear
.filter((entry) => entry.year >= currentYear - 4)
.sort((a, b) => a.year - b.year);
return (
<div className="flex flex-col gap-4">
<h2 className="font-bold text-2xl">Publication Statistics</h2>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<div className="flex flex-col gap-4">
<Card>
<CardContent className="px-4 py-0 flex flex-col gap-1">
<p className="text-sm font-medium text-muted-foreground">
Citations
</p>
<p className="text-2xl font-bold">{stats.citations.all}</p>
</CardContent>
</Card>
<div className="grid grid-cols-2 gap-4">
<Card>
<CardContent className="px-4 py-0 flex flex-col gap-1">
<p className="text-sm font-medium text-muted-foreground">
h-index
</p>
<p className="text-2xl font-bold">{stats.hIndex.all}</p>
</CardContent>
</Card>
<Card>
<CardContent className="px-4 py-0 flex flex-col gap-1">
<p className="text-sm font-medium text-muted-foreground">
i10-index
</p>
<p className="text-2xl font-bold">{stats.i10Index.all}</p>
</CardContent>
</Card>
</div>
</div>
<Card>
<CardHeader>
<CardTitle className="text-sm font-medium">
Citations per Year
</CardTitle>
</CardHeader>
<CardContent className="h-32">
<PublicationChartClient data={lastFiveYearsData} />
</CardContent>
</Card>
</div>
</div>
);
}