import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import Col from 'react-bootstrap/Col';
import Row from 'react-bootstrap/Row';
import { fetchProjects, fetchLanguages, type ProjectSummary, type Source } from '../api/client';
import { ContributionGraph, AllTimeGraph } from '../components/ContributionGraph';
export function DashPage() {
const projectsQ = useQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
refetchInterval: 60_000,
});
const projects = projectsQ.data ?? [];
const ranked = rankProjects(projects);
return (
<>
i rarely say anything that warrants capital letters. a peek into the
projects i'm working on is below.
{projectsQ.isLoading &&
loading...
}
{projectsQ.isError && (
error: {(projectsQ.error as Error).message}
)}
{ranked.map((p) => (
))}
>
);
}
function ProjectCard({ project: p }: { project: ProjectSummary }) {
const langsQ = useQuery({
queryKey: ['languages', p.source, p.host, p.repo],
queryFn: () => fetchLanguages(p.source as Source, p.host, p.repo),
enabled: p.source === 'github' || p.source === 'gitea',
staleTime: 10 * 60_000,
});
const langs = langsQ.data;
const topLangs = langs ? topLanguages(langs, 3) : null;
return (
{p.repo}
{p.source}
{topLangs && ` · ${topLangs}`}
{p.commit_count > 0 && {p.commit_count} commits}
{p.issue_count > 0 && {p.issue_count} issues}
{p.pr_count > 0 && {p.pr_count} prs}
{formatRange(p.first_activity, p.last_activity)}
);
}
function topLanguages(langs: Record, n: number): string {
return Object.entries(langs)
.sort(([, a], [, b]) => b - a)
.slice(0, n)
.map(([lang]) => lang.toLowerCase())
.join(', ');
}
function formatRange(first: string | null, last: string | null): string {
const fmt = (iso: string) =>
new Date(iso).toLocaleDateString('en-GB', { month: 'short', year: 'numeric' }).toLowerCase();
if (first && last) return `${fmt(first)} — ${fmt(last)}`;
if (last) return fmt(last);
return '';
}
function rankProjects(projects: ProjectSummary[]): ProjectSummary[] {
if (projects.length === 0) return [];
const now = Date.now();
const maxVolume = Math.max(...projects.map((p) => p.commit_count + p.issue_count + p.pr_count));
const oldest = Math.min(
...projects.map((p) => (p.last_activity ? new Date(p.last_activity).getTime() : 0)),
);
const range = now - oldest || 1;
return [...projects].sort((a, b) => score(b) - score(a));
function score(p: ProjectSummary): number {
const volume = (p.commit_count + p.issue_count + p.pr_count) / (maxVolume || 1);
const recency = p.last_activity
? (new Date(p.last_activity).getTime() - oldest) / range
: 0;
return 0.6 * recency + 0.4 * volume;
}
}