diff --git a/frontend/src/components/git/ForgejoHeatmap.tsx b/frontend/src/components/git/ForgejoHeatmap.tsx index ece91fc..b961e5e 100644 --- a/frontend/src/components/git/ForgejoHeatmap.tsx +++ b/frontend/src/components/git/ForgejoHeatmap.tsx @@ -523,12 +523,10 @@ function HeatmapGrid({ days, range, onRangeChange }: { {/* Contributions summary */}

- {displayedCount.toLocaleString()} + {heatmap.totalEvents.toLocaleString()} - {hoveredDay - ? `contributions on ${displayedLabel}` - : `contributions across all tracked repositories in the last ${RANGE_SUMMARY[range]}`} + contributions across all tracked repositories in the last {RANGE_SUMMARY[range]}

@@ -583,7 +581,7 @@ export function ForgejoHeatmap({ style={{background:"rgba(139,92,246,0.07)", border:"1px solid rgba(139,92,246,0.20)"}}>
+{fmtLines(totalAdditions)} - lines added + lines of code added
diff --git a/frontend/src/components/git/ForgejoIssueMetricCards.tsx b/frontend/src/components/git/ForgejoIssueMetricCards.tsx index d341794..2c01279 100644 --- a/frontend/src/components/git/ForgejoIssueMetricCards.tsx +++ b/frontend/src/components/git/ForgejoIssueMetricCards.tsx @@ -1,6 +1,6 @@ "use client"; -import type { ComponentType } from "react"; +import { useEffect, useState, type ComponentType } from "react"; import Link from "next/link"; import { AlertCircle, @@ -14,7 +14,6 @@ import { } from "lucide-react"; import type { ForgejoIssueMetrics, ForgejoRepository } from "@/lib/api-forgejo"; -import { formatRelativeTimestamp } from "@/lib/formatters"; import { cn } from "@/lib/utils"; type ForgejoIssueMetricCardsProps = { @@ -48,6 +47,17 @@ const parseDate = (value: string | null | undefined): Date | null => { return Number.isNaN(date.getTime()) ? null : date; }; +const formatRelativeTimestampLive = (date: Date, nowMs: number): string => { + const diff = Math.max(0, nowMs - date.getTime()); + const minutes = Math.round(diff / 60000); + if (minutes < 1) return "Just now"; + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.round(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.round(hours / 24); + return `${days}d ago`; +}; + const newestDate = (dates: Date[]): Date | null => { if (dates.length === 0) return null; return dates.reduce((latest, date) => @@ -89,6 +99,7 @@ const toneValueClasses: Record = { function buildSyncHealthCard( metrics: ForgejoIssueMetrics | null, repositories: ForgejoRepository[], + nowMs: number, ): MetricCard { const repositoryCount = repositories.length; const syncErrorCount = Object.values(metrics?.sync_error_counts ?? {}).filter( @@ -104,7 +115,7 @@ function buildSyncHealthCard( metricSyncDates.length > 0 ? metricSyncDates : repositorySyncDates, ); const latestSyncAge = latestSync - ? Date.now() - latestSync.getTime() + ? Math.max(0, nowMs - latestSync.getTime()) : Number.POSITIVE_INFINITY; if (repositoryCount === 0) { @@ -141,7 +152,7 @@ function buildSyncHealthCard( return { title: "Last Sync Health", value: "Stale", - caption: `Last sync ${formatRelativeTimestamp(latestSync.toISOString())}.`, + caption: `Last sync ${formatRelativeTimestampLive(latestSync, nowMs)}.`, href: "/git-projects/repositories", tone: "amber", icon: Clock3, @@ -150,7 +161,7 @@ function buildSyncHealthCard( return { title: "Last Sync Health", value: "Healthy", - caption: `Last sync ${formatRelativeTimestamp(latestSync.toISOString())}.`, + caption: `Last sync ${formatRelativeTimestampLive(latestSync, nowMs)}.`, href: "/git-projects/repositories", tone: "cyan", icon: ShieldCheck, @@ -213,6 +224,13 @@ export function ForgejoIssueMetricCards({ isLoading = false, error, }: ForgejoIssueMetricCardsProps) { + const [nowMs, setNowMs] = useState(0); + + useEffect(() => { + const id = window.setInterval(() => setNowMs(Date.now()), 1000); + return () => window.clearInterval(id); + }, []); + const openIssues = metrics?.open_issues ?? 0; const recentlyClosed = metrics?.closed_last_7_days ?? 0; const staleOpen = metrics?.stale_open_issues ?? 0; @@ -255,7 +273,7 @@ export function ForgejoIssueMetricCards({ tone: staleTone, icon: staleOpen === 0 ? CheckCircle2 : Clock3, }, - buildSyncHealthCard(metrics, repositories), + buildSyncHealthCard(metrics, repositories, nowMs), ]; return (