diff --git a/app/(charts)/day.tsx b/app/(charts)/day.tsx index e4c2984..26a193d 100644 --- a/app/(charts)/day.tsx +++ b/app/(charts)/day.tsx @@ -2,7 +2,6 @@ import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ChartConfig, ChartContainer, @@ -22,91 +21,35 @@ const chartConfig = { } satisfies ChartConfig; export function DayChart({ - data, + chartData, }: { - data: { - timestamp: number; + chartData: { + day: string; + value: number; foldtime: number; }[]; }) { - const thisMonthTimestamp = new Date(); - thisMonthTimestamp.setHours(0, 0, 0, 0); - thisMonthTimestamp.setDate(1); - const nextMonthTimestamp = new Date(); - nextMonthTimestamp.setHours(0, 0, 0, 0); - nextMonthTimestamp.setDate(1); - nextMonthTimestamp.setMonth(nextMonthTimestamp.getMonth() + 1); - - console.log(thisMonthTimestamp.getTime(), nextMonthTimestamp.getTime()); - - const thisMonthData = data.filter( - (data) => - data.timestamp >= thisMonthTimestamp.getTime() && - data.timestamp < nextMonthTimestamp.getTime() - ); - - const todayDataPerHour = thisMonthData.reduce((acc, data) => { - const day = new Date(data.timestamp).getDate(); - if (!acc[day]) { - acc[day] = { - count: 1, - foldTime: data.foldtime, - }; - } - acc[day].count += 1; - acc[day].foldTime += data.foldtime; - return acc; - }, {} as Record); - - const thisMonthDays = new Date(); - thisMonthDays.setMonth(thisMonthDays.getMonth() + 1); - thisMonthDays.setDate(1); - thisMonthDays.setDate(thisMonthDays.getDate() - 1); - - const chartData = new Array(thisMonthDays.getDate()).fill(0).map((_, i) => { - const day = i + 1; - if (!todayDataPerHour[day]) - return { day: "Day " + day, value: 0, foldtime: 0 }; - return { - day: "Day " + day, - value: todayDataPerHour[day].count, - foldtime: - todayDataPerHour[day].foldTime / todayDataPerHour[day].count, - }; - }); return ( - - - This Month - {/* January - June 2024 */} - - - - - - - } - /> - - - - - - + + + + + } + /> + + + + ); } diff --git a/app/(charts)/hour.tsx b/app/(charts)/hour.tsx index b338265..c130e95 100644 --- a/app/(charts)/hour.tsx +++ b/app/(charts)/hour.tsx @@ -2,7 +2,6 @@ import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ChartConfig, ChartContainer, @@ -22,82 +21,35 @@ const chartConfig = { } satisfies ChartConfig; export function HourChart({ - data, + chartData, }: { - data: { - timestamp: number; + chartData: { + hour: string; + value: number; foldtime: number; }[]; }) { - const todayTimestamp = new Date(); - todayTimestamp.setHours(0, 0, 0, 0); - const tomorrowTimestamp = new Date(); - tomorrowTimestamp.setHours(0, 0, 0, 0); - tomorrowTimestamp.setDate(tomorrowTimestamp.getDate() + 1); - - console.log(todayTimestamp.getTime(), tomorrowTimestamp.getTime()); - - const todayData = data.filter( - (data) => - data.timestamp >= todayTimestamp.getTime() && - data.timestamp < tomorrowTimestamp.getTime() - ); - - const todayDataPerHour = todayData.reduce((acc, data) => { - const hour = new Date(data.timestamp).getHours(); - if (!acc[hour]) { - acc[hour] = { - count: 1, - foldTime: data.foldtime, - }; - } - acc[hour].count += 1; - acc[hour].foldTime += data.foldtime; - return acc; - }, {} as Record); - - const chartData = new Array(24).fill(0).map((_, i) => { - if (!todayDataPerHour[i]) - return { hour: i + 1 + ":00", value: 0, foldtime: 0 }; - return { - hour: i + 1 + ":00", - value: todayDataPerHour[i].count, - foldtime: todayDataPerHour[i].foldTime / todayDataPerHour[i].count, - }; - }); return ( - - - Today - {/* January - June 2024 */} - - - - - - - } - /> - - - - - - + + + + + } + /> + + + + ); } diff --git a/app/(charts)/minute.tsx b/app/(charts)/minute.tsx index bcbab3b..80c406f 100644 --- a/app/(charts)/minute.tsx +++ b/app/(charts)/minute.tsx @@ -2,7 +2,6 @@ import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ChartConfig, ChartContainer, @@ -22,85 +21,36 @@ const chartConfig = { } satisfies ChartConfig; export function MinuteChart({ - data, + chartData, }: { - data: { - timestamp: number; + chartData: { + minute: string; + value: number; foldtime: number; }[]; }) { - const thisHourTimestamp = new Date(); - thisHourTimestamp.setMinutes(0, 0, 0); - const nextHourTimestamp = new Date(); - nextHourTimestamp.setHours(nextHourTimestamp.getHours() + 1); - nextHourTimestamp.setMinutes(0, 0, 0); - - console.log(thisHourTimestamp.getTime(), nextHourTimestamp.getTime()); - - const todayData = data.filter( - (data) => - data.timestamp >= thisHourTimestamp.getTime() && - data.timestamp < nextHourTimestamp.getTime() - ); - - const todayDataPerHour = todayData.reduce((acc, data) => { - const hour = new Date(data.timestamp).getMinutes(); - if (!acc[hour]) { - acc[hour] = { - count: 1, - foldTime: data.foldtime, - }; - } - acc[hour].count += 1; - acc[hour].foldTime += data.foldtime; - return acc; - }, {} as Record); - - const chartData = new Array(60).fill(0).map((_, i) => { - if (!todayDataPerHour[i]) - return { minute: "Minute " + i, value: 0, foldtime: 0 }; - return { - minute: "Minute " + i, - value: todayDataPerHour[i].count, - foldtime: todayDataPerHour[i].foldTime / todayDataPerHour[i].count, - }; - }); return ( - - - This Hour - {/* January - June 2024 */} - - - - - - - value.replace("Minute ", "") - } - /> - } - /> - - - - - - + + + + value.replace("Minute ", "")} + /> + } + /> + + + + ); } diff --git a/app/page.tsx b/app/page.tsx index 51dc3bb..7319abd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,6 +2,15 @@ import fs from "node:fs"; import { HourChart } from "./(charts)/hour"; import { DayChart } from "./(charts)/day"; import { MinuteChart } from "./(charts)/minute"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; export default async function Home() { const file = await fs.promises.readFile("./output.txt"); @@ -9,20 +18,264 @@ export default async function Home() { const lines = file.toString().split("\n"); const data = lines.map((line) => { - const [timestamp, event, value] = line.split(","); + const [timestamp, _, value] = line.split(","); return { timestamp: Math.round(parseFloat(timestamp) * 1000), foldtime: parseFloat(value), }; }); - console.log(data); + const thisMonthData = convertDataToThisMonthPerDay(data); + const thisHourData = convertDataToThisHourPerMinute(data); + const todayData = convertDataToTodayPerHour(data); return (
- - - +
+
+ This Month +

+ {thisMonthData.reduce((acc, curr) => { + return acc + 1; + }, 0)} + {" Towel(s)"} +

+ + Avg Fold Time (s):{" "} + {thisMonthData.reduce((acc, curr) => { + return acc + curr.foldtime; + }, 0) / thisMonthData.length || 0} + +
+
+ Today +

+ {todayData.reduce((acc, curr) => { + return acc + 1; + }, 0)} + {" Towel(s)"} +

+ + Avg Fold Time (s):{" "} + {todayData.reduce((acc, curr) => { + return acc + curr.foldtime; + }, 0) / todayData.length || 0} + +
+
+ This Month +

+ {thisMonthData.reduce((acc, curr) => { + return acc + 1; + }, 0)} + {" Towel(s)"} +

+ + Avg Fold Time (s):{" "} + {thisHourData.reduce((acc, curr) => { + return acc + curr.foldtime; + }, 0) / thisHourData.length || 0} + +
+
+ + + + + + + Month + + + Day + + + Hour + + + + + + + + + + + + + + + + +
); } + +const convertDataToThisMonthPerDay = ( + data: { timestamp: number; foldtime: number }[] +) => { + const thisMonthTimestamp = new Date(); + thisMonthTimestamp.setHours(0, 0, 0, 0); + thisMonthTimestamp.setDate(1); + const nextMonthTimestamp = new Date(); + nextMonthTimestamp.setHours(0, 0, 0, 0); + nextMonthTimestamp.setDate(1); + nextMonthTimestamp.setMonth(nextMonthTimestamp.getMonth() + 1); + + console.log(thisMonthTimestamp.getTime(), nextMonthTimestamp.getTime()); + + const thisMonthData = data.filter( + (data) => + data.timestamp >= thisMonthTimestamp.getTime() && + data.timestamp < nextMonthTimestamp.getTime() + ); + + return thisMonthData; +}; + +const convertDataToThisMonthPerDayChartData = ( + data: { timestamp: number; foldtime: number }[] +) => { + const todayDataPerHour = data.reduce((acc, data) => { + const day = new Date(data.timestamp).getDate(); + if (!acc[day]) { + acc[day] = { + count: 0, + foldTime: 0, + }; + } + acc[day].count += 1; + acc[day].foldTime += data.foldtime; + return acc; + }, {} as Record); + + const thisMonthDays = new Date(); + thisMonthDays.setMonth(thisMonthDays.getMonth() + 1); + thisMonthDays.setDate(1); + thisMonthDays.setDate(thisMonthDays.getDate() - 1); + + const chartData = new Array(thisMonthDays.getDate()).fill(0).map((_, i) => { + const day = i + 1; + if (!todayDataPerHour[day]) + return { day: "Day " + day, value: 0, foldtime: 0 }; + return { + day: "Day " + day, + value: todayDataPerHour[day].count, + foldtime: + todayDataPerHour[day].foldTime / todayDataPerHour[day].count, + }; + }); + + return chartData; +}; + +const convertDataToThisHourPerMinute = ( + data: { timestamp: number; foldtime: number }[] +) => { + const thisHourTimestamp = new Date(); + thisHourTimestamp.setMinutes(0, 0, 0); + const nextHourTimestamp = new Date(); + nextHourTimestamp.setHours(nextHourTimestamp.getHours() + 1); + nextHourTimestamp.setMinutes(0, 0, 0); + + console.log(thisHourTimestamp.getTime(), nextHourTimestamp.getTime()); + + const todayData = data.filter( + (data) => + data.timestamp >= thisHourTimestamp.getTime() && + data.timestamp < nextHourTimestamp.getTime() + ); + + return todayData; +}; + +const convertDataToThisHourPerMinuteChartData = ( + data: { timestamp: number; foldtime: number }[] +) => { + const todayDataPerHour = data.reduce((acc, data) => { + const hour = new Date(data.timestamp).getMinutes(); + if (!acc[hour]) { + acc[hour] = { + count: 0, + foldTime: 0, + }; + } + acc[hour].count += 1; + acc[hour].foldTime += data.foldtime; + return acc; + }, {} as Record); + + const chartData = new Array(60).fill(0).map((_, i) => { + if (!todayDataPerHour[i]) + return { minute: "Minute " + i, value: 0, foldtime: 0 }; + return { + minute: "Minute " + i, + value: todayDataPerHour[i].count, + foldtime: todayDataPerHour[i].foldTime / todayDataPerHour[i].count, + }; + }); + + return chartData; +}; + +const convertDataToTodayPerHour = ( + data: { timestamp: number; foldtime: number }[] +) => { + const todayTimestamp = new Date(); + todayTimestamp.setHours(0, 0, 0, 0); + const tomorrowTimestamp = new Date(); + tomorrowTimestamp.setHours(0, 0, 0, 0); + tomorrowTimestamp.setDate(tomorrowTimestamp.getDate() + 1); + + console.log(todayTimestamp.getTime(), tomorrowTimestamp.getTime()); + + const todayData = data.filter( + (data) => + data.timestamp >= todayTimestamp.getTime() && + data.timestamp < tomorrowTimestamp.getTime() + ); + + return todayData; +}; + +const convertDataToTodayPerHourChartData = ( + data: { timestamp: number; foldtime: number }[] +) => { + const todayDataPerHour = data.reduce((acc, data) => { + const hour = new Date(data.timestamp).getHours(); + if (!acc[hour]) { + acc[hour] = { + count: 0, + foldTime: 0, + }; + } + acc[hour].count += 1; + acc[hour].foldTime += data.foldtime; + return acc; + }, {} as Record); + + const chartData = new Array(24).fill(0).map((_, i) => { + if (!todayDataPerHour[i]) + return { hour: i + 1 + ":00", value: 0, foldtime: 0 }; + return { + hour: i + 1 + ":00", + value: todayDataPerHour[i].count, + foldtime: todayDataPerHour[i].foldTime / todayDataPerHour[i].count, + }; + }); + + return chartData; +}; diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx new file mode 100644 index 0000000..26eb109 --- /dev/null +++ b/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/package-lock.json b/package-lock.json index 99aaae7..c175a52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-tabs": "^1.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.469.0", @@ -439,6 +440,31 @@ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", @@ -481,6 +507,23 @@ } } }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-presence": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", @@ -526,6 +569,36 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz", + "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-scroll-area": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz", @@ -573,6 +646,35 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz", + "integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -587,6 +689,23 @@ } } }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", diff --git a/package.json b/package.json index 4c27a83..5688237 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-tabs": "^1.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.469.0",