Category creation logic
This commit is contained in:
parent
444e20f5b1
commit
899ea04954
@ -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 }} />
|
||||
|
||||
@ -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> */}
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user