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:
@@ -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 }} />
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user