Skip to content

Commit 9cde924

Browse files
committed
Merge commit '7b9a50edd7f48ff1303634f129b1fa28cb59d2d0' into feature/home-page
2 parents cd4c8f7 + 7b9a50e commit 9cde924

10 files changed

Lines changed: 354 additions & 13 deletions

File tree

app/(with-sidebar)/(home)/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const Page = () => {
9999
/>
100100

101101
{/* 2-2. GraphSection */}
102-
<GraphSection></GraphSection>
102+
<GraphSection uid={currentUser.uid}></GraphSection>
103103

104104
{/* 2-3. BottomSection */}
105105
<BottomSection

app/globals.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,33 @@
4242
/* 텍스트(흰색) */
4343
--color-text-invert: #ffffff;
4444
}
45+
/* --- 잔디그래프 --- */
46+
.react-calendar-heatmap {
47+
transform-origin: top left;
48+
}
49+
50+
.react-calendar-heatmap text {
51+
font-size: 8px;
52+
fill: #9ca3af; /* gray-400 */
53+
}
54+
55+
.react-calendar-heatmap rect {
56+
rx: 3px;
57+
ry: 3px;
58+
}
59+
60+
.react-calendar-heatmap .grass-empty {
61+
fill: #f1f5f9;
62+
}
63+
.react-calendar-heatmap .grass-1 {
64+
fill: #a7f3d0;
65+
}
66+
.react-calendar-heatmap .grass-2 {
67+
fill: #34d399;
68+
}
69+
.react-calendar-heatmap .grass-3 {
70+
fill: #059669;
71+
}
72+
.react-calendar-heatmap .grass-4 {
73+
fill: #065f46;
74+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
'use client';
2+
3+
import { useEffect, useMemo, useState } from 'react';
4+
import CalendarHeatmap from 'react-calendar-heatmap';
5+
import 'react-calendar-heatmap/dist/styles.css';
6+
import { Tooltip } from 'react-tooltip';
7+
import 'react-tooltip/dist/react-tooltip.css';
8+
import { fetchDailyStats } from '@/services/heatmap/dailyStat.service';
9+
10+
type HeatmapValue = { date: string; count: number };
11+
12+
function endOfWeek(date = new Date()) {
13+
const d = new Date(date);
14+
d.setDate(d.getDate() + (6 - d.getDay()));
15+
return d;
16+
}
17+
18+
type Props = { uid?: string };
19+
20+
export default function GrassHeatmap({ uid }: Props) {
21+
const endDate = useMemo(() => endOfWeek(new Date()), []);
22+
const startDate = useMemo(() => {
23+
const d = new Date(endDate);
24+
d.setFullYear(d.getFullYear() - 1);
25+
d.setDate(d.getDate() + 1);
26+
return d;
27+
}, [endDate]);
28+
29+
const [values, setValues] = useState<
30+
{ date: string; total: number; tilCount?: number; todoDoneCount?: number }[]
31+
>([]);
32+
33+
useEffect(() => {
34+
if (!uid) return;
35+
(async () => {
36+
const stats = await fetchDailyStats(uid);
37+
setValues(stats);
38+
})();
39+
}, [uid]);
40+
41+
const byDate = useMemo(() => {
42+
const m = new Map<
43+
string,
44+
{ tilCount: number; todoDoneCount: number; total: number }
45+
>();
46+
for (const s of values) {
47+
m.set(s.date, {
48+
tilCount: s.tilCount ?? 0,
49+
todoDoneCount: s.todoDoneCount ?? 0,
50+
total: s.total ?? 0,
51+
});
52+
}
53+
return m;
54+
}, [values]);
55+
56+
const heatmapValues: HeatmapValue[] = useMemo(
57+
() => values.map((s) => ({ date: s.date, count: s.total ?? 0 })),
58+
[values]
59+
);
60+
61+
return (
62+
<>
63+
<div className="overflow-x-auto">
64+
<div className="min-w-max">
65+
<CalendarHeatmap
66+
startDate={startDate}
67+
endDate={endDate}
68+
values={heatmapValues}
69+
gutterSize={2}
70+
showWeekdayLabels
71+
classForValue={(value) => {
72+
if (!value) return 'grass-empty';
73+
if (value.count >= 4) return 'grass-4';
74+
if (value.count === 3) return 'grass-3';
75+
if (value.count === 2) return 'grass-2';
76+
return 'grass-1';
77+
}}
78+
tooltipDataAttrs={(value) => {
79+
if (!value?.date)
80+
return { 'data-tooltip-id': '', 'data-tooltip-html': '' };
81+
const d = byDate.get(value.date);
82+
83+
const til = d?.tilCount ?? 0;
84+
const todo = d?.todoDoneCount ?? 0;
85+
const total = d?.total ?? 0;
86+
87+
return {
88+
'data-tooltip-id': 'grass-tip',
89+
// HTML 툴팁 (작은 박스)
90+
'data-tooltip-html':
91+
total === 0
92+
? `<div style="font-size:12px"><b>${value.date}</b><br/>기록 없음</div>`
93+
: `<div style="font-size:12px">
94+
<b>${value.date}</b><br/>
95+
📘 TIL: ${til}개<br/>
96+
✅ Plan: ${todo}개<br/>
97+
🔥 합계: ${total}
98+
</div>`,
99+
};
100+
}}
101+
/>
102+
</div>
103+
</div>
104+
105+
{/* 툴팁 컴포넌트 */}
106+
<Tooltip
107+
id="grass-tip"
108+
place="top"
109+
className="!rounded-lg !bg-black/80 !px-3 !py-2 !text-xs !text-white"
110+
/>
111+
{/* 범례 */}
112+
<div className="mt-4 flex items-center justify-end gap-2 text-xs text-slate-500">
113+
<span>Less</span>
114+
<span className="h-3 w-3 rounded-sm bg-slate-100" />
115+
<span className="h-3 w-3 rounded-sm bg-emerald-200" />
116+
<span className="h-3 w-3 rounded-sm bg-emerald-400" />
117+
<span className="h-3 w-3 rounded-sm bg-emerald-600" />
118+
<span className="h-3 w-3 rounded-sm bg-emerald-800" />
119+
<span>More</span>
120+
</div>
121+
</>
122+
);
123+
}

components/home/GraphSection.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import Card from './Card';
2+
import GrassHeatmap from '../heatmap/GrassHeatmap';
23

34
interface GraphSectionProps {
45
className?: string;
6+
uid?: string;
57
}
68

7-
const GraphSection = ({ className }: GraphSectionProps) => {
9+
const GraphSection = ({ className, uid }: GraphSectionProps) => {
810
return (
911
<div className={className}>
10-
<Card title="학습 기록">잔디그래프 구현 예정</Card>
12+
<Card title="학습 기록">
13+
<GrassHeatmap uid={uid} />
14+
</Card>
1115
</div>
1216
);
1317
};

package-lock.json

Lines changed: 42 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"lint": "eslint"
1010
},
1111
"dependencies": {
12-
"@radix-ui/react-popover": "^1.1.15",
1312
"@headlessui/react": "^2.2.9",
13+
"@radix-ui/react-popover": "^1.1.15",
1414
"@uiw/react-md-editor": "^4.0.11",
1515
"clsx": "^2.1.1",
1616
"date-fns": "^4.1.0",
@@ -19,9 +19,11 @@
1919
"lucide-react": "^0.562.0",
2020
"next": "16.1.1",
2121
"react": "19.2.3",
22+
"react-calendar-heatmap": "^1.10.0",
2223
"react-day-picker": "^9.13.0",
2324
"react-dom": "19.2.3",
24-
"react-icons": "^5.5.0"
25+
"react-icons": "^5.5.0",
26+
"react-tooltip": "^5.30.0"
2527
},
2628
"devDependencies": {
2729
"@tailwindcss/postcss": "^4",

0 commit comments

Comments
 (0)