From 75dfd4432123ed1089feed16ff761b68d987d90f Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Tue, 19 Dec 2023 21:59:44 +0000 Subject: [PATCH] MVP --- package-lock.json | 43 +++++++++++++ package.json | 4 ++ src/app/calendar-styles.css | 15 +++++ src/app/calendarOverviewCard.tsx | 100 +++++++++++++++++++++++++++++++ src/app/globals.css | 14 +---- src/app/page.tsx | 11 +--- 6 files changed, 166 insertions(+), 21 deletions(-) create mode 100644 src/app/calendar-styles.css create mode 100644 src/app/calendarOverviewCard.tsx diff --git a/package-lock.json b/package-lock.json index 5cfda92..cc9bb47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7d69751..fdfa5dc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/calendar-styles.css b/src/app/calendar-styles.css new file mode 100644 index 0000000..14ce7ab --- /dev/null +++ b/src/app/calendar-styles.css @@ -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; +} diff --git a/src/app/calendarOverviewCard.tsx b/src/app/calendarOverviewCard.tsx new file mode 100644 index 0000000..fa5c91d --- /dev/null +++ b/src/app/calendarOverviewCard.tsx @@ -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(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 + + Overview + { + return value.count ? { + 'data-tooltip-id': `calendar-tooltip`, + 'data-tooltip-content': `${value.count.toFixed(2)} hours` + } : undefined; + }} + /> + + +} diff --git a/src/app/globals.css b/src/app/globals.css index d9da8c8..9e61485 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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; } diff --git a/src/app/page.tsx b/src/app/page.tsx index 4868321..a0453d2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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 (
-

Revision Tracker

+

Revision Tracker

- +