Enhance ContributionCalendar component and update development.astro for better contribution visualization

- Modify getCalendarDays function to start the week on Monday and improve date calculations.
- Add getMonthLabels function to generate month labels for the contribution calendar.
- Update styling and structure of the ContributionCalendar for improved layout and responsiveness.
- Integrate ContributionCalendar into development.astro to visually represent code contributions over the past year, enhancing project transparency.
This commit is contained in:
2025-06-06 22:14:49 +02:00
parent f01ac6f675
commit 673d1b7c29
2 changed files with 143 additions and 32 deletions

View File

@@ -1,13 +1,17 @@
import React from 'react'; import React from 'react';
// Helper to get all days in the last 52 weeks // Helper to get all days in the last 52 weeks, starting on Monday
function getCalendarDays() { function getCalendarDays() {
const days = []; const days = [];
const today = new Date(); const today = new Date();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
// Go back 51 weeks (to get 52 weeks total) // Find the most recent Monday
const start = new Date(today); const start = new Date(today);
start.setDate(start.getDate() - (7 * 51 + today.getDay())); const dayOfWeek = start.getDay();
// getDay(): 0=Sunday, 1=Monday, ..., 6=Saturday
// If today is not Monday, go back to the previous Monday
const offset = (dayOfWeek === 0 ? -6 : 1 - dayOfWeek); // If Sunday, go back 6 days; else, go back to Monday
start.setDate(start.getDate() - (7 * 51) + offset);
for (let i = 0; i < 7 * 52; i++) { for (let i = 0; i < 7 * 52; i++) {
const d = new Date(start); const d = new Date(start);
d.setDate(start.getDate() + i); d.setDate(start.getDate() + i);
@@ -16,6 +20,24 @@ function getCalendarDays() {
return days; return days;
} }
// Get month labels for the top
function getMonthLabels(days) {
const labels = [];
let lastMonth = null;
for (let week = 0; week < 52; week++) {
const day = days[week * 7];
const month = day.toLocaleString('default', { month: 'short' });
if (month !== lastMonth) {
labels.push({ week, month });
lastMonth = month;
}
}
return labels;
}
// Day labels for rows (Monday to Sunday)
const DAY_LABELS = ['Mon', '', 'Wed', '', 'Fri', '', 'Sun'];
// Color scale (GitHub-like) // Color scale (GitHub-like)
const COLORS = [ const COLORS = [
'#ebedf0', // 0 '#ebedf0', // 0
@@ -32,18 +54,83 @@ function getColor(count) {
export default function ContributionCalendar({ data }) { export default function ContributionCalendar({ data }) {
const days = getCalendarDays(); const days = getCalendarDays();
const monthLabels = getMonthLabels(days);
// Get max count for scaling (optional, for more dynamic color) // Get max count for scaling (optional, for more dynamic color)
// const max = Math.max(...Object.values(data)); // const max = Math.max(...Object.values(data));
return ( return (
<div style={{ overflowX: 'auto' }}> <div
<div style={{ display: 'flex' }}> style={{
width: '100%',
maxWidth: '100%',
margin: '0 auto',
padding: 0,
borderRadius: '1rem',
background: 'rgba(255,255,255,0.7)',
boxShadow: '0 4px 24px 0 rgba(0,0,0,0.08)',
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
border: '1px solid rgba(255,255,255,0.3)',
overflowX: 'auto',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: 160,
}}
className="contribution-calendar"
>
{/* Month labels */}
<div style={{ display: 'flex', marginBottom: 4, justifyContent: 'center' }}>
<div style={{ width: 44 }} />
{Array.from({ length: 52 }).map((_, weekIdx) => {
const label = monthLabels.find((l) => l.week === weekIdx);
return (
<div
key={weekIdx}
style={{
flex: '0 0 14px',
textAlign: 'center',
fontSize: 12,
color: '#888',
fontWeight: 500,
minWidth: 14,
}}
>
{label ? label.month : ''}
</div>
);
})}
</div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{/* Day labels */}
<div style={{ display: 'flex', flexDirection: 'column', marginRight: 4, justifyContent: 'center' }}>
{DAY_LABELS.map((label, i) => (
<div
key={i}
style={{
height: 14,
fontSize: 12,
color: '#888',
textAlign: 'right',
lineHeight: '14px',
marginBottom: 1,
minWidth: 32,
}}
>
{label}
</div>
))}
</div>
{/* Weeks (columns) */} {/* Weeks (columns) */}
<div style={{ display: 'flex' }}>
{Array.from({ length: 52 }).map((_, weekIdx) => ( {Array.from({ length: 52 }).map((_, weekIdx) => (
<div key={weekIdx} style={{ display: 'flex', flexDirection: 'column' }}> <div key={weekIdx} style={{ display: 'flex', flexDirection: 'column', flex: '0 0 14px' }}>
{/* Days (rows) */} {/* Days (rows) */}
{Array.from({ length: 7 }).map((_, dayIdx) => { {Array.from({ length: 7 }).map((_, dayIdx) => {
const day = days[weekIdx * 7 + dayIdx]; // Shift the dayIdx so that Monday is the first row and Sunday is the last
const shiftedDayIdx = (dayIdx + 1) % 7;
const day = days[weekIdx * 7 + shiftedDayIdx];
const dateStr = day.toISOString().slice(0, 10); const dateStr = day.toISOString().slice(0, 10);
const count = data[dateStr] || 0; const count = data[dateStr] || 0;
return ( return (
@@ -63,7 +150,8 @@ export default function ContributionCalendar({ data }) {
</div> </div>
))} ))}
</div> </div>
<div style={{ fontSize: 12, color: '#888', marginTop: 4 }}> </div>
<div style={{ fontSize: 12, color: '#888', marginTop: 4, textAlign: 'center' }}>
<span>Less</span> <span>Less</span>
{COLORS.map((color, i) => ( {COLORS.map((color, i) => (
<span key={i} style={{ display: 'inline-block', width: 12, height: 12, background: color, margin: '0 2px', borderRadius: 2 }} /> <span key={i} style={{ display: 'inline-block', width: 12, height: 12, background: color, margin: '0 2px', borderRadius: 2 }} />

View File

@@ -2,6 +2,9 @@
import Layout from '../layouts/Layout.astro'; import Layout from '../layouts/Layout.astro';
import { getPermalink } from '../utils/permalinks'; import { getPermalink } from '../utils/permalinks';
import { getTranslation } from '../i18n/translations'; import { getTranslation } from '../i18n/translations';
import React, { useMemo } from 'react';
import dynamic from 'astro/dynamic';
import ContributionCalendar from '../components/ContributionCalendar.jsx';
const lang = Astro.params.lang || 'en'; const lang = Astro.params.lang || 'en';
const t = getTranslation(lang); const t = getTranslation(lang);
@@ -40,12 +43,32 @@ const formatDate = (dateString: string) => {
day: 'numeric', day: 'numeric',
}); });
}; };
// Helper: Group commits by date (YYYY-MM-DD)
function getContributionData(commits) {
const contributions = {};
for (const commit of commits) {
const date = commit.commit?.author?.date?.slice(0, 10);
if (date) {
contributions[date] = (contributions[date] || 0) + 1;
}
}
return contributions;
}
const contributionData = getContributionData(commits);
--- ---
<Layout> <Layout>
<div class="max-w-4xl mx-auto px-4 py-8"> <div class="max-w-4xl mx-auto px-4 py-8">
<h1 class="text-4xl font-bold mb-8">{t.development.title || 'Development Progress'}</h1> <h1 class="text-4xl font-bold mb-4">{t.development.title || 'Development Progress'}</h1>
<p class="mb-8 text-lg text-gray-700 dark:text-gray-300">
This page provides a transparent overview of the ongoing development activity for the <strong>365DevNet</strong> project. The contribution calendar below visually represents code contributions (commits) made over the past year, with darker squares indicating more active days. Below the calendar, you'll find a list of the most recent commits, including details about each change. This helps users, contributors, and stakeholders track project progress and stay up to date with the latest updates.
</p>
{/* Contribution Calendar */}
<div class="mb-8">
<ContributionCalendar data={contributionData} client:only="react" />
</div>
<div class="space-y-8"> <div class="space-y-8">
<section> <section>
<h2 class="text-2xl font-semibold mb-4">{t.development.latestCommits || 'Latest Commits'}</h2> <h2 class="text-2xl font-semibold mb-4">{t.development.latestCommits || 'Latest Commits'}</h2>