From c506ddea0ab73c4d7db0dfaa2cd2264421cd3c65 Mon Sep 17 00:00:00 2001 From: YamiNo-Okami <23f1002440@ds.study.iitm.ac.in> Date: Wed, 27 May 2026 16:26:27 +0530 Subject: [PATCH 1/2] package-lock.json --- .gitignore | 2 ++ package-lock.json | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3620579f..8d17ca51 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ test-results/ .vercel .env* + +package-lock.json \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c47f690f..4d540824 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4564,7 +4564,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, From 533ad24aade6712690b4e3702e75f14b4d78b8ec Mon Sep 17 00:00:00 2001 From: YamiNo-Okami <23f1002440@ds.study.iitm.ac.in> Date: Wed, 27 May 2026 17:14:18 +0530 Subject: [PATCH 2/2] feat: add dynamic date range controls for contribution heatmap --- src/components/ContributionHeatmap.tsx | 274 ++++++++++++++++++++++--- 1 file changed, 249 insertions(+), 25 deletions(-) diff --git a/src/components/ContributionHeatmap.tsx b/src/components/ContributionHeatmap.tsx index f0b68eed..3a3b5773 100644 --- a/src/components/ContributionHeatmap.tsx +++ b/src/components/ContributionHeatmap.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useHeatmapTheme } from "@/hooks/useHeatmapTheme"; import DailyBreakdownSheet from "@/components/DailyBreakdownSheet"; @@ -20,13 +20,20 @@ interface HeatmapCell { } const DEFAULT_DAYS = 365; -const CELL_SIZE = 12; -const CELL_GAP = 2; -const LABEL_WIDTH = 42; -const HEADER_HEIGHT = 18; +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 }, + { label: "90d", days: 90 }, + { label: "6mo", days: 180 }, + { 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" }); @@ -37,13 +44,24 @@ function formatDateKey(date: Date) { return `${year}-${month}-${day}`; } -function buildHeatmap(days: number, contributions: Record) { - const endDate = new Date(); - endDate.setHours(23, 59, 59, 999); - - const startDate = new Date(endDate); - startDate.setDate(endDate.getDate() - (days - 1)); - startDate.setHours(0, 0, 0, 0); +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()); @@ -81,12 +99,117 @@ 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(""); + const [customTo, setCustomTo] = useState(""); + const [customLabel, setCustomLabel] = useState(null); + const [customError, setCustomError] = useState(null); + const popoverRef = useRef(null); + + // Load persisted range preference + useEffect(() => { + if (typeof window !== "undefined") { + try { + const stored = localStorage.getItem("devtrack:heatmap-range"); + if (stored === "30" || stored === "90" || stored === "180" || stored === "365") { + setSelectedDays(Number(stored)); + } else { + localStorage.setItem("devtrack:heatmap-range", String(days)); + } + } catch { + setSelectedDays(days); + } + } + }, [days]); + + // Handle popover dismiss + useEffect(() => { + if (!showPopover) return; + const handleKey = (e: KeyboardEvent) => { + if (e.key === "Escape") setShowPopover(false); + }; + const handleClick = (e: MouseEvent) => { + if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) { + setShowPopover(false); + } + }; + document.addEventListener("keydown", handleKey); + document.addEventListener("mousedown", handleClick); + return () => { + document.removeEventListener("keydown", handleKey); + document.removeEventListener("mousedown", handleClick); + }; + }, [showPopover]); + + const handleRangeChange = (newDays: number) => { + setSelectedDays(newDays); + setCustomLabel(null); + setCustomFrom(""); + setCustomTo(""); + setCustomError(null); + if (typeof window !== "undefined") { + try { + localStorage.setItem("devtrack:heatmap-range", String(newDays)); + } catch {} + } + }; + + const handleCustomApply = () => { + setCustomError(null); + const today = new Date().toISOString().slice(0, 10); + + if (!customFrom || !customTo) { + setCustomError("Please select both dates."); + return; + } + if (customFrom > customTo) { + setCustomError("Start date must be before end date."); + return; + } + if (customTo > today) { + setCustomError("End date can't be in the future."); + return; + } + const msPerDay = 1000 * 60 * 60 * 24; + const diff = + (new Date(customTo).getTime() - new Date(customFrom).getTime()) / msPerDay; + if (diff > 365 * 2) { + setCustomError("Max range is 2 years."); + return; + } + + const fmt = (d: string) => { + const [year, month, day] = d.split("-").map(Number); + return new Date(year, month - 1, day).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }); + }; + setCustomLabel(`${fmt(customFrom)} – ${fmt(customTo)}`); + 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 currentTo = customLabel ? customTo : formatDateKey(new Date()); + useEffect(() => { let active = true; setLoading(true); setError(null); - fetch(`/api/metrics/contributions?days=${days}`) + 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"); return response.json(); @@ -109,7 +232,7 @@ export default function ContributionHeatmap({ return () => { active = false; }; - }, [days]); + }, [currentFrom, currentTo]); useEffect(() => { if (!lastUpdated) return; @@ -120,7 +243,26 @@ export default function ContributionHeatmap({ }, [lastUpdated]); const { themeConfig, theme, setTheme } = useHeatmapTheme(); - const cells = useMemo(() => buildHeatmap(days, data), [days, data]); + + 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 selectedDays; + }, [customLabel, customFrom, customTo, selectedDays]); + + const cells = useMemo( + () => buildHeatmap( + displayDays, + data, + customLabel ? customFrom : undefined, + customLabel ? customTo : undefined + ), + [displayDays, data, customLabel, customFrom, customTo] + ); const weekCount = Math.ceil(cells.length / 7); // 100% MATHEMATICALLY PRECISE MONTH TRACKING SYSTEM @@ -169,10 +311,89 @@ export default function ContributionHeatmap({

Contribution Heatmap

-

Last {days} days of commit activity.

+

+ {customLabel ? `${customLabel}` : `Last ${selectedDays} days of commit activity.`} +

-
+
+ {/* Range buttons */} +
+ {PRESET_RANGES.map((r) => ( + + ))} +
+ + {/* Custom date range popover */} +
+ + + {showPopover && ( +
+

+ Custom range +

+
+ + + {customError && ( +

{customError}

+ )} + +
+
+ )} +
+