2024-11-19 16:02:11 -06:00
|
|
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
|
|
import { useFetchGithubCommits } from '@/hooks/githubQueries/useFetchGithubCommits';
|
|
|
|
import { Tooltip } from 'primereact/tooltip';
|
2024-11-19 19:16:47 -06:00
|
|
|
import { formatDateTime } from "@/utils/time";
|
2024-11-19 16:02:11 -06:00
|
|
|
|
2024-12-09 20:40:22 -06:00
|
|
|
const CombinedContributionChart = ({ session }) => {
|
2024-11-19 16:02:11 -06:00
|
|
|
const [contributionData, setContributionData] = useState({});
|
2024-11-19 19:16:47 -06:00
|
|
|
const [totalContributions, setTotalContributions] = useState(0);
|
|
|
|
|
|
|
|
const prepareProgressData = useCallback(() => {
|
|
|
|
if (!session?.user?.userCourses) return {};
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
const activityData = {};
|
|
|
|
const allActivities = []; // Array to store all activities for logging
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
// Process course activities
|
|
|
|
session.user.userCourses.forEach(courseProgress => {
|
|
|
|
if (courseProgress.started) {
|
|
|
|
const startDate = new Date(courseProgress.startedAt);
|
2024-12-06 13:04:21 -06:00
|
|
|
const date = new Date(startDate.getTime() - startDate.getTimezoneOffset() * 60000)
|
|
|
|
.toLocaleDateString('en-CA');
|
2024-11-19 19:16:47 -06:00
|
|
|
activityData[date] = (activityData[date] || 0) + 1;
|
|
|
|
allActivities.push({
|
|
|
|
type: 'course_started',
|
|
|
|
name: courseProgress.course?.name,
|
|
|
|
date: date
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (courseProgress.completed) {
|
|
|
|
const completeDate = new Date(courseProgress.completedAt);
|
2024-12-06 13:04:21 -06:00
|
|
|
const date = new Date(completeDate.getTime() - completeDate.getTimezoneOffset() * 60000)
|
|
|
|
.toLocaleDateString('en-CA');
|
2024-11-19 19:16:47 -06:00
|
|
|
activityData[date] = (activityData[date] || 0) + 1;
|
|
|
|
allActivities.push({
|
|
|
|
type: 'course_completed',
|
|
|
|
name: courseProgress.course?.name,
|
|
|
|
date: date
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Process lesson activities
|
|
|
|
session.user.userLessons?.forEach(lessonProgress => {
|
|
|
|
if (lessonProgress.opened) {
|
|
|
|
const openDate = new Date(lessonProgress.openedAt);
|
2024-12-06 13:04:21 -06:00
|
|
|
const date = new Date(openDate.getTime() - openDate.getTimezoneOffset() * 60000)
|
|
|
|
.toLocaleDateString('en-CA');
|
2024-11-19 19:16:47 -06:00
|
|
|
activityData[date] = (activityData[date] || 0) + 1;
|
|
|
|
allActivities.push({
|
|
|
|
type: 'lesson_started',
|
|
|
|
name: lessonProgress.lesson?.name,
|
|
|
|
date: date
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (lessonProgress.completed) {
|
|
|
|
const completeDate = new Date(lessonProgress.completedAt);
|
2024-12-06 13:04:21 -06:00
|
|
|
const date = new Date(completeDate.getTime() - completeDate.getTimezoneOffset() * 60000)
|
|
|
|
.toLocaleDateString('en-CA');
|
2024-11-19 19:16:47 -06:00
|
|
|
activityData[date] = (activityData[date] || 0) + 1;
|
|
|
|
allActivities.push({
|
|
|
|
type: 'lesson_completed',
|
|
|
|
name: lessonProgress.lesson?.name,
|
|
|
|
date: date
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log('All Learning Activities:', allActivities);
|
|
|
|
console.log('Activities by Date:', activityData);
|
|
|
|
|
|
|
|
return activityData;
|
|
|
|
}, [session]);
|
2024-11-19 16:02:11 -06:00
|
|
|
|
|
|
|
const handleNewCommit = useCallback(({ contributionData, totalCommits }) => {
|
2024-11-19 19:16:47 -06:00
|
|
|
const activityData = prepareProgressData();
|
|
|
|
console.log("GitHub Contribution Data:", contributionData);
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
// Create a new object with GitHub commits
|
|
|
|
const combinedData = { ...contributionData };
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
// Add activities to the combined data
|
|
|
|
Object.entries(activityData).forEach(([date, count]) => {
|
|
|
|
combinedData[date] = (combinedData[date] || 0) + count;
|
|
|
|
});
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
console.log("Combined Data:", combinedData);
|
|
|
|
setContributionData(combinedData);
|
|
|
|
setTotalContributions(totalCommits + Object.values(activityData).reduce((a, b) => a + b, 0));
|
|
|
|
}, [prepareProgressData]);
|
2024-11-19 16:02:11 -06:00
|
|
|
|
2024-12-09 20:40:22 -06:00
|
|
|
const { data, isLoading, isFetching } = useFetchGithubCommits(session, handleNewCommit);
|
2024-11-19 16:02:11 -06:00
|
|
|
|
|
|
|
// Initialize from cached data if available
|
|
|
|
useEffect(() => {
|
|
|
|
if (data && !isLoading) {
|
2024-11-19 19:16:47 -06:00
|
|
|
const activityData = prepareProgressData();
|
|
|
|
const combinedData = { ...data.contributionData };
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
// Add activities to the combined data
|
|
|
|
Object.entries(activityData).forEach(([date, count]) => {
|
|
|
|
combinedData[date] = (combinedData[date] || 0) + count;
|
|
|
|
});
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
setContributionData(combinedData);
|
|
|
|
setTotalContributions(data.totalCommits + Object.values(activityData).reduce((a, b) => a + b, 0));
|
2024-11-19 16:02:11 -06:00
|
|
|
}
|
2024-11-19 19:16:47 -06:00
|
|
|
}, [data, isLoading, prepareProgressData]);
|
2024-11-19 16:02:11 -06:00
|
|
|
|
|
|
|
const getColor = useCallback((count) => {
|
|
|
|
if (count === 0) return 'bg-gray-100';
|
2024-11-19 19:16:47 -06:00
|
|
|
if (count < 3) return 'bg-green-300';
|
|
|
|
if (count < 6) return 'bg-green-400';
|
|
|
|
if (count < 12) return 'bg-green-600';
|
2024-11-19 16:02:11 -06:00
|
|
|
return 'bg-green-700';
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const generateCalendar = useCallback(() => {
|
|
|
|
const today = new Date();
|
2024-12-06 13:04:21 -06:00
|
|
|
today.setHours(23, 59, 59, 999);
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-12-06 13:04:21 -06:00
|
|
|
// Calculate the start date (52 weeks + remaining days to today)
|
|
|
|
const oneYearAgo = new Date(today);
|
|
|
|
oneYearAgo.setDate(today.getDate() - 364); // 52 weeks * 7 days = 364 days
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-12-06 13:04:21 -06:00
|
|
|
// Start from the first Sunday before or on oneYearAgo
|
|
|
|
const startDate = new Date(oneYearAgo);
|
|
|
|
startDate.setDate(startDate.getDate() - startDate.getDay());
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-12-06 13:04:21 -06:00
|
|
|
const calendar = [];
|
|
|
|
// Create 7 rows for days of the week (Sunday to Saturday)
|
2024-11-19 16:02:11 -06:00
|
|
|
for (let i = 0; i < 7; i++) {
|
|
|
|
calendar[i] = [];
|
|
|
|
}
|
|
|
|
|
2024-12-06 13:04:21 -06:00
|
|
|
// Fill in the dates by week columns
|
|
|
|
let currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= today) {
|
|
|
|
const weekDay = currentDate.getDay();
|
|
|
|
const dateString = currentDate.toLocaleDateString('en-CA');
|
2024-11-19 19:16:47 -06:00
|
|
|
const githubCount = data?.contributionData[dateString] || 0;
|
|
|
|
const activityCount = (contributionData[dateString] || 0) - (data?.contributionData[dateString] || 0);
|
|
|
|
const totalCount = githubCount + activityCount;
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-12-06 13:04:21 -06:00
|
|
|
calendar[weekDay].push({
|
|
|
|
date: new Date(currentDate),
|
2024-11-19 19:16:47 -06:00
|
|
|
count: totalCount,
|
|
|
|
githubCount,
|
|
|
|
activityCount
|
|
|
|
});
|
2024-12-11 18:04:22 -06:00
|
|
|
|
2024-12-06 13:04:21 -06:00
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
2024-11-19 16:02:11 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return calendar;
|
2024-11-19 19:16:47 -06:00
|
|
|
}, [contributionData, data?.contributionData]);
|
2024-11-19 16:02:11 -06:00
|
|
|
|
|
|
|
const calendar = generateCalendar();
|
|
|
|
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
|
|
|
|
|
|
const getMonthLabels = useCallback(() => {
|
|
|
|
const today = new Date();
|
2024-12-06 13:04:21 -06:00
|
|
|
const oneYearAgo = new Date(today);
|
|
|
|
oneYearAgo.setFullYear(today.getFullYear() - 1);
|
|
|
|
oneYearAgo.setDate(today.getDate() + 1);
|
2024-11-19 16:02:11 -06:00
|
|
|
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 (
|
2024-12-11 18:04:22 -06:00
|
|
|
<div className="w-full mx-2 bg-gray-800 rounded-lg border border-gray-700 shadow-md">
|
|
|
|
<div className="flex flex-row justify-between items-center p-4">
|
|
|
|
<h1 className="text-2xl font-bold text-gray-200">Activity</h1>
|
|
|
|
<i className="pi pi-question-circle text-2xl cursor-pointer text-gray-200"
|
|
|
|
data-pr-tooltip="Combined total of GitHub commits and learning activities (starting/completing courses and lessons)" />
|
|
|
|
<Tooltip target=".pi-question-circle" position="left" />
|
2024-11-19 16:02:11 -06:00
|
|
|
</div>
|
2024-12-11 18:04:22 -06:00
|
|
|
<div className="max-w-[910px] p-4">
|
|
|
|
{(isLoading || isFetching) && <h4 className="text-base font-semibold text-gray-200 mb-3">Loading contribution data... ({totalContributions} total contributions / activities fetched)</h4>}
|
|
|
|
{!isLoading && !isFetching &&
|
|
|
|
<div className="flex justify-between items-center mb-3">
|
|
|
|
<h4 className="text-base font-semibold text-gray-200">
|
|
|
|
{totalContributions} total contributions / activities in the last year
|
|
|
|
</h4>
|
2024-11-19 16:02:11 -06:00
|
|
|
</div>
|
2024-12-11 18:04:22 -06:00
|
|
|
}
|
|
|
|
<div className="flex">
|
|
|
|
{/* Days of week labels */}
|
|
|
|
<div className="flex flex-col gap-[3px] text-[11px] text-gray-400 pr-3">
|
|
|
|
{weekDays.map((day, index) => (
|
|
|
|
<div key={day} className="h-[13px] leading-[13px]">
|
|
|
|
{index % 2 === 0 && day}
|
2024-11-19 16:02:11 -06:00
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
2024-12-11 18:04:22 -06:00
|
|
|
<div className="flex flex-col">
|
|
|
|
{/* Calendar grid */}
|
|
|
|
<div className="flex gap-[3px]">
|
|
|
|
{calendar[0].map((_, weekIndex) => (
|
|
|
|
<div key={weekIndex} className="flex flex-col gap-[3px]">
|
|
|
|
{calendar.map((row, dayIndex) => (
|
|
|
|
row[weekIndex] && (
|
|
|
|
<div
|
|
|
|
key={`${weekIndex}-${dayIndex}`}
|
|
|
|
className={`w-[14px] h-[14px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`}
|
|
|
|
title={`${row[weekIndex].date.toDateString()}: ${[
|
|
|
|
row[weekIndex].githubCount > 0 ? `${row[weekIndex].githubCount} contribution${row[weekIndex].githubCount !== 1 ? 's' : ''}` : '',
|
|
|
|
row[weekIndex].activityCount > 0 ? `${row[weekIndex].activityCount} activit${row[weekIndex].activityCount !== 1 ? 'ies' : 'y'}` : ''
|
|
|
|
].filter(Boolean).join(' & ') || 'No contributions or activities'
|
|
|
|
}`}
|
|
|
|
></div>
|
|
|
|
)
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
{/* Month labels moved to bottom */}
|
|
|
|
<div className="flex text-[11px] text-gray-400 h-[20px] mt-1">
|
|
|
|
{getMonthLabels().map((month, index) => (
|
|
|
|
<div
|
|
|
|
key={index}
|
|
|
|
className="absolute"
|
|
|
|
style={{ marginLeft: `${month.index * 15}px` }}
|
|
|
|
>
|
|
|
|
{month.name}
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-11-19 16:02:11 -06:00
|
|
|
</div>
|
2024-12-11 18:04:22 -06:00
|
|
|
<div className="text-[11px] text-gray-400 flex items-center justify-end">
|
|
|
|
<span className="mr-2">Less</span>
|
|
|
|
<div className="flex gap-[3px]">
|
|
|
|
<div className="w-[14px] h-[14px] bg-gray-100 rounded-[2px]"></div>
|
|
|
|
<div className="w-[14px] h-[14px] bg-green-300 rounded-[2px]"></div>
|
|
|
|
<div className="w-[14px] h-[14px] bg-green-400 rounded-[2px]"></div>
|
|
|
|
<div className="w-[14px] h-[14px] bg-green-600 rounded-[2px]"></div>
|
|
|
|
<div className="w-[14px] h-[14px] bg-green-700 rounded-[2px]"></div>
|
|
|
|
</div>
|
|
|
|
<span className="ml-2">More</span>
|
2024-11-19 16:02:11 -06:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-11-19 19:16:47 -06:00
|
|
|
export default CombinedContributionChart;
|