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 -
-
-
-
-
-
+
+
+
+
+
+
More
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; } }