Skip to content

Commit 0a5bf84

Browse files
committed
feat: add heatmap
1 parent b9150da commit 0a5bf84

4 files changed

Lines changed: 206 additions & 132 deletions

File tree

src/App.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
Outlet,
88
useLocation,
99
} from "react-router-dom";
10-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
10+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1111
import SplashPage from "./components/pages/SplashPage";
1212
import ServerConfigurationPage from "./components/pages/ServerConfigurationPage";
1313
import MoviesReviewPage from "./components/pages/MoviesReviewPage";
@@ -25,7 +25,6 @@ import DeviceStatsPage from "./components/pages/DeviceStatsPage";
2525
import ShowOfTheMonthPage from "./components/pages/ShowOfTheMonthPage";
2626
import UnfinishedShowsPage from "./components/pages/UnfinishedShowsPage";
2727
import CriticallyAcclaimedPage from "./components/pages/CriticallyAcclaimedPage";
28-
import PunchCardPage from "./components/pages/PunchCardPage";
2928
import { useEffect } from "react";
3029
import ActivityCalendarPage from "./components/pages/ActivityCalendarPage";
3130

Lines changed: 144 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,222 @@
1-
import { useQuery } from '@tanstack/react-query';
2-
import { getCalendarData, type CalendarData } from '@/lib/playback-reporting-queries';
3-
import { ResponsiveCalendar } from '@nivo/calendar';
4-
import { Card } from '@/components/ui/card';
5-
import { Button, Box, Spinner } from '@radix-ui/themes';
6-
import { useNavigate } from 'react-router-dom';
7-
import { subYears, format } from 'date-fns';
8-
import { Title } from '../ui/styled';
9-
import { motion } from 'framer-motion';
1+
import { useQuery } from "@tanstack/react-query";
2+
import {
3+
getCalendarData,
4+
} from "@/lib/playback-reporting-queries";
5+
import { ResponsiveCalendar } from "@nivo/calendar";
6+
import { Card } from "@/components/ui/card";
7+
import { Button, Box, Spinner } from "@radix-ui/themes";
8+
import { useNavigate } from "react-router-dom";
9+
import { subYears, format } from "date-fns";
10+
import { Title } from "../ui/styled";
11+
import { motion } from "framer-motion";
12+
import { useEffect, useState } from "react";
1013

1114
const NEXT_PAGE = "/";
1215

16+
// You can adjust this breakpoint based on your needs
17+
const MOBILE_BREAKPOINT = "768px";
18+
19+
function useIsMobile() {
20+
const [isMobile, setIsMobile] = useState(false);
21+
22+
useEffect(() => {
23+
// Check if window is available (for SSR compatibility)
24+
if (typeof window === "undefined") return;
25+
26+
const mediaQuery = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT})`);
27+
28+
// Set initial value
29+
setIsMobile(mediaQuery.matches);
30+
31+
// Create event listener function
32+
const handleResize = (event: MediaQueryListEvent) => {
33+
setIsMobile(event.matches);
34+
};
35+
36+
// Add event listener
37+
mediaQuery.addEventListener("change", handleResize);
38+
39+
// Cleanup
40+
return () => mediaQuery.removeEventListener("change", handleResize);
41+
}, []);
42+
43+
return isMobile;
44+
}
45+
1346
export default function ActivityCalendarPage() {
1447
const navigate = useNavigate();
48+
const isMobile = useIsMobile();
1549
const { data, isLoading, error } = useQuery({
16-
queryKey: ['calendar-data'],
17-
queryFn: getCalendarData
50+
queryKey: ["calendar-data"],
51+
queryFn: getCalendarData,
1852
});
1953

20-
const from = format(subYears(new Date(), 1), 'yyyy-MM-dd');
21-
const to = format(new Date(), 'yyyy-MM-dd');
54+
const from = format(subYears(new Date(), 1), "yyyy-MM-dd");
55+
const to = format(new Date(), "yyyy-MM-dd");
2256

2357
if (isLoading) {
2458
return (
25-
<div style={{
26-
display: 'flex',
27-
alignItems: 'center',
28-
justifyContent: 'center',
29-
minHeight: '100vh'
30-
}}>
31-
<Box style={{
32-
backgroundColor: 'var(--gray-8)',
33-
minHeight: '100vh',
34-
minWidth: '100vw',
35-
display: 'flex',
36-
alignItems: 'center',
37-
justifyContent: 'center'
38-
}}>
59+
<div
60+
style={{
61+
display: "flex",
62+
alignItems: "center",
63+
justifyContent: "center",
64+
minHeight: "100vh",
65+
}}
66+
>
67+
<Box
68+
style={{
69+
backgroundColor: "var(--gray-8)",
70+
minHeight: "100vh",
71+
minWidth: "100vw",
72+
display: "flex",
73+
alignItems: "center",
74+
justifyContent: "center",
75+
}}
76+
>
3977
<Spinner size="3" />
4078
</Box>
4179
</div>
4280
);
4381
}
4482

4583
if (error) {
46-
console.error('Error loading calendar data:', error);
84+
console.error("Error loading calendar data:", error);
4785
return <div>Error loading viewing patterns</div>;
4886
}
4987

5088
if (!data?.length) {
5189
return (
52-
<Box
53-
style={{ backgroundColor: "var(--purple-8)" }}
54-
className="min-h-screen"
55-
>
56-
<div className="container mx-auto p-4">
57-
<h1 className="text-2xl font-bold mb-4">No viewing data available</h1>
58-
<Button
59-
size={"4"}
60-
style={{ width: "100%" }}
61-
onClick={() => {
62-
void navigate(NEXT_PAGE);
63-
}}
64-
>
65-
Next
66-
</Button>
67-
</div></Box>
90+
<Box
91+
style={{ backgroundColor: "var(--purple-8)" }}
92+
className="min-h-screen"
93+
>
94+
<div className="container mx-auto p-4">
95+
<h1 className="text-2xl font-bold mb-4">No viewing data available</h1>
96+
<Button
97+
size={"4"}
98+
style={{ width: "100%" }}
99+
onClick={() => {
100+
void navigate(NEXT_PAGE);
101+
}}
102+
>
103+
Next
104+
</Button>
105+
</div>
106+
</Box>
68107
);
69108
}
70109

71110
return (
72-
<Box style={{ backgroundColor: 'var(--purple-12)', minWidth: "100vw", minHeight: "100vh" }} className="min-h-screen p-4">
111+
<Box
112+
style={{
113+
backgroundColor: "var(--purple-12)",
114+
minWidth: "100vw",
115+
minHeight: "100vh",
116+
}}
117+
className="min-h-screen p-4"
118+
>
73119
<div className="container mx-auto">
74-
<Title as={motion.h1} style={{textAlign: "center"}}>Your Viewing Calendar</Title>
120+
<Title as={motion.h1} style={{ textAlign: "center" }}>
121+
Your Calendar Heatmap
122+
</Title>
75123
<Card className="p-4 mb-4">
76-
<div style={{
77-
height: 'calc(min(600px, 80vh))',
78-
minHeight: '400px',
79-
width: '100%'
80-
}}>
124+
<div
125+
style={{
126+
height: isMobile
127+
? "calc(min(800px, 90vh))"
128+
: "calc(min(600px, 80vh))",
129+
minHeight: isMobile ? "600px" : "400px",
130+
width: "100%",
131+
}}
132+
>
81133
<ResponsiveCalendar
82134
data={data}
83135
from={from}
84136
to={to}
85137
emptyColor="#eeeeee"
86-
colors={[
87-
'#a1e4c4',
88-
'#70c4a4',
89-
'#4da584',
90-
'#2a8564',
91-
'#076544'
92-
]}
138+
direction={isMobile ? "vertical" : undefined}
139+
colors={["#a1e4c4", "#70c4a4", "#4da584", "#2a8564", "#076544"]}
93140
margin={{
94141
top: 50,
95142
right: 20,
96143
bottom: 50,
97-
left: 20
144+
left: 60, //20
98145
}}
99146
yearSpacing={40}
100147
monthBorderColor="#ffffff"
101148
dayBorderWidth={2}
102149
dayBorderColor="#ffffff"
103150
monthLegend={(year, month) => {
104151
const date = new Date(year, month, 1);
105-
return format(date, 'MMM');
152+
return format(date, "MMM");
106153
}}
107154
tooltip={({ day, value }) => (
108155
<div
109156
style={{
110-
background: 'rgba(0, 0, 0, 0.9)',
111-
color: 'white',
112-
padding: '12px 16px',
113-
borderRadius: '4px',
114-
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.3)',
115-
fontSize: '14px',
116-
lineHeight: '1.4'
157+
background: "rgba(0, 0, 0, 0.9)",
158+
color: "white",
159+
padding: "12px 16px",
160+
borderRadius: "4px",
161+
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.3)",
162+
fontSize: "14px",
163+
lineHeight: "1.4",
117164
}}
118165
>
119-
<strong style={{ fontSize: '16px', display: 'block', marginBottom: '4px' }}>
120-
{format(new Date(day), 'MMMM d, yyyy')}
166+
<strong
167+
style={{
168+
fontSize: "16px",
169+
display: "block",
170+
marginBottom: "4px",
171+
}}
172+
>
173+
{format(new Date(day), "MMMM d, yyyy")}
121174
</strong>
122-
{value} {value === 1 ? 'item' : 'items'} watched
175+
{value} {value === "1" ? "item" : "items"} watched
123176
</div>
124177
)}
125178
theme={{
126179
text: {
127180
fontSize: 14,
128-
fill: '#ffffff',
129-
fontWeight: 600
181+
fill: "#ffffff",
182+
fontWeight: 600,
130183
},
131184
legends: {
132185
text: {
133186
fontSize: 16,
134-
fill: '#ffffff',
135-
fontWeight: 500
136-
}
187+
fill: "#ffffff",
188+
fontWeight: 500,
189+
},
137190
},
138191
tooltip: {
139192
container: {
140-
background: '#000000',
141-
color: '#ffffff',
142-
fontSize: '14px'
143-
}
144-
}
193+
background: "#000000",
194+
color: "#ffffff",
195+
fontSize: "14px",
196+
},
197+
},
145198
}}
146199
legends={[
147200
{
148-
anchor: 'bottom',
149-
direction: 'row',
201+
anchor: "bottom",
202+
direction: "row",
150203
translateY: 50,
151204
itemCount: 5,
152205
itemWidth: 60,
153206
itemHeight: 36,
154207
itemsSpacing: 20,
155-
itemDirection: 'right-to-left',
156-
itemTextColor: '#ffffff',
208+
itemDirection: "right-to-left",
209+
itemTextColor: "#ffffff",
157210
symbolSize: 20,
158211
effects: [
159212
{
160-
on: 'hover',
213+
on: "hover",
161214
style: {
162-
itemTextColor: '#a1e4c4'
163-
}
164-
}
165-
]
166-
}
215+
itemTextColor: "#a1e4c4",
216+
},
217+
},
218+
],
219+
},
167220
]}
168221
/>
169222
</div>
@@ -180,4 +233,4 @@ export default function ActivityCalendarPage() {
180233
</div>
181234
</Box>
182235
);
183-
}
236+
}

0 commit comments

Comments
 (0)