AnalyticsView
This commit is contained in:
parent
4222c910eb
commit
10b1ed944d
@ -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
|
<h1>Analytics for
|
||||||
{dateRange[0].getUTCDate() == dateRange[1].getUTCDate()
|
{dateRange[0].getUTCDate() == dateRange[1].getUTCDate()
|
||||||
@ -147,52 +164,82 @@ 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">
|
||||||
<div className="grow">
|
|
||||||
<div className="grid font-bold border-b border-gray-200"
|
|
||||||
style={{
|
|
||||||
gridTemplateColumns: "1fr 1fr"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="whitespace-nowrap ">
|
|
||||||
Total
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{categoryFrequencies.reduce((acc, category) => acc + category.count, 0)} hours
|
|
||||||
</div></div>
|
|
||||||
{
|
|
||||||
categoryFrequencies
|
|
||||||
.sort((a, b) => b.count - a.count)
|
|
||||||
.map((category, i) => (
|
|
||||||
|
|
||||||
<div key={i} className="grid"
|
<PieChart width={500} height={500} startAngle={180} endAngle={0} >
|
||||||
style={{
|
<Pie data={categoryFrequencies} dataKey="percentage" nameKey="categoryName" label={false}
|
||||||
gridTemplateColumns: "1fr 1fr"
|
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="flex flex-col justify-evenly h-full">
|
||||||
|
<div>
|
||||||
|
<div className="grid font-bold border-b border-gray-200 pl-4"
|
||||||
|
style={{
|
||||||
|
gridTemplateColumns: "1fr 1fr"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="whitespace-nowrap ">
|
||||||
|
Total
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{categoryFrequencies.reduce((acc, category) => acc + category.count, 0)} hours
|
||||||
|
</div></div>
|
||||||
|
{
|
||||||
|
categoryFrequencies
|
||||||
|
.sort((a, b) => b.count - a.count)
|
||||||
|
.map((category, i) => (
|
||||||
|
|
||||||
|
<div key={i} className="grid p-1 pl-4"
|
||||||
|
style={{
|
||||||
|
gridTemplateColumns: "1fr 1fr",
|
||||||
|
color: category.inverse,
|
||||||
|
backgroundColor: category.color
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="whitespace-nowrap">
|
||||||
|
{category.categoryName ?? "[Unallocated]"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{category.count} hours
|
||||||
|
</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"}
|
||||||
>
|
>
|
||||||
<div className="whitespace-nowrap">
|
Hide
|
||||||
{category.categoryName ?? "[Unallocated]"}
|
</Button>
|
||||||
</div>
|
<Button
|
||||||
<div>
|
onClick={() => setHideSleep(false)}
|
||||||
{category.count} hours
|
color={hideSleep ? "primary" : "gray"}
|
||||||
</div></div>
|
>
|
||||||
))
|
Show
|
||||||
}
|
</Button>
|
||||||
</div>
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4">
|
<div className="mt-8 pt-4 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 >
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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'>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user