diff --git a/src/components/charts/GithubContributionChart.js b/src/components/charts/GithubContributionChart.js
index d6ebf70..6a8bc35 100644
--- a/src/components/charts/GithubContributionChart.js
+++ b/src/components/charts/GithubContributionChart.js
@@ -1,11 +1,25 @@
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useCallback, useEffect } from 'react';
import { useFetchGithubCommits } from '@/hooks/githubQueries/useFetchGithubCommits';
import { Tooltip } from 'primereact/tooltip';
+
const GithubContributionChart = ({ username }) => {
const [contributionData, setContributionData] = useState({});
const [totalCommits, setTotalCommits] = useState(0);
- const { data: commits, isLoading, isFetching } = useFetchGithubCommits(username);
+ const handleNewCommit = useCallback(({ contributionData, totalCommits }) => {
+ setContributionData(contributionData);
+ setTotalCommits(totalCommits);
+ }, []);
+
+ const { data, isLoading, isFetching } = useFetchGithubCommits(username, handleNewCommit);
+
+ // Initialize from cached data if available
+ useEffect(() => {
+ if (data && !isLoading) {
+ setContributionData(data.contributionData);
+ setTotalCommits(data.totalCommits);
+ }
+ }, [data, isLoading]);
const getColor = useCallback((count) => {
if (count === 0) return 'bg-gray-100';
@@ -17,65 +31,110 @@ const GithubContributionChart = ({ username }) => {
const generateCalendar = useCallback(() => {
const today = new Date();
- const sixMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 5, 1);
+ const oneYearAgo = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate());
const calendar = [];
+
+ // Create 7 rows for days of the week
+ for (let i = 0; i < 7; i++) {
+ calendar[i] = [];
+ }
- for (let d = new Date(sixMonthsAgo); d <= today; d.setDate(d.getDate() + 1)) {
+ // Fill in the dates
+ for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) {
const dateString = d.toISOString().split('T')[0];
const count = contributionData[dateString] || 0;
- calendar.push({ date: new Date(d), count });
+ const dayOfWeek = d.getDay();
+ calendar[dayOfWeek].push({ date: new Date(d), count });
}
return calendar;
}, [contributionData]);
- useEffect(() => {
- if (commits) {
- let commitCount = 0;
-
- const newContributionData = {};
- commits.forEach(commit => {
- const date = commit.commit.author.date.split('T')[0];
- newContributionData[date] = (newContributionData[date] || 0) + 1;
- commitCount++;
- });
-
- setContributionData(newContributionData);
- setTotalCommits(commitCount);
-
- console.log(`Total commits fetched: ${commitCount}`);
- }
- }, [commits]);
-
const calendar = generateCalendar();
+ const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+
+ const getMonthLabels = useCallback(() => {
+ const today = new Date();
+ const oneYearAgo = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate());
+ const months = [];
+ let currentMonth = -1;
+
+ for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) {
+ const month = d.getMonth();
+ if (month !== currentMonth) {
+ months.push({
+ name: d.toLocaleString('default', { month: 'short' }),
+ index: calendar[0].findIndex(
+ (_, weekIndex) => calendar[0][weekIndex]?.date.getMonth() === month
+ )
+ });
+ currentMonth = month;
+ }
+ }
+ return months;
+ }, [calendar]);
return (
-
+
{(isLoading || isFetching) &&
Loading contribution data... ({totalCommits} commits fetched)
}
{!isLoading && !isFetching &&
-
-
Total commits: {totalCommits}
-
+
+
+ {totalCommits} contributions in the last year
+
+
}
-
- {calendar.map((day, index) => (
-
- ))}
+
+ {/* Days of week labels */}
+
+ {weekDays.map((day, index) => (
+
+ {index % 2 === 0 && day}
+
+ ))}
+
+
+ {/* Calendar grid */}
+
+ {calendar[0].map((_, weekIndex) => (
+
+ {calendar.map((row, dayIndex) => (
+ row[weekIndex] && (
+
+ )
+ ))}
+
+ ))}
+
+ {/* Month labels moved to bottom */}
+
+ {getMonthLabels().map((month, index) => (
+
+ {month.name}
+
+ ))}
+
+
-
+
Less
-
diff --git a/src/hooks/githubQueries/useFetchGithubCommits.js b/src/hooks/githubQueries/useFetchGithubCommits.js
index ba22181..f4ca2e3 100644
--- a/src/hooks/githubQueries/useFetchGithubCommits.js
+++ b/src/hooks/githubQueries/useFetchGithubCommits.js
@@ -1,22 +1,38 @@
import { useQuery } from '@tanstack/react-query';
import { getAllCommits } from '@/lib/github';
-export function useFetchGithubCommits(username) {
- const fetchCommits = async () => {
- const sixMonthsAgo = new Date();
- sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
-
- const commits = [];
- for await (const commit of getAllCommits(username, sixMonthsAgo)) {
- commits.push(commit);
- }
- return commits;
- };
-
+export function useFetchGithubCommits(username, onCommitReceived) {
return useQuery({
queryKey: ['githubCommits', username],
- queryFn: fetchCommits,
+ queryFn: async () => {
+ const oneYearAgo = new Date();
+ oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
+
+ const commits = [];
+ const contributionData = {};
+ let totalCommits = 0;
+
+ for await (const commit of getAllCommits(username, oneYearAgo)) {
+ commits.push(commit);
+ const date = commit.commit.author.date.split('T')[0];
+ contributionData[date] = (contributionData[date] || 0) + 1;
+ totalCommits++;
+
+ // Call the callback with the running totals
+ onCommitReceived?.({
+ contributionData,
+ totalCommits
+ });
+ }
+
+ return {
+ commits,
+ contributionData,
+ totalCommits
+ };
+ },
staleTime: 1000 * 60 * 30, // 30 minutes
refetchInterval: 1000 * 60 * 30, // 30 minutes
+ cacheTime: 1000 * 60 * 60, // 1 hour
});
}
\ No newline at end of file
diff --git a/src/lib/github.js b/src/lib/github.js
index a1bb59c..779260c 100644
--- a/src/lib/github.js
+++ b/src/lib/github.js
@@ -21,44 +21,45 @@ const octokit = new ThrottledOctokit({
});
export async function* getAllCommits(username, since) {
- let page = 1;
-
- while (true) {
- try {
- const { data: repos } = await octokit.repos.listForUser({
- username,
- per_page: 100,
- page,
- });
-
- if (repos.length === 0) break;
-
- const repoPromises = repos.map(repo =>
- octokit.repos.listCommits({
- owner: username,
- repo: repo.name,
- since: since.toISOString(),
- per_page: 100,
- })
- );
-
- const repoResults = await Promise.allSettled(repoPromises);
-
- for (const result of repoResults) {
- if (result.status === 'fulfilled') {
- for (const commit of result.value.data) {
- yield commit;
- }
- } else {
- console.warn(`Error fetching commits: ${result.reason}`);
- }
- }
-
- page++;
- } catch (error) {
- console.error("Error fetching repositories:", error.message);
- break;
+ // Create time windows of 1 month each
+ const endDate = new Date();
+ let currentDate = new Date(since);
+
+ while (currentDate < endDate) {
+ let nextDate = new Date(currentDate);
+ nextDate.setMonth(nextDate.getMonth() + 1);
+
+ // If next date would be in the future, use current date instead
+ if (nextDate > endDate) {
+ nextDate = endDate;
}
+
+ let page = 1;
+
+ while (true) {
+ try {
+ const { data } = await octokit.search.commits({
+ q: `author:${username} committer-date:${currentDate.toISOString().split('T')[0]}..${nextDate.toISOString().split('T')[0]}`,
+ per_page: 100,
+ page,
+ });
+
+ if (data.items.length === 0) break;
+
+ for (const commit of data.items) {
+ yield commit;
+ }
+
+ if (data.items.length < 100) break;
+ page++;
+ } catch (error) {
+ console.error("Error fetching commits:", error.message);
+ break;
+ }
+ }
+
+ // Move to next time window
+ currentDate = nextDate;
}
}