Compare commits
No commits in common. "65e8899426c3b7b0a826b5bf54d9266c579aa590" and "cf1788ebcd840d4211bd133b4a24aec53e3e5ad5" have entirely different histories.
65e8899426
...
cf1788ebcd
@ -1,131 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import * as dFns from "date-fns";
|
|
||||||
|
|
||||||
interface DateValue {
|
|
||||||
date: Date;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
startDate: Date;
|
|
||||||
endDate: Date;
|
|
||||||
data: DateValue[];
|
|
||||||
className?: string;
|
|
||||||
goal: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function GitHubCalendarHeatmap({
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
data,
|
|
||||||
className,
|
|
||||||
goal
|
|
||||||
}: Props) {
|
|
||||||
const numDays = dFns.differenceInDays(endDate, startDate);
|
|
||||||
|
|
||||||
// Calculate the number of weeks in the range
|
|
||||||
const numWeeks = Math.ceil(numDays / 7);
|
|
||||||
|
|
||||||
// Calculate the size of each square in the heatmap and the padding between each square
|
|
||||||
const squareSize = 15;
|
|
||||||
const padding = 2;
|
|
||||||
const weekTotalPadding = 10; // Extra padding for the week total row
|
|
||||||
const totalWidth = numWeeks * (squareSize + padding);
|
|
||||||
const totalHeight = 8 * (squareSize + padding) + weekTotalPadding; // Added an extra row for week total
|
|
||||||
|
|
||||||
// Goal times for the daily and weekly squares
|
|
||||||
const dayGoalTime = goal;
|
|
||||||
const weekGoalTime = 5 * dayGoalTime;
|
|
||||||
|
|
||||||
const getColorForValue = (value: number, goalTime: number): string => {
|
|
||||||
const values = [
|
|
||||||
"#eeeeee",
|
|
||||||
"#d6e685",
|
|
||||||
"#8cc665",
|
|
||||||
"#44a340",
|
|
||||||
"#1e6823",
|
|
||||||
]
|
|
||||||
|
|
||||||
const linearValue = Math.round((value / goalTime) * 4);
|
|
||||||
|
|
||||||
// If we did something, but not enough to reach the first level, return 1
|
|
||||||
if (linearValue == 0 && value > 0) return values[1];
|
|
||||||
|
|
||||||
// Clamp to the granularity
|
|
||||||
if (linearValue > 4) return values[4];
|
|
||||||
return values[linearValue];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getWeekTotal = (weekIndex: number): number => {
|
|
||||||
const weekStart = dFns.addWeeks(dFns.startOfWeek(startDate, {weekStartsOn: 1}), weekIndex);
|
|
||||||
|
|
||||||
return data.filter(dv => dFns.isSameWeek(dv.date, weekStart, {weekStartsOn: 1}))
|
|
||||||
.reduce((total, {count}) => total + count, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const dateValues: DateValue[] = dFns.eachDayOfInterval({
|
|
||||||
start: startDate,
|
|
||||||
end: endDate,
|
|
||||||
}).map((date) => {
|
|
||||||
const foundData = data.find(
|
|
||||||
({date: dataDate}) => dFns.isEqual(dFns.startOfDay(date), dFns.startOfDay(dataDate))
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
date,
|
|
||||||
count: foundData ? foundData.count : 0,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg width="100%" height="100%" viewBox={`0 0 ${totalWidth} ${totalHeight}`} className={className}>
|
|
||||||
{dateValues.map(({
|
|
||||||
date,
|
|
||||||
count
|
|
||||||
}) => {
|
|
||||||
const weekIndex = dFns.differenceInCalendarWeeks(date, startDate, {weekStartsOn: 1});
|
|
||||||
|
|
||||||
const dayOfWeek = (date.getDay() + 6) % 7;
|
|
||||||
const x = weekIndex * (squareSize + padding);
|
|
||||||
const y = dayOfWeek * (squareSize + padding);
|
|
||||||
const color = getColorForValue(count, dayGoalTime);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<rect
|
|
||||||
key={date.toISOString()}
|
|
||||||
x={x}
|
|
||||||
y={y}
|
|
||||||
width={squareSize}
|
|
||||||
height={squareSize}
|
|
||||||
fill={color}
|
|
||||||
data-tooltip-id={`calendar-tooltip`}
|
|
||||||
data-tooltip-content={count ? `${dFns.format(date, 'EEE do')}: ${count.toFixed(2)} hours` : `${dFns.format(date, 'EEE do')}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{Array(numWeeks).fill(0)
|
|
||||||
.map((_, weekIndex) => {
|
|
||||||
const x = weekIndex * (squareSize + padding);
|
|
||||||
const y = 7 * (squareSize + padding) + weekTotalPadding; // Position for the week total
|
|
||||||
const count = getWeekTotal(weekIndex);
|
|
||||||
const color = getColorForValue(count, weekGoalTime);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<rect
|
|
||||||
key={`week-total-${weekIndex}`}
|
|
||||||
x={x}
|
|
||||||
y={y}
|
|
||||||
width={squareSize}
|
|
||||||
height={squareSize}
|
|
||||||
fill={color}
|
|
||||||
data-tooltip-id={`calendar-tooltip`}
|
|
||||||
data-tooltip-content={count ? `Week total ${count.toFixed(2)} hours` : ``}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GitHubCalendarHeatmap;
|
|
||||||
@ -1,9 +1,25 @@
|
|||||||
|
"use client";
|
||||||
import {Card, Title} from "@tremor/react";
|
import {Card, Title} from "@tremor/react";
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import * as dFns from 'date-fns';
|
import * as dFns from 'date-fns';
|
||||||
|
import CalendarHeatmap from 'react-calendar-heatmap';
|
||||||
|
import 'react-calendar-heatmap/dist/styles.css';
|
||||||
|
import '../../app/calendar-styles.css'
|
||||||
import {Tooltip} from 'react-tooltip';
|
import {Tooltip} from 'react-tooltip';
|
||||||
import {Data} from "@/data/fetchData";
|
import {Data} from "@/data/fetchData";
|
||||||
import HeatMap from "@/components/HeatMap";
|
|
||||||
|
const granularity = 4;
|
||||||
|
|
||||||
|
function computeCompletionShade(value: number, dailyGoal: number) {
|
||||||
|
const linearValue = Math.round((value / dailyGoal) * granularity);
|
||||||
|
|
||||||
|
// If we did something, but not enough to reach the first level, return 1
|
||||||
|
if (linearValue == 0 && value > 0) return 1;
|
||||||
|
|
||||||
|
// Clamp to the granularity
|
||||||
|
if (linearValue > granularity) return granularity;
|
||||||
|
return linearValue;
|
||||||
|
}
|
||||||
|
|
||||||
function useCalendarData(data: Data, initialDate: Date, endDate: Date) {
|
function useCalendarData(data: Data, initialDate: Date, endDate: Date) {
|
||||||
const timeEntries = data.timeEntries;
|
const timeEntries = data.timeEntries;
|
||||||
@ -52,9 +68,19 @@ export function CalendarOverviewCard({
|
|||||||
|
|
||||||
return <Card className="col-span-1">
|
return <Card className="col-span-1">
|
||||||
<Tooltip id="calendar-tooltip"/>
|
<Tooltip id="calendar-tooltip"/>
|
||||||
<Title>Semester Overview</Title>
|
<Title>Overview</Title>
|
||||||
<div className="m-2">
|
<CalendarHeatmap
|
||||||
<HeatMap startDate={initialDate} endDate={endDate} data={calendarData} goal={goal}/>
|
showWeekdayLabels={true}
|
||||||
</div>
|
startDate={initialDate}
|
||||||
|
endDate={endDate}
|
||||||
|
values={calendarData}
|
||||||
|
classForValue={value => `color-github-${computeCompletionShade(value?.count ?? 0, goal)}`}
|
||||||
|
tooltipDataAttrs={(value: any) => {
|
||||||
|
return value.date ? {
|
||||||
|
'data-tooltip-id': `calendar-tooltip`,
|
||||||
|
'data-tooltip-content': value.count ? `${dFns.format(value.date, 'EEE do')}: ${value.count.toFixed(2)} hours` : `${dFns.format(value.date, 'EEE do')}`
|
||||||
|
} : undefined
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user