Compare commits
No commits in common. "86c88307dfea03a2895f8faf93aceca7fa90aa4f" and "a932321324fc5fe5557432cb029d681f2ec6de0d" have entirely different histories.
86c88307df
...
a932321324
@ -1,43 +0,0 @@
|
|||||||
import { NextRequest } from "next/server";
|
|
||||||
import { zMeasurementInputSchema } from "@lifetracker/shared/types/metrics";
|
|
||||||
|
|
||||||
import { buildHandler } from "../utils/handler";
|
|
||||||
|
|
||||||
import spacetime from "spacetime";
|
|
||||||
|
|
||||||
export const POST = (req: NextRequest) =>
|
|
||||||
buildHandler({
|
|
||||||
req,
|
|
||||||
bodySchema: zMeasurementInputSchema,
|
|
||||||
handler: async ({ api, body }) => {
|
|
||||||
|
|
||||||
const datetime = spacetime(
|
|
||||||
body?.dateTimeQuery || new Date(),
|
|
||||||
body?.timezone || "Etc/UTC"
|
|
||||||
).goto("Etc/UTC");
|
|
||||||
|
|
||||||
// const dayId = (await api.days.get({
|
|
||||||
// dateQuery: datetime.format("iso-short")
|
|
||||||
// })).id;
|
|
||||||
|
|
||||||
const hour = await api.hours.get({
|
|
||||||
dateQuery: datetime.format("iso-short"),
|
|
||||||
time: datetime.hour(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
metricName: body!.metricName,
|
|
||||||
hourId: hour.id!,
|
|
||||||
dayId: hour.dayId
|
|
||||||
};
|
|
||||||
|
|
||||||
const measurement =
|
|
||||||
body?.value
|
|
||||||
? await api.measurements.setValue({
|
|
||||||
...obj,
|
|
||||||
value: body.value!,
|
|
||||||
})
|
|
||||||
: await api.measurements.incrementCount(obj);
|
|
||||||
return { status: 201, resp: measurement };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import AnalyticsView from "@/components/dashboard/analytics/AnalyticsView";
|
|
||||||
|
|
||||||
export default async function AnalyticsPage() {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnalyticsView />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import LoadingSpinner from "@/components/ui/spinner";
|
|
||||||
import { api } from "@/lib/trpc";
|
|
||||||
|
|
||||||
export default function AnalyticsView() {
|
|
||||||
|
|
||||||
const { data: metrics } = api.metrics.list.useQuery();
|
|
||||||
|
|
||||||
const drugsList = metrics?.filter((metric) => metric.type === "drug");
|
|
||||||
const timeSinceDrug = drugsList?.map((drug) => {
|
|
||||||
console.log(api.measurements.timeSinceLastMeasurement.useQuery({ metricId: drug.id! }));
|
|
||||||
return drug.name;
|
|
||||||
});
|
|
||||||
console.log(timeSinceDrug);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<h1 className="font-bold text-xl">Drugs</h1>
|
|
||||||
<div>
|
|
||||||
{
|
|
||||||
!drugsList ? <LoadingSpinner /> :
|
|
||||||
<ul>
|
|
||||||
{drugsList.map((drug) => (
|
|
||||||
<li key={drug.id}>
|
|
||||||
{drug.name}:
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -12,7 +12,7 @@ export default async function MobileSidebar() {
|
|||||||
/>
|
/>
|
||||||
<MobileSidebarItem logo={<Tag />} path="/dashboard/categories" />
|
<MobileSidebarItem logo={<Tag />} path="/dashboard/categories" />
|
||||||
<MobileSidebarItem logo={<CheckCheck />} path="/dashboard/metrics" />
|
<MobileSidebarItem logo={<CheckCheck />} path="/dashboard/metrics" />
|
||||||
<MobileSidebarItem logo={<GaugeCircleIcon />} path="/dashboard/analytics" />
|
<MobileSidebarItem logo={<GaugeCircleIcon />} path="/analytics" />
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import SidebarItem from "@/components/shared/sidebar/SidebarItem";
|
|||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { api } from "@/server/api/client";
|
import { api } from "@/server/api/client";
|
||||||
import { getServerAuthSession } from "@/server/auth";
|
import { getServerAuthSession } from "@/server/auth";
|
||||||
import { Archive, ArrowRightFromLine, Calendar, CheckCheck, Gauge, GaugeCircleIcon, Home, LineChart, PanelLeftOpen, Ruler, Search, SunMoon, Tag } from "lucide-react";
|
import { Archive, ArrowRightFromLine, Calendar, CheckCheck, Gauge, Home, LineChart, PanelLeftOpen, Ruler, Search, SunMoon, Tag } from "lucide-react";
|
||||||
import serverConfig from "@lifetracker/shared/config";
|
import serverConfig from "@lifetracker/shared/config";
|
||||||
|
|
||||||
import AllLists from "./AllLists";
|
import AllLists from "./AllLists";
|
||||||
@ -43,11 +43,6 @@ export default async function Sidebar() {
|
|||||||
icon: <Calendar size={18} />,
|
icon: <Calendar size={18} />,
|
||||||
path: "/dashboard/timeline",
|
path: "/dashboard/timeline",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Analytics",
|
|
||||||
icon: <GaugeCircleIcon size={18} />,
|
|
||||||
path: "/dashboard/analytics",
|
|
||||||
},
|
|
||||||
...searchItem,
|
...searchItem,
|
||||||
{
|
{
|
||||||
name: "Metrics",
|
name: "Metrics",
|
||||||
|
|||||||
@ -13,13 +13,6 @@ export const zMeasurementSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type ZMeasurement = z.infer<typeof zMeasurementSchema>;
|
export type ZMeasurement = z.infer<typeof zMeasurementSchema>;
|
||||||
|
|
||||||
export const zMeasurementInputSchema = z.object({
|
|
||||||
metricName: z.string(),
|
|
||||||
dateTimeQuery: z.string().nullish(),
|
|
||||||
timezone: z.string().nullish(),
|
|
||||||
value: z.coerce.number().nullish(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const zMetricSchema = z.object({
|
export const zMetricSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
|||||||
@ -92,16 +92,15 @@ export const hoursAppRouter = router({
|
|||||||
}))
|
}))
|
||||||
.output(zHourSchema)
|
.output(zHourSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const hourRes = await ctx.db.select().from(hours)
|
const date = dateFromInput({ dateQuery: input.dateQuery });
|
||||||
.leftJoin(days, eq(days.id, hours.dayId))
|
const hourRes = await getHourSelectQuery(ctx, date, input.time,
|
||||||
.where(
|
and(eq(hours.time, input.time), eq(days.date, date))
|
||||||
and(
|
);
|
||||||
eq(hours.time, input.time),
|
|
||||||
eq(days.date, input.dateQuery),
|
return {
|
||||||
eq(hours.userId, ctx.user!.id)
|
date: format(date, "yyyy-MM-dd"),
|
||||||
)
|
...hourRes[0]
|
||||||
);
|
};
|
||||||
return hourRes[0].hour;
|
|
||||||
}),
|
}),
|
||||||
update: authedProcedure
|
update: authedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|||||||
@ -8,23 +8,6 @@ import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/met
|
|||||||
import type { Context } from "../index";
|
import type { Context } from "../index";
|
||||||
import { authedProcedure, router } from "../index";
|
import { authedProcedure, router } from "../index";
|
||||||
import { zColorSchema } from "@lifetracker/shared/types/colors";
|
import { zColorSchema } from "@lifetracker/shared/types/colors";
|
||||||
import { titleCase } from "title-case";
|
|
||||||
|
|
||||||
const getMetricFromInput = async (ctx: Context, input:
|
|
||||||
{ metricId?: string | null | undefined; metricName?: string | null | undefined; }) => {
|
|
||||||
const metric = input.metricName
|
|
||||||
? await ctx.db.select().from(metrics).where(eq(metrics.name, titleCase(input.metricName)))
|
|
||||||
: await ctx.db.select().from(metrics).where(eq(metrics.id, input.metricId!));
|
|
||||||
|
|
||||||
if (!metric[0]) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Metric not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return metric[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const measurementsAppRouter = router({
|
export const measurementsAppRouter = router({
|
||||||
list: authedProcedure
|
list: authedProcedure
|
||||||
@ -42,23 +25,19 @@ export const measurementsAppRouter = router({
|
|||||||
return dbMeasurements;
|
return dbMeasurements;
|
||||||
}),
|
}),
|
||||||
setValue: authedProcedure
|
setValue: authedProcedure
|
||||||
.input(z.object({
|
.input(z.object({ metricId: z.string(), hourId: z.string(), dayId: z.string(), value: z.number() }))
|
||||||
metricId: z.string().nullish(), metricName: z.string().nullish(),
|
|
||||||
hourId: z.string(), dayId: z.string(), value: z.number()
|
|
||||||
}))
|
|
||||||
.output(zMeasurementSchema)
|
.output(zMeasurementSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
if (!input.metricId && !input.metricName) {
|
const metric = await ctx.db.select().from(metrics).where(eq(metrics.id, input.metricId));
|
||||||
|
if (!metric[0]) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "NOT_FOUND",
|
||||||
message: "Metric name or id is required",
|
message: "Metric not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const metric = await getMetricFromInput(ctx, input);
|
|
||||||
|
|
||||||
// Check if there is a measurement for this metric in this hour, if so, update it, if not, create it
|
// Check if there is a measurement for this metric in this hour, if so, update it, if not, create it
|
||||||
const existingMeasurement = await ctx.db.select().from(measurements).where(and(
|
const existingMeasurement = await ctx.db.select().from(measurements).where(and(
|
||||||
eq(measurements.metricId, metric.id),
|
eq(measurements.metricId, input.metricId),
|
||||||
eq(measurements.hourId, input.hourId),
|
eq(measurements.hourId, input.hourId),
|
||||||
));
|
));
|
||||||
if (existingMeasurement[0]) {
|
if (existingMeasurement[0]) {
|
||||||
@ -68,12 +47,12 @@ export const measurementsAppRouter = router({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedMeasurement[0],
|
...updatedMeasurement[0],
|
||||||
icon: metric.icon,
|
icon: metric[0].icon,
|
||||||
metricName: metric.name,
|
metricName: metric[0].name,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const newMeasurement = await ctx.db.insert(measurements).values({
|
const newMeasurement = await ctx.db.insert(measurements).values({
|
||||||
metricId: metric.id,
|
metricId: input.metricId,
|
||||||
hourId: input.hourId,
|
hourId: input.hourId,
|
||||||
dayId: input.dayId,
|
dayId: input.dayId,
|
||||||
value: input.value.toString(),
|
value: input.value.toString(),
|
||||||
@ -81,16 +60,16 @@ export const measurementsAppRouter = router({
|
|||||||
}).returning();
|
}).returning();
|
||||||
return {
|
return {
|
||||||
...newMeasurement[0],
|
...newMeasurement[0],
|
||||||
icon: metric.icon,
|
icon: metric[0].icon,
|
||||||
metricName: metric.name,
|
metricName: metric[0].name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
incrementCount: authedProcedure
|
incrementCount: authedProcedure
|
||||||
.input(z.object({ metricId: z.string().nullish(), metricName: z.string().nullish(), hourId: z.string(), dayId: z.string() }))
|
.input(z.object({ metricId: z.string(), hourId: z.string(), dayId: z.string() }))
|
||||||
.output(zMeasurementSchema)
|
.output(zMeasurementSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const metric = await getMetricFromInput(ctx, input);
|
const metric = await ctx.db.select().from(metrics).where(eq(metrics.id, input.metricId));
|
||||||
if (!metric[0]) {
|
if (!metric[0]) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
@ -165,20 +144,4 @@ export const measurementsAppRouter = router({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
timeSinceLastMeasurement: authedProcedure
|
|
||||||
.input(z.object({ metricId: z.string() }))
|
|
||||||
.output(z.number())
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const lastMeasurement = await ctx.db.select().from(measurements).where(and(
|
|
||||||
eq(measurements.metricId, input.metricId),
|
|
||||||
eq(measurements.userId, ctx.user.id),
|
|
||||||
)).orderBy(desc(measurements.createdAt)).limit(1);
|
|
||||||
if (lastMeasurement[0]) {
|
|
||||||
const lastMeasurementTime = new Date(lastMeasurement[0].createdAt).getTime();
|
|
||||||
const currentTime = new Date().getTime();
|
|
||||||
return (currentTime - lastMeasurementTime) / 1000;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
@ -8,7 +8,6 @@ import { zMetricSchema, zMeasurementSchema } from "@lifetracker/shared/types/met
|
|||||||
import type { Context } from "../index";
|
import type { Context } from "../index";
|
||||||
import { authedProcedure, router } from "../index";
|
import { authedProcedure, router } from "../index";
|
||||||
import { zColorSchema } from "@lifetracker/shared/types/colors";
|
import { zColorSchema } from "@lifetracker/shared/types/colors";
|
||||||
import { titleCase } from "title-case";
|
|
||||||
|
|
||||||
export const metricsAppRouter = router({
|
export const metricsAppRouter = router({
|
||||||
list: authedProcedure
|
list: authedProcedure
|
||||||
@ -30,7 +29,7 @@ export const metricsAppRouter = router({
|
|||||||
const result = await trx
|
const result = await trx
|
||||||
.insert(metrics)
|
.insert(metrics)
|
||||||
.values({
|
.values({
|
||||||
name: titleCase(input.name),
|
name: input.name,
|
||||||
userId: ctx.user!.id,
|
userId: ctx.user!.id,
|
||||||
unit: input.unit ?? null,
|
unit: input.unit ?? null,
|
||||||
type: input.type,
|
type: input.type,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user