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';
// 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() {
const days = [];
const today = new Date();
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);
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++) {
const d = new Date(start);
d.setDate(start.getDate() + i);
@@ -16,6 +20,24 @@ function getCalendarDays() {
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)
const COLORS = [
'#ebedf0', // 0
@@ -32,38 +54,104 @@ function getColor(count) {
export default function ContributionCalendar({ data }) {
const days = getCalendarDays();
const monthLabels = getMonthLabels(days);
// Get max count for scaling (optional, for more dynamic color)
// const max = Math.max(...Object.values(data));
return (
<div style={{ overflowX: 'auto' }}>
<div style={{ display: 'flex' }}>
{/* Weeks (columns) */}
{Array.from({ length: 52 }).map((_, weekIdx) => (
<div key={weekIdx} style={{ display: 'flex', flexDirection: 'column' }}>
{/* Days (rows) */}
{Array.from({ length: 7 }).map((_, dayIdx) => {
const day = days[weekIdx * 7 + dayIdx];
const dateStr = day.toISOString().slice(0, 10);
const count = data[dateStr] || 0;
return (
<div
key={dateStr}
title={`${dateStr}: ${count} contribution${count === 1 ? '' : 's'}`}
style={{
width: 12,
height: 12,
margin: 1,
background: getColor(count),
borderRadius: 2,
}}
/>
);
})}
</div>
))}
<div
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={{ fontSize: 12, color: '#888', marginTop: 4 }}>
<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) */}
<div style={{ display: 'flex' }}>
{Array.from({ length: 52 }).map((_, weekIdx) => (
<div key={weekIdx} style={{ display: 'flex', flexDirection: 'column', flex: '0 0 14px' }}>
{/* Days (rows) */}
{Array.from({ length: 7 }).map((_, 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 count = data[dateStr] || 0;
return (
<div
key={dateStr}
title={`${dateStr}: ${count} contribution${count === 1 ? '' : 's'}`}
style={{
width: 12,
height: 12,
margin: 1,
background: getColor(count),
borderRadius: 2,
}}
/>
);
})}
</div>
))}
</div>
</div>
<div style={{ fontSize: 12, color: '#888', marginTop: 4, textAlign: 'center' }}>
<span>Less</span>
{COLORS.map((color, i) => (
<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 { getPermalink } from '../utils/permalinks';
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 t = getTranslation(lang);
@@ -40,12 +43,32 @@ const formatDate = (dateString: string) => {
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>
<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">
<section>
<h2 class="text-2xl font-semibold mb-4">{t.development.latestCommits || 'Latest Commits'}</h2>