Skip to content

Commit 7b8133c

Browse files
committed
feat: コントリビューション(草)を表示
1 parent 10a97aa commit 7b8133c

File tree

2 files changed

+92
-1
lines changed

2 files changed

+92
-1
lines changed

app/recap/github-grass.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { FC } from "react";
2+
3+
type Props = {
4+
weeklyContributions: { date: string; contributionCount: number }[];
5+
};
6+
7+
// 芝生(草)を描画するためのコンポーネント
8+
export const GitHubGrass: FC<Props> = ({ weeklyContributions }) => {
9+
// 7日ずつまとめて週ごとに分割し、日曜が先頭になるように並べ替え
10+
const weeks = chunkByWeek(weeklyContributions);
11+
12+
return (
13+
<div className="flex gap-1">
14+
{weeks.map((daysInWeek, weekIndex) => (
15+
<div
16+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
17+
key={weekIndex}
18+
className="space-y-1"
19+
>
20+
{daysInWeek.map((day, dayIndex) => {
21+
const color = getContributionColor(day.contributionCount);
22+
return (
23+
<div
24+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
25+
key={dayIndex}
26+
title={`${day.date}: ${day.contributionCount} contributions`}
27+
className="w-3 h-3 rounded-[2px]"
28+
style={{ backgroundColor: color }}
29+
/>
30+
);
31+
})}
32+
</div>
33+
))}
34+
</div>
35+
);
36+
};
37+
38+
const contributionColors = [
39+
{ min: 0, max: 0, color: "#ebedf0" },
40+
{ min: 1, max: 11, color: "#9be9a8" },
41+
{ min: 12, max: 22, color: "#40c463" },
42+
{ min: 23, max: 34, color: "#30a14e" },
43+
{ min: 35, max: Number.POSITIVE_INFINITY, color: "#216e39" },
44+
];
45+
46+
const getContributionColor = (count: number) => {
47+
const color = contributionColors.find(
48+
({ min, max }) => count >= min && count <= max,
49+
);
50+
return color ? color.color : "#ebedf0";
51+
};
52+
53+
/**
54+
* 連続した日付配列を「日曜始まり」に揃えてから、7日(1週間)ずつに区切る関数
55+
* ・最初の日の曜日を取得し、もし日曜でなければダミー要素を先頭に追加
56+
* ・7日ごとにチャンク
57+
*/
58+
const chunkByWeek = (
59+
contributions: { date: string; contributionCount: number }[],
60+
) => {
61+
// 1. 日付昇順にソート(念のため)
62+
const sorted = [...contributions].sort(
63+
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
64+
);
65+
66+
// 2. 先頭日の曜日を取得(0=日, 1=月, ... 6=土)
67+
const firstDay = new Date(sorted[0].date).getDay();
68+
69+
// 3. 先頭日が日曜日でなければ、日曜スタートに合わせるためのダミーを追加
70+
const placeholders = [];
71+
for (let i = 0; i < firstDay; i++) {
72+
placeholders.push({
73+
date: "", // 日付なし
74+
contributionCount: 0,
75+
});
76+
}
77+
78+
// 4. ダミーを先頭に連結
79+
const aligned = [...placeholders, ...sorted];
80+
81+
// 5. 7日ごとに分割
82+
const chunked: { date: string; contributionCount: number }[][] = [];
83+
for (let i = 0; i < aligned.length; i += 7) {
84+
chunked.push(aligned.slice(i, i + 7));
85+
}
86+
87+
return chunked;
88+
};

app/recap/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { auth } from "@/lib/auth";
22
import { fetchGitHubStats } from "./fetchGitHubStats";
3+
import { GitHubGrass } from "./github-grass";
34

45
export default async function Page() {
56
const session = await auth();
@@ -14,7 +15,9 @@ export default async function Page() {
1415
return (
1516
<div className="min-h-dvh py-4">
1617
<h1 className="text-center">Contributions for @{session.user.login}</h1>
17-
<pre>{JSON.stringify(data, null, 2)}</pre>
18+
<div className="flex justify-center py-8">
19+
<GitHubGrass weeklyContributions={data.weeklyContributions} />
20+
</div>
1821
</div>
1922
);
2023
}

0 commit comments

Comments
 (0)