Maybe have frontend web working with codes at least.

This commit is contained in:
Ryan Pandya 2024-12-03 19:17:15 -08:00
parent a46a8a9399
commit 5625e9a6fd
15 changed files with 362 additions and 245 deletions

View File

@ -2,22 +2,19 @@ import { notFound } from "next/navigation";
import { api } from "@/server/api/client"; import { api } from "@/server/api/client";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import DayView from "@/components/dashboard/days/DayView"; import DayView from "@/components/dashboard/days/DayView";
import spacetime from "spacetime"; import LoadingSpinner from "@/components/ui/spinner";
import { useTimezone } from "@lifetracker/shared-react/hooks/timezones";
import { date } from "drizzle-orm/pg-core";
export default async function DayPage({ params }: { params: { dateQuery: string }; }) { export default async function DayPage({ params }: { params: { dateQuery: string }; }) {
const { dateQuery } = await params;
let day; let day;
const tzName = await api.users.getTimezone(); const { dateQuery } = await params;
// console.log(`(dashboard/day/[dateQuery]/page.tsx) Loading ${spacetime.now(tzName).format("yyyy-MM-dd")} in ${tzName}.`); const timezone = await api.users.getTimezone();
try { try {
day = await api.days.get({ day = await api.days.get({
dateQuery: spacetime(dateQuery, tzName).format("yyyy-MM-dd"), dateQuery: dateQuery,
timezone: tzName, timezone: timezone,
}); });
} catch (e) { } catch (e) {
console.log("DATEQ", params);
if (e instanceof TRPCError) { if (e instanceof TRPCError) {
if (e.code == "NOT_FOUND") { if (e.code == "NOT_FOUND") {
notFound(); notFound();
@ -28,9 +25,13 @@ export default async function DayPage({ params }: { params: { dateQuery: string
return ( return (
<> <>
<DayView {
day={day} day == undefined ?
/> <LoadingSpinner /> :
<DayView
day={day}
/>
}
</> </>
); );
} }

View File

@ -1,17 +1,12 @@
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { api } from "@/server/api/client";
import { getServerAuthSession } from "@/server/auth"; import { getServerAuthSession } from "@/server/auth";
import { ZDay } from "@lifetracker/shared/types/days"; import { ZDay } from "@lifetracker/shared/types/days";
import EditableDayComment from "./EditableDayComment"; import EditableDayComment from "./EditableDayComment";
import { MoodStars } from "./MoodStars"; import { MoodStars } from "./MoodStars";
import { format, addDays } from "date-fns";
import { ButtonWithTooltip } from "@/components/ui/button";
import { router } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ArrowLeftSquare, ArrowRightSquare } from "lucide-react"; import { ArrowLeftSquare, ArrowRightSquare } from "lucide-react";
import { UTCDate, utc } from "@date-fns/utc";
import spacetime from "spacetime"; import spacetime from "spacetime";
import EditableHour from "@/components/dashboard/hours/EditableHour"; import EditableHour from "@/components/dashboard/hours/EditableHour";
@ -31,7 +26,7 @@ export default async function DayView({
return ( return (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div className="flex justify-between"> <div className="flex justify-between pr-4">
<div className="flex"> <div className="flex">
<Link <Link
href={`/dashboard/day/${prevDay}`} href={`/dashboard/day/${prevDay}`}
@ -57,7 +52,7 @@ export default async function DayView({
</div> </div>
</Link> </Link>
</div> </div>
<div> <div className="flex items-center">
<MoodStars <MoodStars
day={day} day={day}
/> />
@ -65,16 +60,17 @@ export default async function DayView({
</div> </div>
<Separator /> <Separator />
<div className="pl-4">
<EditableDayComment day={day} <EditableDayComment day={day}
className="text-xl" className="text-xl"
/> />
</div>
<Separator /> <Separator />
<ul> <ul>
<li> <li>
<div className={cn( {/* <div className={cn(
"p-4 grid justify-between", "p-4 grid justify-between",
)} )}
style={{ style={{
@ -91,11 +87,11 @@ export default async function DayView({
<div className="text-right"> <div className="text-right">
Actions Actions
</div> </div>
</div> </div> */}
</li> </li>
{day.hours.map((hour) => ( {day.hours.map((hour, i) => (
<li key={hour.time} id={"hour-" + hour.time.toString()}> <li key={hour.time} id={"hour-" + i.toString()}>
<EditableHour hour={hour} /> <EditableHour hour={hour} i={i} />
</li> </li>
))} ))}
</ul> </ul>

View File

@ -3,131 +3,69 @@
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { toast } from "@/components/ui/use-toast"; import { toast } from "@/components/ui/use-toast";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import React, { useState, useEffect, useRef } from 'react';
import { useUpdateHour } from "@lifetracker/shared-react/hooks/days"; import { useUpdateHour } from "@lifetracker/shared-react/hooks/days";
import { EditableText } from "@/components/dashboard/EditableText"; import { EditableText } from "@/components/dashboard/EditableText";
import { format } from "date-fns"; import { format } from "date-fns";
import { TZDate } from "@date-fns/tz"; import { TZDate } from "@date-fns/tz";
import { ZHour } from "@lifetracker/shared/types/days"; import { ZHour } from "@lifetracker/shared/types/days";
import { useEffect, useRef } from "react"; import { MessageCircle } from "lucide-react";
import { ButtonWithTooltip } from "@/components/ui/button";
function selectNext(time: number) { import { EditableHourCode } from "./EditableHourCode";
document.getElementById("hour-" + (time).toString())?.getElementsByClassName("edit-hour-code")[0].focus(); import { EditableHourComment } from "./EditableHourComment";
} import { api } from "@/lib/trpc";
import spacetime from 'spacetime';
function EditCode({ import { eq } from "drizzle-orm";
originalText,
hour,
onSubmit
}) {
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (ref.current) {
ref.current.value = originalText;
}
}, [ref]);
const submit = () => {
let newCode: string | null = ref.current?.value ?? null;
if (originalText == newCode) {
// Nothing to do here
return;
}
if (newCode == "") {
if (originalText == null) {
// Set value to previous hour's value
newCode = document.getElementById("hour-" + (hour.time - 1).toString())?.getElementsByClassName("edit-hour-code")[0].value;
console.log(newCode);
}
else {
newCode = null;
}
}
// console.log(hour);
onSubmit({
dateQuery: hour.date,
time: hour.time,
code: newCode,
})
selectNext(hour.time + 1);
};
return (
<input
className="w-8 border-b-2 text-center edit-hour-code"
style={{
background: hour.background, color: hour.foreground, fontFamily: "inherit",
}}
ref={ref}
defaultValue={originalText}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
submit();
}
if (e.key == "ArrowDown") {
e.preventDefault();
selectNext(hour.time + 1);
}
if (e.key == "ArrowUp") {
e.preventDefault();
selectNext(hour.time - 1);
}
}}
onClick={(e) => {
const range = document.createRange();
range.selectNodeContents(ref.current);
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
}
}}
/>
// <input type="text"
// className="w-10 bg-inherit border-b-2 text-center"
// style={{ color: "inherit", borderColor: "inherit" }}
// onKeyDown={(e) => {
// if (e.key === "Enter") {
// e.preventDefault();
// onSave();
// }
// }}
// value={originalText.originalText ?? ""}
// />
);
}
export default function EditableHour({ export default function EditableHour({
hour, hour: initialHour,
i,
className, className,
}: { }: {
hour: ZHour, hour: ZHour,
i: number,
className?: string; className?: string;
}) { }) {
const router = useRouter(); const router = useRouter();
const currentPath = usePathname(); const currentPath = usePathname();
const [hour, setHour] = useState(initialHour);
const { mutate: updateHour, isPending } = useUpdateHour({ const { mutate: updateHour, isPending } = useUpdateHour({
onSuccess: () => { onSuccess: (res, req, meta) => {
const { categoryCode: oldCode, comment: oldComment } = hour;
const newHour = {
categoryCode: req.code,
comment: oldComment,
...res,
};
console.log(res);
setHour(newHour);
toast({ toast({
description: "Hour updated!", description: "Hour updated!",
}); });
if (currentPath.includes("dashboard")) {
router.refresh();
}
}, },
}); });
const tzOffset = spacetime().offset() / 60;
const localDateTime = spacetime(hour.date).add(hour.time + tzOffset, "hour");
hour.datetime = `${localDateTime.format('{hour} {ampm}')}`;
useEffect(() => {
// console.log(hour.categoryDesc);
}, [hour]);
function isActiveHour(hour: ZHour) { function isActiveHour(hour: ZHour) {
const now = new TZDate(); const now = new TZDate();
return (hour.date == format(now, "yyyy-MM-dd")) && (((now.getHours()) + (now.getTimezoneOffset() / 60) - (parseInt(hour.time))) == 0) return (hour.date == format(now, "yyyy-MM-dd")) && (((now.getHours()) + (now.getTimezoneOffset() / 60) - (parseInt(hour.time))) == 0)
} }
return ( return (
<div className={cn( <div
"p-4 grid justify-between", hourid={hour.id}
)} className={cn(
"p-4 grid justify-between",
)}
style={{ style={{
background: hour.background, color: hour.foreground, fontFamily: "inherit", background: hour.background, color: hour.foreground, fontFamily: "inherit",
gridTemplateColumns: "100px 100px 1fr 200px" gridTemplateColumns: "100px 100px 1fr 200px"
@ -138,20 +76,34 @@ export default function EditableHour({
{hour.datetime} {hour.datetime}
</span> </span>
<div className="flex justify-center"> <div className="flex justify-center">
<EditCode <EditableHourCode
originalText={hour.categoryCode} originalText={hour.categoryCode}
hour={hour} hour={hour}
onSubmit={updateHour} onSubmit={updateHour}
i={i}
/> />
</div> </div>
<span> <span>
{hour.categoryDesc || " "} <EditableHourComment
originalText={hour.categoryDesc}
hour={hour}
onSubmit={updateHour}
i={i}
/>
</span> </span>
<div className="text-right"> <div className="text-right">
<EditableText <ButtonWithTooltip
originalText={hour.comment || " "} delayDuration={500}
viewClassName="text-right" tooltip="Add Comment"
/> size="none"
variant="ghost"
className="align-middle text-gray-400"
onClick={() => {
console.log("Pushed edit")
}}
>
<MessageCircle className="size-4" />
</ButtonWithTooltip>
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,102 @@
"use client";
import { or } from "drizzle-orm";
import { useEffect, useRef } from "react";
function selectNext(time: number) {
document.getElementById("hour-" + (time).toString())?.getElementsByClassName("edit-hour-code")[0].focus();
}
export function EditableHourCode({
originalText,
hour,
onSubmit,
i
}) {
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (ref.current) {
ref.current.value = originalText;
}
}, [ref]);
const submit = () => {
let newCode: string | null = ref.current?.value ?? null;
console.log(`Original ${originalText}, new ${newCode}`);
if (originalText === newCode) {
// Nothing to do here
console.log("Skipping.");
selectNext(i + 1);
return;
}
if (newCode == "") {
if (originalText == null) {
// Set value to previous hour's value
newCode = document.getElementById("hour-" + (i - 1).toString())?.getElementsByClassName("edit-hour-code")[0].value;
ref.current.value = newCode;
}
else {
newCode = null;
}
}
onSubmit({
date: hour.date,
hourTime: hour.time,
dayId: hour.dayId,
code: newCode ?? "",
comment: hour.comment,
})
selectNext(i + 1);
};
return (
<>
{/* The below is a gross hack because I don't understand why including it makes the component refresh, but without it, it won't update */}
<div className="hidden">
{originalText}
</div>
<input
className="w-8 border-b-2 text-center edit-hour-code"
style={{
background: hour.background ?? "inherit", color: hour.foreground ?? "inherit", fontFamily: "inherit",
}}
ref={ref}
defaultValue={originalText}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
submit();
}
if (e.key == "ArrowDown") {
e.preventDefault();
selectNext(i + 1);
}
if (e.key == "ArrowUp") {
e.preventDefault();
selectNext(i - 1);
}
}}
onClick={(e) => {
e.target.select();
}}
onFocus={(e) => {
e.target.select();
}}
/></>
// <input type="text"
// className="w-10 bg-inherit border-b-2 text-center"
// style={{ color: "inherit", borderColor: "inherit" }}
// onKeyDown={(e) => {
// if (e.key === "Enter") {
// e.preventDefault();
// onSave();
// }
// }}
// value={originalText.originalText ?? ""}
// />
);
}

View File

@ -0,0 +1,83 @@
"use client";
import { or } from "drizzle-orm";
import { useEffect, useRef } from "react";
function selectNext(time: number) {
console.log(time);
document.getElementById("hour-" + (time).toString())?.getElementsByClassName("edit-hour-comment")[0].focus();
}
export function EditableHourComment({
originalText,
hour,
onSubmit,
i
}) {
// console.log(`Hello from ${hour.time}, where the categoryDesc is ${hour.categoryDesc}`);
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (ref.current) {
ref.current.value = originalText;
}
}, [ref]);
const submit = () => {
let newComment: string | null = ref.current?.value ?? null;
if (originalText == newComment) {
// Nothing to do here
selectNext(hour.time + 1);
return;
}
if (newComment == "") {
if (originalText == null) {
// Set value to previous hour's value
newComment = document.getElementById("hour-" + (i - 1).toString())?.getElementsByClassName("edit-hour-comment")[0].value;
}
else {
newComment = null;
}
}
onSubmit({
date: hour.date,
hourTime: hour.time,
dayId: hour.dayId,
comment: newComment,
code: hour.categoryCode.toString(),
})
selectNext(hour.time + 1);
};
return (
<input
className="w-full border-b-2 text-left edit-hour-comment"
style={{
background: hour.background ?? "inherit", color: hour.foreground ?? "inherit", fontFamily: "inherit",
}}
ref={ref}
value={originalText ?? ""}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
submit();
}
if (e.key == "ArrowDown") {
e.preventDefault();
selectNext(i + 1);
}
if (e.key == "ArrowUp") {
e.preventDefault();
selectNext(i - 1);
}
}}
onClick={(e) => {
e.target.select();
}}
onFocus={(e) => {
e.target.select();
}}
/>
);
}

View File

@ -67,7 +67,7 @@ CREATE TABLE `hour` (
`createdAt` integer NOT NULL, `createdAt` integer NOT NULL,
`userId` text NOT NULL, `userId` text NOT NULL,
`comment` text, `comment` text,
`time` integer, `time` integer NOT NULL,
`dayId` text NOT NULL, `dayId` text NOT NULL,
`categoryId` text, `categoryId` text,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade, FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,

View File

@ -1,7 +1,7 @@
{ {
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"id": "ebffb4c7-5ecf-46d0-93c6-68f8e48a9fc4", "id": "a91a2a0e-7727-4187-8a75-c96d8e304f27",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"tables": { "tables": {
"account": { "account": {
@ -505,7 +505,7 @@
"name": "time", "name": "time",
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"dayId": { "dayId": {

View File

@ -5,8 +5,8 @@
{ {
"idx": 0, "idx": 0,
"version": "6", "version": "6",
"when": 1732766704666, "when": 1733261637429,
"tag": "0000_cold_golden_guardian", "tag": "0000_sad_lionheart",
"breakpoints": true "breakpoints": true
} }
] ]

View File

@ -4,7 +4,6 @@ export function useUpdateDay(
...opts: Parameters<typeof api.days.update.useMutation> ...opts: Parameters<typeof api.days.update.useMutation>
) { ) {
const apiUtils = api.useUtils(); const apiUtils = api.useUtils();
// console.log("UPDATING DAY");
return api.days.update.useMutation({ return api.days.update.useMutation({
...opts[0], ...opts[0],
onSuccess: (res, req, meta) => { onSuccess: (res, req, meta) => {
@ -18,7 +17,6 @@ export function useUpdateHour(
...opts: Parameters<typeof api.hours.update.useMutation> ...opts: Parameters<typeof api.hours.update.useMutation>
) { ) {
const apiUtils = api.useUtils(); const apiUtils = api.useUtils();
// console.log(opts[0]);
return api.hours.update.useMutation({ return api.hours.update.useMutation({
...opts[0], ...opts[0],
onSuccess: (res, req, meta) => { onSuccess: (res, req, meta) => {

View File

@ -13,6 +13,5 @@ export function useUpdateUserTimezone(
export function useTimezone() { export function useTimezone() {
const res = api.users.getTimezone.useQuery(); const res = api.users.getTimezone.useQuery();
console.log("react hook useTimezone", res);
return res; return res;
} }

View File

@ -4,19 +4,28 @@ import { UTCDate, utc } from "@date-fns/utc";
import spacetime from "spacetime"; import spacetime from "spacetime";
export function dateFromInput(input: { dateQuery: string, timezone: string }) { export function dateFromInput(input: { dateQuery: string, timezone: string }) {
console.log(`Looking for ${input.dateQuery} in ${input.timezone}`);
let t: string; let t: string;
if (input.dateQuery == "today") { try {
t = spacetime(input.dateQuery, input.timezone).format("yyyy-MM-dd"); t = format(new UTCDate(input.dateQuery), "yyyy-MM-dd", { in: utc });
return t;
} }
else { catch (e) {
t = new UTCDate(input.dateQuery); const now_here = spacetime.now(input.timezone);
return format(t, "yyyy-MM-dd", { in: utc }); switch (input.dateQuery) {
case "today":
t = now_here.format("yyyy-MM-dd");
break;
case "yesterday":
t = now_here.subtract(1, "day").format("yyyy-MM-dd");
break;
case "tomorrow":
t = now_here.add(1, "day").format("yyyy-MM-dd");
break;
default:
throw new Error("Invalid dateQuery");
}
} }
console.log(`dateFromInput(${input.dateQuery}, ${input.timezone}) = ${t}`);
return t;
} }
function generateHour(d, t) { function generateHour(d, t) {

View File

@ -37,7 +37,6 @@ async function createColor(
}); });
return result[0]; return result[0];
} catch (e) { } catch (e) {
console.log(e);
if (e instanceof SqliteError) { if (e instanceof SqliteError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { if (e.code == "SQLITE_CONSTRAINT_UNIQUE") {
throw new TRPCError({ throw new TRPCError({

View File

@ -12,10 +12,9 @@ import { authedProcedure, router } from "../index";
import { dateFromInput, hoursListInUTC } from "@lifetracker/shared/utils/days"; import { dateFromInput, hoursListInUTC } from "@lifetracker/shared/utils/days";
import { closestIndexTo, format } from "date-fns"; import { closestIndexTo, format } from "date-fns";
import { TZDate } from "@date-fns/tz"; import { TZDate } from "@date-fns/tz";
import { hoursAppRouter } from "./hours"; import { hoursAppRouter, hourColors, hourJoinsQuery } from "./hours";
import spacetime from "spacetime"; import spacetime from "spacetime";
import { getHourFromTime, getTimeFromHour } from "@lifetracker/shared/utils/hours"; import { getHourFromTime, getTimeFromHour } from "@lifetracker/shared/utils/hours";
import { hourColors } from "./hours";
async function createDay(date: string, ctx: Context) { async function createDay(date: string, ctx: Context) {
return await ctx.db.transaction(async (trx) => { return await ctx.db.transaction(async (trx) => {
@ -52,13 +51,12 @@ async function createDay(date: string, ctx: Context) {
}); });
} }
async function createHour(day, time, ctx,) { async function createHour(day: ZDay, time: number, ctx: Context,) {
const newHour = (await ctx.db.insert(hours).values({ const newHour = (await ctx.db.insert(hours).values({
dayId: day.id, dayId: day.id,
time: time, time: time,
userId: ctx.user!.id, userId: ctx.user!.id,
}).returning()); }).returning());
console.log(newHour);
return newHour[0]; return newHour[0];
} }
@ -115,21 +113,27 @@ export const daysAppRouter = router({
})) }))
.output(zDaySchema) .output(zDaySchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
// Get a Day
// Use timezone and date string to get the local date
const timezone = input.timezone ?? await getTimezone(ctx); const timezone = input.timezone ?? await getTimezone(ctx);
const date = dateFromInput({ const date = dateFromInput({
dateQuery: input.dateQuery, dateQuery: input.dateQuery,
timezone: timezone timezone: timezone
}); });
const allHours = hoursListInUTC({ // Get the list of UTC hours corresponding to this day
const utcHours = hoursListInUTC({
timezone, timezone,
...input ...input
}); });
const dayRange = [...new Set(allHours.map(({ date: date, time: _time }) => date))]; // console.log(`utcHours:\n,${utcHours.map(({ date, time }) => `${date} ${time}\n`)}`);
const allDayIds = await Promise.all(dayRange.map(async function (date) { // Flatten the 24 hours to the 2 unique days
const uniqueDays = [...new Set(utcHours.map(({ date: date, time: _time }) => date))];
// ...and get their IDs
const uniqueDayIds = await Promise.all(uniqueDays.map(async function (date) {
const dayObj = await getDay(input, ctx, date); const dayObj = await getDay(input, ctx, date);
return { return {
id: dayObj.id, id: dayObj.id,
@ -137,49 +141,13 @@ export const daysAppRouter = router({
} }
})); }));
const dayHours = await Promise.all(allHours.map(async function (map: { date, time }, i) { // Finally, use the two unique day IDs and the 24 hours to get the actual Hour objects for each day
const dayId = allDayIds.find((dayIds: { id, date }) => map.date == dayIds.date)!.id; const dayHours = await Promise.all(utcHours.map(async function (map: { date: string, time: number }, i) {
const dayId = uniqueDayIds.find((dayIds: { id: string, date: string }) => map.date == dayIds.date)!.id;
const hourMatch = await ctx.db.select({ return hourJoinsQuery(ctx, dayId, map.time);
id: hours.id,
dayId: hours.dayId,
time: hours.time,
categoryId: hours.categoryId,
categoryCode: categories.code,
categoryName: categories.name,
categoryDesc: categories.description,
comment: hours.comment,
date: days.date
}).from(hours)
.leftJoin(categories, eq(categories.id, hours.categoryId))
.leftJoin(days, eq(days.id, hours.dayId))
.where(and(
eq(hours.time, map.time),
eq(hours.dayId, dayId)));
// console.log({
// ...allHours,
// dayId: dayId
// });
// console.log("Search values:: ", `allDayIds: ${allDayIds}, d: ${date}, t: ${time}, dayId: ${dayId}`)
// console.log("hourMatch", hourMatch[0]);
// console.log(hourMatch[0].categoryDesc);
const dayHour = {
...hourMatch[0],
...(await hourColors(hourMatch[0], ctx)),
};
const localDateTime = spacetime(date, timezone).add(i, "hour");
return {
...dayHour,
datetime: `${localDateTime.format('{hour} {ampm}')}`,
// datetime: `${localDateTime.format('{nice}')} ${timezone} (${localDateTime.goto("UTC").format('{nice}')} UTC)`,
};
})); }));
// console.log(dayHours.flat());
return { return {
...await getDay(input, ctx, date), ...await getDay(input, ctx, date),
@ -206,6 +174,6 @@ export const daysAppRouter = router({
await ctx.db await ctx.db
.update(days) .update(days)
.set(updatedProps) .set(updatedProps)
.where(eq(days.date, dateFromInput({ dateQuery: dateQuery, timezone: timezone ?? ctx.user.timezone }))); .where(eq(days.date, utcDateFromInput({ dateQuery: dateQuery, timezone: timezone ?? ctx.user.timezone })));
}), }),
}); });

View File

@ -12,10 +12,9 @@ import { authedProcedure, router } from "../index";
import { format } from "date-fns"; import { format } from "date-fns";
import { TZDate } from "@date-fns/tz"; import { TZDate } from "@date-fns/tz";
import { dateFromInput } from "@lifetracker/shared/utils/days"; import { dateFromInput } from "@lifetracker/shared/utils/days";
import { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
export async function hourColors(hour: ZHour, ctx: Context) { export async function hourColors(hour: ZHour, ctx: Context) {
const categoryColor = await ctx.db.select() const categoryColor = await ctx.db.select()
.from(colors) .from(colors)
.leftJoin(categories, eq(categories.id, hour.categoryId)) .leftJoin(categories, eq(categories.id, hour.categoryId))
@ -24,7 +23,6 @@ export async function hourColors(hour: ZHour, ctx: Context) {
eq(colors.userId, ctx.user!.id) eq(colors.userId, ctx.user!.id)
)) ))
// console.log(categoryColor);
if (!categoryColor[0]) { if (!categoryColor[0]) {
return { return {
background: "inherit", background: "inherit",
@ -39,6 +37,39 @@ export async function hourColors(hour: ZHour, ctx: Context) {
} }
} }
export async function hourJoinsQuery(
ctx: Context,
dayId: string,
time: number,
) {
const hourMatch = await ctx.db.select({
id: hours.id,
dayId: hours.dayId,
time: hours.time,
categoryId: hours.categoryId,
categoryCode: categories.code,
categoryName: categories.name,
categoryDesc: categories.description,
comment: hours.comment,
date: days.date,
}).from(hours)
.leftJoin(categories, eq(categories.id, hours.categoryId))
.leftJoin(days, eq(days.id, hours.dayId))
.where(and(
eq(hours.time, time),
eq(hours.dayId, dayId)
));
const dayHour = {
...hourMatch[0],
...(await hourColors(hourMatch[0], ctx)),
};
return dayHour;
};
export const hoursAppRouter = router({ export const hoursAppRouter = router({
get: authedProcedure get: authedProcedure
.input(z.object({ .input(z.object({
@ -47,22 +78,11 @@ export const hoursAppRouter = router({
})) }))
.output(zHourSchema) .output(zHourSchema)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {
const date = dateFromInput(input); const date = dateFromInput({ dateQuery: input.dateQuery });
const hourRes = await ctx.db const hourRes = await getHourSelectQuery(ctx, date, input.time,
.select({ and(eq(hours.time, input.time), eq(days.date, date))
id: hours.id, );
dayId: hours.dayId,
time: hours.time,
categoryId: hours.categoryId,
categoryCode: categories.code,
categoryName: categories.name,
categoryDesc: categories.description,
comment: hours.comment,
})
.from(hours)
.leftJoin(days, eq(days.id, hours.dayId)) // Ensure days table is joined first
.leftJoin(categories, eq(categories.id, hours.categoryId))
.where(and(eq(hours.time, input.time), eq(days.date, date))) // Use correct alias for days table
return { return {
date: format(date, "yyyy-MM-dd"), date: format(date, "yyyy-MM-dd"),
...hourRes[0] ...hourRes[0]
@ -71,23 +91,23 @@ export const hoursAppRouter = router({
update: authedProcedure update: authedProcedure
.input( .input(
z.object({ z.object({
hourId: z.string().optional(), date: z.string(),
dayId: z.string().optional(), hourTime: z.number(),
dateQuery: z.string(), dayId: z.string(),
time: z.number(), code: z.string().nullish(),
code: z.string().optional(),
comment: z.string().nullable().optional(), comment: z.string().nullable().optional(),
}), }),
) )
.output(zHourSchema) .output(zHourSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { dateQuery, time, code, ...updatedProps } = input; const { code, ...updatedProps } = input;
let dateCondition; let dateCondition;
if (input.dayId) { if (input.dayId) {
dateCondition = eq(days.id, input.dayId) dateCondition = eq(days.id, input.dayId)
} }
else { else {
dateCondition = eq(days.date, dateFromInput({ dateQuery: dateQuery })); throw new TRPCError({ code: "BAD_REQUEST", message: "dayId is required" });
} }
const category = const category =
@ -110,15 +130,6 @@ export const hoursAppRouter = router({
) )
); );
const day = await ctx.db.select()
.from(days)
.where(
and(
dateCondition,
eq(days.userId, ctx.user!.id),
)
);
const newProps = { const newProps = {
categoryId: category[0].id, categoryId: category[0].id,
code: code, code: code,
@ -132,18 +143,18 @@ export const hoursAppRouter = router({
.set(newProps) .set(newProps)
.where( .where(
and( and(
eq(hours.time, time), eq(hours.time, input.hourTime),
eq(hours.dayId, day[0].id), eq(hours.dayId, input.dayId),
eq(hours.userId, ctx.user!.id) eq(hours.userId, ctx.user!.id)
) )
) ).returning();
.returning();
// return {
// date: input.date,
// ...hourRes[0]
// };
return hourJoinsQuery(ctx, input.dayId, input.hourTime);
return {
date: format(day[0].date, "yyyy-MM-dd"),
categoryName: category[0].name,
categoryDesc: category[0].description,
...hourRes[0]
}
}), }),
}); });

View File

@ -22,7 +22,6 @@ export async function createUser(
ctx: Context, ctx: Context,
role?: "user" | "admin", role?: "user" | "admin",
) { ) {
// console.log(ctx.db);
return ctx.db.transaction(async (trx) => { return ctx.db.transaction(async (trx) => {
let userRole = role; let userRole = role;