AnalyticsView

This commit is contained in:
Ryan Pandya 2025-02-01 16:17:44 -08:00
parent 4222c910eb
commit 10b1ed944d
3 changed files with 113 additions and 51 deletions

View File

@ -8,9 +8,10 @@ import { useSearchParams } from "next/navigation";
import { parseISO, format as fmt } from "date-fns"; import { parseISO, format as fmt } from "date-fns";
import { DatePickerInput } from '@mantine/dates'; import { DatePickerInput } from '@mantine/dates';
import { Calendar1, MenuIcon } from "lucide-react"; import { Calendar1, MenuIcon } from "lucide-react";
import { Anchor, Button, Menu } from "@mantine/core"; import { Anchor, Button, ButtonGroup, Menu } from "@mantine/core";
import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip'; import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip';
import PieChart from "./PieChart"; // import PieChart from "./PieChart";
import { Cell, LabelList, Pie, PieChart, Tooltip } from "recharts";
import { useTimezone } from "@/lib/userLocalSettings/client"; import { useTimezone } from "@/lib/userLocalSettings/client";
import TimeseriesChart from "./Timeseries"; import TimeseriesChart from "./Timeseries";
@ -56,6 +57,7 @@ export default function AnalyticsView() {
const [dateRange, setDateRange] = useState(initialDateRange); const [dateRange, setDateRange] = useState(initialDateRange);
const [datePickerRange, setDatePickerRange] = useState(initialDateRange); const [datePickerRange, setDatePickerRange] = useState(initialDateRange);
const [hideSleep, setHideSleep] = useState(true);
useEffect(() => { useEffect(() => {
if (datePickerRef.current?.getAttribute("aria-expanded") === "false") { if (datePickerRef.current?.getAttribute("aria-expanded") === "false") {
@ -71,19 +73,34 @@ export default function AnalyticsView() {
scroll: true, scroll: true,
}); });
const categoryFrequencies = api.hours.categoryFrequencies.useQuery({ const rawCategoryFrequencies = api.hours.categoryFrequencies.useQuery({
dateRange, dateRange,
timezone: useTimezone(), timezone: useTimezone(),
}).data ?? []; }).data?.filter(
(category) => category.categoryCode != undefined
) ?? [];
const categoryFrequencies = hideSleep ? rawCategoryFrequencies.filter((category) => category.categoryCode > 1) : rawCategoryFrequencies;
const maybeExtendWindow = (dateRange: Date[]) => {
// If distance between dates is less than 2 days, extend the first date back by 14 days
if (spacetime(dateRange[0]).diff(dateRange[1], "day") < 2) {
return [
spacetime(dateRange[0]).subtract(14, "day").toNativeDate(),
dateRange[1]
];
}
return [dateRange[0], dateRange[1]];
}
const weightData = api.measurements.getTimeseries.useQuery({ const weightData = api.measurements.getTimeseries.useQuery({
dateRange, dateRange: maybeExtendWindow(dateRange) as [Date, Date],
timezone: useTimezone(), timezone: useTimezone(),
metricName: "weight" metricName: "weight"
}).data ?? []; }).data ?? [];
return ( return (
<div className="px-4 flex flex-col gap-y-4"> <div className="px-4 flex flex-col">
<div className="flex justify-between"> <div className="flex justify-between">
<h1>Analytics for&nbsp; <h1>Analytics for&nbsp;
{dateRange[0].getUTCDate() == dateRange[1].getUTCDate() {dateRange[0].getUTCDate() == dateRange[1].getUTCDate()
@ -147,12 +164,29 @@ export default function AnalyticsView() {
</Menu></div> </Menu></div>
</div> </div>
<div className="flex hidden"> <div className="mt-8 pt-4 flex gap-4">
<PieChart width={500} height={500} <h2>Time</h2>
data={categoryFrequencies} {categoryFrequencies.length === 0 ? <LoadingSpinner /> :
<div className="w-full flex flex-col lg:flex-row gap-4">
<PieChart width={500} height={500} startAngle={180} endAngle={0} >
<Pie data={categoryFrequencies} dataKey="percentage" nameKey="categoryName" label={false}
paddingAngle={0} stroke={(c) => console.log(c)}>
{categoryFrequencies.sort(
(a, b) => b.count - a.count
).map((c, _i) => (
<Cell key={`pie-category-${c.id}`} fill={c.background}
style={{ outline: 'none !important' }}
/> />
))}
</Pie>
<Tooltip />
</PieChart>
<div className="grow"> <div className="grow">
<div className="grid font-bold border-b border-gray-200" <div className="flex flex-col justify-evenly h-full">
<div>
<div className="grid font-bold border-b border-gray-200 pl-4"
style={{ style={{
gridTemplateColumns: "1fr 1fr" gridTemplateColumns: "1fr 1fr"
}} }}
@ -168,9 +202,11 @@ export default function AnalyticsView() {
.sort((a, b) => b.count - a.count) .sort((a, b) => b.count - a.count)
.map((category, i) => ( .map((category, i) => (
<div key={i} className="grid" <div key={i} className="grid p-1 pl-4"
style={{ style={{
gridTemplateColumns: "1fr 1fr" gridTemplateColumns: "1fr 1fr",
color: category.inverse,
backgroundColor: category.color
}} }}
> >
<div className="whitespace-nowrap"> <div className="whitespace-nowrap">
@ -180,19 +216,30 @@ export default function AnalyticsView() {
{category.count} hours {category.count} hours
</div></div> </div></div>
)) ))
}</div>
</div>
</div>
<div className="mt-4 bg-accent w-fit p-2 border border-primary rounded-md cursor-pointer flex flex-col items-center">
Sleep
<ButtonGroup className="mt-2">
<Button
onClick={() => setHideSleep(true)}
color={hideSleep ? "gray" : "primary"}
>
Hide
</Button>
<Button
onClick={() => setHideSleep(false)}
color={hideSleep ? "primary" : "gray"}
>
Show
</Button>
</ButtonGroup>
</div>
</div>
} }
</div> </div>
</div> <div className="mt-8 pt-4 flex gap-4">
<div className="flex gap-4">
<h2>Weight</h2>
<div>
{
!weightData ? <LoadingSpinner /> :
<TimeseriesChart data={weightData} />
}
</div>
</div>
<div className="flex gap-4">
<h2 className="">Drugs</h2> <h2 className="">Drugs</h2>
<div> <div>
{ {
@ -206,7 +253,19 @@ export default function AnalyticsView() {
</ul> </ul>
} }
</div> </div>
</div> </div>
<div className="mt-8 pt-4 flex gap-4">
<h2>Weight</h2>
{
weightData.length === 0 ? <LoadingSpinner /> :
<div>
<TimeseriesChart data={weightData} />
</div> </div>
}
</div>
</div >
); );
} }

View File

@ -29,7 +29,6 @@ export default function TimeseriesChart({ data }) {
smoothed: polyResult.predict(i)[1], smoothed: polyResult.predict(i)[1],
}; };
})); }));
console.log(polyResult);
return ( return (
<div className='flex flex-col items-center'> <div className='flex flex-col items-center'>

View File

@ -229,6 +229,8 @@ export const hoursAppRouter = router({
{ {
count: z.number(), count: z.number(),
percentage: z.number(), percentage: z.number(),
color: z.string(),
inverse: z.string(),
...zHourSchema.shape ...zHourSchema.shape
} }
))) )))
@ -257,6 +259,8 @@ export const hoursAppRouter = router({
if (!categoriesList[h.categoryCode]) { if (!categoriesList[h.categoryCode]) {
categoriesList[h.categoryCode] = { categoriesList[h.categoryCode] = {
count: 0, count: 0,
color: h.background,
inverse: h.foreground,
...h ...h
}; };
} }
@ -269,7 +273,7 @@ export const hoursAppRouter = router({
const percentage = (count / totalHours); const percentage = (count / totalHours);
return { return {
...categoriesList[categoryCode], ...categoriesList[categoryCode],
percentage: percentage percentage: percentage,
}; };
}); });
return categoryPercentages; return categoryPercentages;