MVP
This commit is contained in:
parent
ef5ef8001f
commit
75dfd44321
43
package-lock.json
generated
43
package-lock.json
generated
@ -11,16 +11,20 @@
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@tremor/react": "^3.12.0",
|
||||
"@types/ramda": "^0.29.9",
|
||||
"date-fns": "^2.30.0",
|
||||
"next": "14.0.4",
|
||||
"ramda": "^0.29.1",
|
||||
"react": "^18",
|
||||
"react-calendar-heatmap": "^1.9.0",
|
||||
"react-dom": "^18",
|
||||
"react-tooltip": "^5.25.0",
|
||||
"reaviz": "^15.2.1",
|
||||
"swr": "^2.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-calendar-heatmap": "^1.6.6",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
@ -954,6 +958,15 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-calendar-heatmap": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-calendar-heatmap/-/react-calendar-heatmap-1.6.6.tgz",
|
||||
"integrity": "sha512-3vjiKV/hmPsOqPNtjgDhZsu7AhJTt8EoNj5NDmPOGbPS8LKajFy+Ia5fQQlTVkJyk0UhhZFkp9zzqoilclabcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.2.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz",
|
||||
@ -4756,6 +4769,23 @@
|
||||
"react": "^16.3.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-calendar-heatmap": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-calendar-heatmap/-/react-calendar-heatmap-1.9.0.tgz",
|
||||
"integrity": "sha512-mGed9any6QLOVckxwxC/eeP9s9wE8mTUW/FCE0V27xF9WOaCGuOftGSRH8DSDoSwgzMSVF6uuH7M1xvc+aZ8sg==",
|
||||
"dependencies": {
|
||||
"memoize-one": "^5.0.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-calendar-heatmap/node_modules/memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="
|
||||
},
|
||||
"node_modules/react-cool-dimensions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-cool-dimensions/-/react-cool-dimensions-3.0.1.tgz",
|
||||
@ -4886,6 +4916,19 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-tooltip": {
|
||||
"version": "5.25.0",
|
||||
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.25.0.tgz",
|
||||
"integrity": "sha512-/eGhmlwbHlJrVoUe75fb58rJfAy9aZnTvQAK9ZUPM0n9mmBGpEk13vDPiQVCeUuax+fBej+7JPsUXlhzaySc7w==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.0.0",
|
||||
"classnames": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
|
||||
@ -12,16 +12,20 @@
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@tremor/react": "^3.12.0",
|
||||
"@types/ramda": "^0.29.9",
|
||||
"date-fns": "^2.30.0",
|
||||
"next": "14.0.4",
|
||||
"ramda": "^0.29.1",
|
||||
"react": "^18",
|
||||
"react-calendar-heatmap": "^1.9.0",
|
||||
"react-dom": "^18",
|
||||
"react-tooltip": "^5.25.0",
|
||||
"reaviz": "^15.2.1",
|
||||
"swr": "^2.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-calendar-heatmap": "^1.6.6",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^8",
|
||||
|
||||
15
src/app/calendar-styles.css
Normal file
15
src/app/calendar-styles.css
Normal file
@ -0,0 +1,15 @@
|
||||
.react-calendar-heatmap .color-gitlab-0 {
|
||||
fill: #ededed;
|
||||
}
|
||||
.react-calendar-heatmap .color-gitlab-1 {
|
||||
fill: #acd5f2;
|
||||
}
|
||||
.react-calendar-heatmap .color-gitlab-2 {
|
||||
fill: #7fa8d1;
|
||||
}
|
||||
.react-calendar-heatmap .color-gitlab-3 {
|
||||
fill: #49729b;
|
||||
}
|
||||
.react-calendar-heatmap .color-gitlab-4 {
|
||||
fill: #254e77;
|
||||
}
|
||||
100
src/app/calendarOverviewCard.tsx
Normal file
100
src/app/calendarOverviewCard.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
import {Card, Title} from "@tremor/react";
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import useSWR from "swr";
|
||||
import {fetcher} from "@/app/utils";
|
||||
import * as R from 'ramda';
|
||||
import * as dFns from 'date-fns';
|
||||
import CalendarHeatmap from 'react-calendar-heatmap';
|
||||
import 'react-calendar-heatmap/dist/styles.css';
|
||||
import './calendar-styles.css'
|
||||
import * as dns from "dns";
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
|
||||
const initialDate = dFns.parseISO('2023-12-15T00:00:00.000Z')
|
||||
const endDate = dFns.parseISO('2024-01-20T00:00:00.000Z')
|
||||
const projectIds = [
|
||||
195482340,
|
||||
195519024,
|
||||
195518593,
|
||||
195754611,
|
||||
];
|
||||
|
||||
function useCalendarData() {
|
||||
|
||||
const {
|
||||
data: timeEntries,
|
||||
error,
|
||||
isLoading,
|
||||
} = useSWR<{
|
||||
raw_json: {
|
||||
start: string,
|
||||
seconds: number,
|
||||
}
|
||||
}[]>(`http://cosmos:8074/time_entry?select=raw_json&start=gt.${dFns.formatISO(initialDate)}&project_id=in.(${projectIds.join(',')})`, fetcher, {});
|
||||
|
||||
const [data, setData] = useState<{
|
||||
date: Date,
|
||||
count: number,
|
||||
}[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timeEntries == undefined) return;
|
||||
|
||||
// Group by day, sum up seconds
|
||||
const grouped = R.groupBy((entry) => {
|
||||
return dFns.formatISO(dFns.parseISO(entry.raw_json.start), 'yyyy-MM-dd');
|
||||
}, timeEntries);
|
||||
|
||||
const summed = R.mapObjIndexed((entries) => {
|
||||
return R.sum((entries ?? []).map((entry) => entry.raw_json.seconds))
|
||||
}, grouped);
|
||||
|
||||
setData(Object.entries(summed)
|
||||
.map(([key, value]) => ({
|
||||
date: dFns.parseISO(key),
|
||||
count: value / (60 * 60),
|
||||
})));
|
||||
}, [timeEntries]);
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export function CalendarOverviewCard() {
|
||||
const data = useCalendarData();
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [height, setHeight] = useState(200);
|
||||
|
||||
// Fix aspect ratio of the heatmap to be 2:1
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (container) {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
const width = container.clientWidth;
|
||||
setHeight(width / 5);
|
||||
});
|
||||
resizeObserver.observe(container);
|
||||
return () => resizeObserver.disconnect();
|
||||
}
|
||||
}, [containerRef]);
|
||||
|
||||
return <Card className="col-span-1">
|
||||
<Tooltip id="calendar-tooltip"/>
|
||||
<Title>Overview</Title>
|
||||
<CalendarHeatmap
|
||||
showWeekdayLabels={true}
|
||||
startDate={initialDate}
|
||||
endDate={endDate}
|
||||
values={data}
|
||||
tooltipDataAttrs={(value: any) => {
|
||||
return value.count ? {
|
||||
'data-tooltip-id': `calendar-tooltip`,
|
||||
'data-tooltip-content': `${value.count.toFixed(2)} hours`
|
||||
} : undefined;
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
}
|
||||
@ -2,18 +2,6 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
/*--background-start-rgb: 214, 219, 220;*/
|
||||
/*--background-end-rgb: 255, 255, 255;*/
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
@apply dark:bg-slate-800 bg-white;
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import {SubjectComparisonCard, SubjectOverviewCard} from "@/app/a.client";
|
||||
import {Title} from "@tremor/react";
|
||||
import {CalendarOverviewCard} from "@/app/calendarOverviewCard";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="m-6">
|
||||
<h1 className="text-3xl font-semibold font-black">Revision Tracker</h1>
|
||||
<h1 className="text-3xl font-semibold text-slate-900 dark:text-white my-2">Revision Tracker</h1>
|
||||
|
||||
<div className="grid gap-5 grid-cols-4">
|
||||
<SubjectOverviewCard
|
||||
@ -25,12 +25,7 @@ export default function Home() {
|
||||
projectId={195754611}
|
||||
/>
|
||||
|
||||
<SubjectComparisonCard projectIds={[
|
||||
195482340,
|
||||
195519024,
|
||||
195518593,
|
||||
195754611,
|
||||
]}/>
|
||||
<CalendarOverviewCard/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user