-
-
-
- Commit Activity ({contributionData.days} days)
-
-
- Total commits: {contributionData.total}
-
-
-
-
- {data.length === 0 ? (
-
- No commit data available.
-
- ) : (
-
-
- {data.length} active days
-
-
- {data.map((day) => (
-
0 ? "var(--accent)" : "var(--control)",
- opacity:
- day.commits > 0
- ? Math.max(0.2, Math.min(day.commits / 10, 1))
- : 1,
- }}
- title={`${day.day}: ${day.commits} commits`}
- />
- ))}
-
-
- )}
-
- );
-}
-
function PublicStreakTracker({ streak }: { streak: any }) {
const stats = [
{
diff --git a/src/components/ContributionHeatmap.tsx b/src/components/ContributionHeatmap.tsx
index db8e0b260..7e5a02eb0 100644
--- a/src/components/ContributionHeatmap.tsx
+++ b/src/components/ContributionHeatmap.tsx
@@ -3,8 +3,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useHeatmapTheme } from "@/hooks/useHeatmapTheme";
import DailyBreakdownSheet from "@/components/DailyBreakdownSheet";
-import { getContributionInsights } from "@/lib/contribution-insights";
-import { Calendar, TrendingUp, Zap, Clock, Award, BarChart2 } from "lucide-react";
+import ContributionHeatmapCalendar from "@/components/ContributionHeatmapCalendar";
+import { formatDateKey, countInRangeCommits, buildHeatmap } from "@/lib/contribution-heatmap";
interface ContributionHeatmapProps {
days?: number;
@@ -14,20 +14,7 @@ interface ContributionResponse {
data: Record
;
}
-interface HeatmapCell {
- date: Date;
- dateKey: string;
- count: number;
- inRange: boolean;
-}
-
const DEFAULT_DAYS = 365;
-const CELL_SIZE = 14;
-const CELL_GAP = 3;
-const LABEL_WIDTH = 48;
-const HEADER_HEIGHT = 20;
-
-const DAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const PRESET_RANGES = [
{ label: "30d", days: 30 },
@@ -36,64 +23,6 @@ const PRESET_RANGES = [
{ label: "1yr", days: 365 },
] as const;
-// Memoized formatting engine to avoid recreation garbage collection cycles inside render loops
-const monthFormatter = new Intl.DateTimeFormat("en-US", { month: "short" });
-
-function formatDateKey(date: Date) {
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, "0");
- const day = String(date.getDate()).padStart(2, "0");
- return `${year}-${month}-${day}`;
-}
-
-function formatCommitCount(count: number) {
- return `${count} commit${count === 1 ? "" : "s"}`;
-}
-
-function buildHeatmap(days: number, contributions: Record, fromDate?: string, toDate?: string) {
- let endDate: Date;
- let startDate: Date;
-
- if (fromDate && toDate) {
- // Use provided custom date range
- endDate = new Date(toDate);
- endDate.setHours(23, 59, 59, 999);
- startDate = new Date(fromDate);
- startDate.setHours(0, 0, 0, 0);
- } else {
- // Calculate from N days ago until today
- endDate = new Date();
- endDate.setHours(23, 59, 59, 999);
- startDate = new Date(endDate);
- startDate.setDate(endDate.getDate() - (days - 1));
- startDate.setHours(0, 0, 0, 0);
- }
-
- const firstWeekStart = new Date(startDate);
- firstWeekStart.setDate(startDate.getDate() - startDate.getDay());
- firstWeekStart.setHours(0, 0, 0, 0);
-
- const lastWeekEnd = new Date(endDate);
- lastWeekEnd.setDate(endDate.getDate() + (6 - endDate.getDay()));
- lastWeekEnd.setHours(23, 59, 59, 999);
-
- const cells: HeatmapCell[] = [];
- const cursor = new Date(firstWeekStart);
-
- while (cursor <= lastWeekEnd) {
- const dateKey = formatDateKey(cursor);
- cells.push({
- date: new Date(cursor),
- dateKey,
- count: contributions[dateKey] ?? 0,
- inRange: cursor >= startDate && cursor <= endDate,
- });
- cursor.setDate(cursor.getDate() + 1);
- }
-
- return cells;
-}
-
export default function ContributionHeatmap({
days = DEFAULT_DAYS,
}: ContributionHeatmapProps) {
@@ -105,7 +34,6 @@ export default function ContributionHeatmap({
const [selectedDate, setSelectedDate] = useState(null);
const handleCloseSheet = useCallback(() => setSelectedDate(null), []);
- // Range state
const [selectedDays, setSelectedDays] = useState(days);
const [showPopover, setShowPopover] = useState(false);
const [customFrom, setCustomFrom] = useState("");
@@ -114,7 +42,6 @@ export default function ContributionHeatmap({
const [customError, setCustomError] = useState(null);
const popoverRef = useRef(null);
- // Load persisted range preference
useEffect(() => {
if (typeof window !== "undefined") {
try {
@@ -124,13 +51,12 @@ export default function ContributionHeatmap({
} else {
localStorage.setItem("devtrack:heatmap-range", String(days));
}
- } catch (e) {
+ } catch {
setSelectedDays(days);
}
}
}, [days]);
- // Handle popover dismiss
useEffect(() => {
if (!showPopover) return;
const handleKey = (e: KeyboardEvent) => {
@@ -158,7 +84,9 @@ export default function ContributionHeatmap({
if (typeof window !== "undefined") {
try {
localStorage.setItem("devtrack:heatmap-range", String(newDays));
- } catch (e) {}
+ } catch {
+ /* ignore */
+ }
}
};
@@ -197,12 +125,14 @@ export default function ContributionHeatmap({
setShowPopover(false);
};
- const currentFrom = customLabel ? customFrom : (() => {
- const endDate = new Date();
- const startDate = new Date(endDate);
- startDate.setDate(endDate.getDate() - (selectedDays - 1));
- return formatDateKey(startDate);
- })();
+ const currentFrom = customLabel
+ ? customFrom
+ : (() => {
+ const endDate = new Date();
+ const startDate = new Date(endDate);
+ startDate.setDate(endDate.getDate() - (selectedDays - 1));
+ return formatDateKey(startDate);
+ })();
const currentTo = customLabel ? customTo : formatDateKey(new Date());
@@ -214,7 +144,7 @@ export default function ContributionHeatmap({
const params = new URLSearchParams();
params.set("from", currentFrom);
params.set("to", currentTo);
-
+
fetch(`/api/metrics/contributions?${params.toString()}`)
.then((response) => {
if (!response.ok) throw new Error("API error");
@@ -249,121 +179,42 @@ export default function ContributionHeatmap({
}, [lastUpdated]);
const { themeConfig, theme, setTheme } = useHeatmapTheme();
-
+
const displayDays = useMemo(() => {
if (customLabel && customFrom && customTo) {
const msPerDay = 1000 * 60 * 60 * 24;
- return Math.ceil(
- (new Date(customTo).getTime() - new Date(customFrom).getTime()) / msPerDay
- ) + 1;
+ return (
+ Math.ceil(
+ (new Date(customTo).getTime() - new Date(customFrom).getTime()) / msPerDay
+ ) + 1
+ );
}
return selectedDays;
}, [customLabel, customFrom, customTo, selectedDays]);
-
- const cells = useMemo(
- () => buildHeatmap(
- displayDays,
+
+ const totalCommits = useMemo(() => {
+ const cells = buildHeatmap(
+ displayDays,
data,
customLabel ? customFrom : undefined,
customLabel ? customTo : undefined
- ),
- [displayDays, data, customLabel, customFrom, customTo]
- );
- const weekCount = Math.ceil(cells.length / 7);
- const maxCommits = Math.max(
- ...cells.map((cell) => cell.count),
- 1
- );
- // 100% MATHEMATICALLY PRECISE MONTH TRACKING SYSTEM
- const monthMarkers = useMemo(() => {
- const markers: Array<{ label: string; weekIndex: number }> = [];
- const seenMonths = new Set();
-
- for (let w = 0; w < weekCount; w++) {
- const weekCells = cells.slice(w * 7, (w + 1) * 7);
-
- for (const cell of weekCells) {
- if (!cell.inRange) continue;
-
- const currentMonth = cell.date.getMonth();
- const currentYear = cell.date.getFullYear();
- const monthKey = `${currentYear}-${currentMonth}`;
-
- if (!seenMonths.has(monthKey)) {
- seenMonths.add(monthKey);
-
- markers.push({
- label: monthFormatter.format(cell.date),
- weekIndex: w,
- });
- break; // Move immediately to scanning the next column track block
- }
- }
- }
- return markers;
- }, [cells, weekCount]);
-
- // Shared matrix geometries matching baseline canvas dimensions
- const totalGridWidth = LABEL_WIDTH + (weekCount * CELL_SIZE) + ((weekCount - 1) * CELL_GAP);
-
- const gridStyle = {
- gridTemplateColumns: `${LABEL_WIDTH}px repeat(${weekCount}, ${CELL_SIZE}px)`,
- gridTemplateRows: `repeat(7, ${CELL_SIZE}px)`,
- columnGap: `${CELL_GAP}px`,
- rowGap: `${CELL_GAP}px`,
- } as const;
-
- const today = new Date();
- const getHeatmapColor = (count: number) => {
- if (count === 0) return themeConfig.missed;
-
- const normalized = count / maxCommits;
-
- if (normalized <= 0.25) {
- return themeConfig.levelOne;
- }
-
- if (normalized <= 0.5) {
- return themeConfig.levelTwo;
- }
-
- if (normalized <= 0.75) {
- return themeConfig.levelThree;
- }
-
- return themeConfig.levelFour;
- };
- const totalCommits = cells
- .filter((cell) => cell.inRange)
- .reduce((total, cell) => total + cell.count, 0);
- const { inRangeDays, insights } = useMemo(() => {
- const days = cells
- .filter((cell) => cell.inRange)
- .map((cell) => ({
- date: cell.date,
- dateKey: cell.dateKey,
- count: cell.count,
- }));
- return {
- inRangeDays: days,
- insights: getContributionInsights(days),
- };
- }, [cells]);
-
- const heatmapSummary = `Contribution heatmap showing ${formatCommitCount(totalCommits)} across ${displayDays} days.`;
+ );
+ return countInRangeCommits(cells);
+ }, [displayDays, data, customLabel, customFrom, customTo]);
return (
-
-
-
Contribution Heatmap
-
+
+
+
+ Contribution Heatmap
+
+
{customLabel ? `${customLabel}` : `Last ${selectedDays} days of commit activity.`}
-
- {/* Range buttons */}
+
{PRESET_RANGES.map((r) => (