Category creation logic

This commit is contained in:
ryan 2025-02-06 11:00:33 -08:00
parent 444e20f5b1
commit 899ea04954
5 changed files with 69 additions and 24 deletions

View File

@ -2,6 +2,22 @@
import { LineChart, Line, YAxis, XAxis, CartesianGrid, Tooltip, Area, Label } from 'recharts';
import spacetime from 'spacetime';
import regression from 'regression';
import { PureComponent } from 'react';
class CustomizedAxisTick extends PureComponent {
render() {
const { x, y, stroke, payload } = this.props;
if (spacetime(payload.value).day() == 1) {
return (
<g transform={`translate(${x},${y})`}>
<text x={0} y={0} dy={16} textAnchor="end" fill="#666" transform="rotate(-35)">
{spacetime(payload.value).format('{date}')}
</text>
</g>
);
}
}
}
interface DataPoint {
id: string; // measurement.id
@ -22,12 +38,8 @@ function polynomialTrendline(data: DataPoint[], degree: number) {
}
function formatXAxis(tickItem: [number, number]) {
const month = spacetime(tickItem).format('{month-short}');
if (spacetime(tickItem).day() === 1) {
return spacetime(tickItem).format('{month-short} {date}');
} else {
return spacetime(tickItem).format('{date}');
}
return spacetime(tickItem).format('{date}');
}
function yAxisTicks(data: DataPoint[]) {
@ -38,6 +50,19 @@ function yAxisTicks(data: DataPoint[]) {
return Array.from({ length: (upperBound - lowerBound) / 2 + 1 }, (_, i) => lowerBound + 2 * i);
}
function xAxisTicks(data: DataPoint[]) {
const bounds = data.sort().filter((_v, i, a) => i === 0 || i === a.length - 1).map(dp => spacetime(dp.datetime));
// Create array from bounds[0] to bounds[1] showing only Mondays
const ticks = [];
let tick = bounds[0].startOf('week').add(1, 'day');
while (tick.isBefore(bounds[1])) {
ticks.push(tick.format('{date}'));
tick = tick.add(2, 'day');
}
console.log(ticks);
return ticks;
}
export default function TimeseriesChart({ data, timeseriesName }: { data: DataPoint[], timeseriesName: string }) {
const unit = data[0] ? data[0].unit : '';
@ -63,8 +88,18 @@ export default function TimeseriesChart({ data, timeseriesName }: { data: DataPo
formatter={(value, name, props) => [value, name === 'smoothed' ? 'Trend' : timeseriesName]}
contentStyle={{ backgroundColor: 'hsl(var(--border))', color: 'black' }}
/>
<XAxis dataKey="datetime" tickFormatter={formatXAxis} tickMargin={10} tickSize={10} />
<YAxis unit={` ${unit}`} width={120} domain={yAxisTicks(data).filter((v, i) => i === 0 || i === yAxisTicks(data).length - 1)}
<XAxis
dataKey="datetime"
type='number'
height={80}
tickMargin={10} tickSize={10}
domain={['dataMin', 'dataMax']}
tick={<CustomizedAxisTick />}
interval={0}
// tickFormatter={formatXAxis}
/>
<YAxis unit={` ${unit}`} width={120}
domain={yAxisTicks(data).filter((v, i) => i === 0 || i === yAxisTicks(data).length - 1)}
allowDecimals={false}
ticks={yAxisTicks(data)} />
<Line type="natural" dataKey="value" stroke="white" activeDot={{ stroke: 'pink', strokeWidth: 2, r: 5 }} dot={{ fill: 'red', stroke: 'red', strokeWidth: 1 }} />

View File

@ -33,10 +33,10 @@ import { TRPCClientError } from "@trpc/client";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zCategorySchema } from "@lifetracker/shared/types/categories";
import { zCategorySchema, zCreateCategorySchema } from "@lifetracker/shared/types/categories";
import { Textarea } from "@/components/ui/textarea";
type CreateCategorySchema = z.infer<typeof zCategorySchema>;
type CreateCategorySchema = z.infer<typeof zCreateCategorySchema>;
export default function AddCategoryDialog({
children,
@ -46,9 +46,9 @@ export default function AddCategoryDialog({
const apiUtils = api.useUtils();
const [isOpen, onOpenChange] = useState(false);
const form = useForm<CreateCategorySchema>({
resolver: zodResolver(zCategorySchema),
resolver: zodResolver(zCreateCategorySchema),
});
const { mutate, isPending } = api.categories.createCategory.useMutation({
const { mutate: createCategory, isPending } = api.categories.create.useMutation({
onSuccess: () => {
toast({
description: "Category created successfully",
@ -84,7 +84,7 @@ export default function AddCategoryDialog({
<DialogTitle>Create Category</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit((val) => mutate(val))}>
<form onSubmit={form.handleSubmit((val) => createCategory(val))}>
<div className="flex w-full flex-col space-y-2">
<div style={{ display: "grid", gridTemplateColumns: "5em 1fr 75px", gap: "10px" }}>
<FormField
@ -125,7 +125,7 @@ export default function AddCategoryDialog({
/>
<FormField
control={form.control}
name="color"
name="colorName"
render={({ field }) => (
<FormItem>
{/* <FormLabel>Color</FormLabel> */}

View File

@ -14,7 +14,7 @@ export const zCreateCategorySchema = z.object({
code: z.coerce.number(),
name: z.string(),
description: z.string().optional(),
color: z.string(),
colorName: z.string(),
parentId: z.string().optional(),
});
export type ZCreateCategories = z.infer<typeof zCreateCategorySchema>;

View File

@ -3,7 +3,7 @@ import { z } from "zod";
export const zColorSchema = z.object({
name: z.string(),
hexcode: z.string(),
inverse: z.string().optional(),
id: z.string().optional(),
inverse: z.string().nullish(),
id: z.string().nullish(),
});
export type ZColor = z.infer<typeof zColorSchema>;

View File

@ -1,5 +1,5 @@
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { and, asc, desc, eq, inArray, notExists } from "drizzle-orm";
import { z } from "zod";
import { DatabaseError } from "@lifetracker/db";
@ -102,11 +102,15 @@ export const categoriesAppRouter = router({
.leftJoin(colors, eq(categories.colorId, colors.id))
;
const categoryParents = dbCategories
.filter((category) => category.code == parseInt(category.code) && category.code < 11)
.sort((a, b) => a.code - b.code);
const sortedCategories = categoryParents.map((parent) => {
return dbCategories
.filter((child) => child.code.toString().startsWith(parent.code.toString()))
});
return {
categories: dbCategories.map(({ color, ...category }) => ({
...category,
color,
})),
categories: sortedCategories.flat(),
};
}),
get: authedProcedure
@ -173,7 +177,7 @@ export const categoriesAppRouter = router({
return category;
}),
create: authedProcedure
.input(zCategorySchema)
.input(zCreateCategorySchema)
.output(
z.object({
id: z.string(),
@ -184,7 +188,13 @@ export const categoriesAppRouter = router({
}),
)
.mutation(async ({ input, ctx }) => {
return createCategory(input, ctx);
const color = await ctx.db.select().from(colors).where(
and(
eq(colors.name, input.colorName),
eq(colors.userId, ctx.user.id),
)
);
return createCategory({ ...input, color: color[0] }, ctx);
}),
update: authedProcedure
.input(zUpdateCategoryRequestSchema)