diff --git a/apps/cli/package.json b/apps/cli/package.json
index 4e04d9a..9ea7840 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -17,6 +17,7 @@
},
"dependencies": {
"@date-fns/tz": "^1.2.0",
+ "@date-fns/utc": "^2.1.0",
"@lifetracker/db": "workspace:*",
"@lifetracker/shared": "workspace:^",
"@lifetracker/trpc": "workspace:^",
diff --git a/apps/cli/src/commands/days.ts b/apps/cli/src/commands/days.ts
index 51b5a94..1545900 100644
--- a/apps/cli/src/commands/days.ts
+++ b/apps/cli/src/commands/days.ts
@@ -10,6 +10,7 @@ import { Command } from "@commander-js/extra-typings";
import { getBorderCharacters, table } from "table";
import { format } from "date-fns";
import { TZDate } from "@date-fns/tz";
+import { utc } from "@date-fns/utc";
import { getHourFromTime, getTimeFromHour } from "@lifetracker/shared/utils/hours";
function moodToStars(mood: number) {
@@ -41,13 +42,14 @@ export const daysCmd = new Command()
} else {
const moodStr = moodToStars(day.mood);
- const dateStr = format(day.date, "EEEE, MMMM do");
+ const dateStr = format(day.date, "EEEE, MMMM do", { in: utc });
const data: string[][] = [[dateStr, '', moodStr], [day.comment ?? "No comment", '', ''],
["Time", "Category", "Comment"]
];
- day.hours.forEach((h) => {
- data.push([getHourFromTime(h.time), h.categoryName ?? "--", h.comment ?? ""]);
+ day.hours.forEach((h, i) => {
+ data.push([getHourFromTime(h.time),
+ h.categoryName ?? "--", h.comment ?? ""]);
})
console.log(table(data, {
diff --git a/apps/web/app/dashboard/day/[dateQuery]/page.tsx b/apps/web/app/dashboard/day/[dateQuery]/page.tsx
index accb234..7a53e2a 100644
--- a/apps/web/app/dashboard/day/[dateQuery]/page.tsx
+++ b/apps/web/app/dashboard/day/[dateQuery]/page.tsx
@@ -2,7 +2,6 @@ import { notFound } from "next/navigation";
import { api } from "@/server/api/client";
import { TRPCError } from "@trpc/server";
import DayView from "@/components/dashboard/days/DayView";
-import LoadingSpinner from "@/components/ui/spinner";
export default async function DayPage({
params,
@@ -10,9 +9,10 @@ export default async function DayPage({
params: { dateQuery: string };
}) {
let day;
-
try {
- day = await api.days.get({ dateQuery: params.dateQuery });
+ day = await api.days.get({
+ dateQuery: params.dateQuery,
+ });
} catch (e) {
if (e instanceof TRPCError) {
if (e.code == "NOT_FOUND") {
@@ -23,11 +23,8 @@ export default async function DayPage({
}
return (
- params.dateQuery === undefined ?
-
- :
-
+
);
}
diff --git a/apps/web/components/dashboard/days/DayView.tsx b/apps/web/components/dashboard/days/DayView.tsx
index 8d936ad..295d400 100644
--- a/apps/web/components/dashboard/days/DayView.tsx
+++ b/apps/web/components/dashboard/days/DayView.tsx
@@ -5,31 +5,55 @@ import { getServerAuthSession } from "@/server/auth";
import { ZDay } from "@lifetracker/shared/types/days";
import EditableDayComment from "./EditableDayComment";
import { MoodStars } from "./MoodStars";
-import { format } from "date-fns";
-
+import { format, addDays } from "date-fns";
+import { ButtonWithTooltip } from "@/components/ui/button";
+import { router } from "next/navigation";
+import Link from "next/link";
+import { cn } from "@/lib/utils";
+import { ArrowLeftSquare, ArrowRightSquare } from "lucide-react";
+import { UTCDate, utc } from "@date-fns/utc";
export default async function DayView({
day,
- header,
- showDivider,
- showEditorCard = false,
}: {
day: ZDay;
- header?: React.ReactNode;
- showDivider?: boolean;
- showEditorCard?: boolean;
}) {
const session = await getServerAuthSession();
if (!session) {
redirect("/");
}
+ const prevDay = format(addDays(day.date, -1), "yyyy-MM-dd");
+ const nextDay = format(addDays(day.date, 1), "yyyy-MM-dd");
+
return (
-
- {format(day.date, "EEEE, MMMM do")}
-
+
+
+
+
+
+ {format(day.date, "EEEE, MMMM do", { in: utc })}
+
+
+
+
+
-
+
+ {day.hours.map((hour) => (
+ -
+ {hour.time}: {hour.categoryName} {hour.comment}
+
+ ))}
+
);
diff --git a/apps/web/components/dashboard/days/EditableDayComment.tsx b/apps/web/components/dashboard/days/EditableDayComment.tsx
index 3306261..33e2ea4 100644
--- a/apps/web/components/dashboard/days/EditableDayComment.tsx
+++ b/apps/web/components/dashboard/days/EditableDayComment.tsx
@@ -41,7 +41,6 @@ export default function EditableDayComment({
},
{
onError: (e) => {
- console.log(e);
toast({
description: e.message,
variant: "destructive",
diff --git a/apps/web/components/dashboard/header/Header.tsx b/apps/web/components/dashboard/header/Header.tsx
index ac1dcec..e254a2e 100644
--- a/apps/web/components/dashboard/header/Header.tsx
+++ b/apps/web/components/dashboard/header/Header.tsx
@@ -15,7 +15,7 @@ export default async function Header() {
return (
-
+
diff --git a/apps/web/components/dashboard/sidebar/Sidebar.tsx b/apps/web/components/dashboard/sidebar/Sidebar.tsx
index 0133062..093d119 100644
--- a/apps/web/components/dashboard/sidebar/Sidebar.tsx
+++ b/apps/web/components/dashboard/sidebar/Sidebar.tsx
@@ -35,7 +35,7 @@ export default async function Sidebar() {
{
name: "Home",
icon: ,
- path: "/dashboard/today",
+ path: "/dashboard/day/today",
},
...searchItem,
{
diff --git a/apps/web/components/settings/sidebar/items.tsx b/apps/web/components/settings/sidebar/items.tsx
index d1b10e2..8335114 100644
--- a/apps/web/components/settings/sidebar/items.tsx
+++ b/apps/web/components/settings/sidebar/items.tsx
@@ -16,7 +16,7 @@ export const settingsSidebarItems: {
{
name: "Back To App",
icon: ,
- path: "/dashboard/today",
+ path: "/dashboard/day/today",
},
{
name: "User Info",
diff --git a/apps/web/package.json b/apps/web/package.json
index fb26322..9662518 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -17,6 +17,7 @@
"dependencies": {
"@auth/drizzle-adapter": "^1.4.2",
"@date-fns/tz": "^1.2.0",
+ "@date-fns/utc": "^2.1.0",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@hookform/resolvers": "^3.3.4",
diff --git a/packages/db/migrations/0000_exotic_dakota_north.sql b/packages/db/migrations/0000_cold_golden_guardian.sql
similarity index 97%
rename from packages/db/migrations/0000_exotic_dakota_north.sql
rename to packages/db/migrations/0000_cold_golden_guardian.sql
index 33821d6..9fa64ff 100644
--- a/packages/db/migrations/0000_exotic_dakota_north.sql
+++ b/packages/db/migrations/0000_cold_golden_guardian.sql
@@ -105,6 +105,5 @@ CREATE UNIQUE INDEX `apiKey_name_userId_unique` ON `apiKey` (`name`,`userId`);--
CREATE UNIQUE INDEX `category_userId_code_unique` ON `category` (`userId`,`code`);--> statement-breakpoint
CREATE UNIQUE INDEX `color_userId_name_unique` ON `color` (`userId`,`name`);--> statement-breakpoint
CREATE UNIQUE INDEX `day_date_unique` ON `day` (`date`);--> statement-breakpoint
-CREATE UNIQUE INDEX `hour_time_unique` ON `hour` (`time`);--> statement-breakpoint
CREATE UNIQUE INDEX `hour_dayId_time_unique` ON `hour` (`dayId`,`time`);--> statement-breakpoint
CREATE UNIQUE INDEX `user_email_unique` ON `user` (`email`);
\ No newline at end of file
diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json
index 0b7fd3d..4067412 100644
--- a/packages/db/migrations/meta/0000_snapshot.json
+++ b/packages/db/migrations/meta/0000_snapshot.json
@@ -1,7 +1,7 @@
{
"version": "6",
"dialect": "sqlite",
- "id": "170be9e2-c822-4d3a-a0b1-18c8468f1c5d",
+ "id": "ebffb4c7-5ecf-46d0-93c6-68f8e48a9fc4",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
@@ -524,13 +524,6 @@
}
},
"indexes": {
- "hour_time_unique": {
- "name": "hour_time_unique",
- "columns": [
- "time"
- ],
- "isUnique": true
- },
"hour_dayId_time_unique": {
"name": "hour_dayId_time_unique",
"columns": [
diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json
index b23047c..288c140 100644
--- a/packages/db/migrations/meta/_journal.json
+++ b/packages/db/migrations/meta/_journal.json
@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "6",
- "when": 1732706352404,
- "tag": "0000_exotic_dakota_north",
+ "when": 1732766704666,
+ "tag": "0000_cold_golden_guardian",
"breakpoints": true
}
]
diff --git a/packages/db/schema.ts b/packages/db/schema.ts
index dcb73b7..fc7e3a9 100644
--- a/packages/db/schema.ts
+++ b/packages/db/schema.ts
@@ -21,7 +21,6 @@ function createdAtField() {
}
export function calcInverseColor(hexcode: string): string {
- console.log(hexcode);
const hex = hexcode.replace("#", "");
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
@@ -148,7 +147,7 @@ export const hours = sqliteTable(
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
comment: text("comment"),
- time: integer("time").unique(),
+ time: integer("time"),
dayId: text("dayId").notNull().references(() => days.id, { onDelete: "cascade" }),
categoryId: text("categoryId").references(() => categories.id),
},
diff --git a/packages/shared-react/hooks/timezones.ts b/packages/shared-react/hooks/timezones.ts
index 7bf9b56..c579414 100644
--- a/packages/shared-react/hooks/timezones.ts
+++ b/packages/shared-react/hooks/timezones.ts
@@ -3,7 +3,6 @@ import { api } from "../trpc";
export function useUpdateUserTimezone(
...opts: Parameters
) {
- const apiUtils = api.useUtils();
return api.users.changeTimezone.useMutation({
...opts[0],
onSuccess: (res, req, meta) => {
diff --git a/packages/shared/package.json b/packages/shared/package.json
index a321c21..8052b6e 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -6,6 +6,7 @@
"type": "module",
"dependencies": {
"@date-fns/tz": "^1.2.0",
+ "@date-fns/utc": "^2.1.0",
"date-fns": "^4.1.0",
"winston": "^3.17.0",
"zod": "^3.23.8"
diff --git a/packages/shared/types/days.ts b/packages/shared/types/days.ts
index 0e1b9e5..fb247d5 100644
--- a/packages/shared/types/days.ts
+++ b/packages/shared/types/days.ts
@@ -6,10 +6,10 @@ export const zHourSchema = z.object({
dayId: z.string(),
date: z.string().optional(),
time: z.number(),
- categoryCode: z.coerce.number().nullable(),
- categoryId: z.string().nullable(),
- categoryName: z.string().nullable(),
- comment: z.string().nullable(),
+ categoryCode: z.coerce.number().nullish(),
+ categoryId: z.string().nullish(),
+ categoryName: z.string().nullish(),
+ comment: z.string().nullish(),
});
export type ZHour = z.infer;
diff --git a/packages/shared/utils/days.ts b/packages/shared/utils/days.ts
index b3a0a8c..a54d3fd 100644
--- a/packages/shared/utils/days.ts
+++ b/packages/shared/utils/days.ts
@@ -1,13 +1,32 @@
-import { format } from "date-fns";
+import { format, addHours } from "date-fns";
import { TZDate } from "@date-fns/tz";
+import { UTCDate, utc } from "@date-fns/utc";
export function dateFromInput(input: { dateQuery: string, timezone: string }) {
- let t: TZDate;
+ let t: string;
if (input.dateQuery == "today") {
- t = TZDate.tz(input.timezone);
+ t = new Date();
+ return format(t, "yyyy-MM-dd", { in: input.timezone });
}
else {
- t = new TZDate(input.dateQuery, input.timezone);
+ t = new UTCDate(input.dateQuery);
+ return format(t, "yyyy-MM-dd", { in: utc });
}
- return format(t, "yyyy-MM-dd") + "T00:00:00";
-}
\ No newline at end of file
+
+}
+
+function generateHour(d, t) {
+ const dt: TZDate = addHours(d, t);
+ return {
+ date: format(dt, 'yyyy-MM-dd'),
+ time: parseInt(format(dt, 'H')),
+ };
+}
+
+export function hoursListInTimezone(input: { dateQuery: string, timezone: string }) {
+ const dateStr = dateFromInput(input);
+ const d = new TZDate(dateStr, input.timezone);
+ return Array.from({ length: 24 }, (_, t) =>
+ generateHour(d, t)
+ );
+}
diff --git a/packages/trpc/routers/categories.ts b/packages/trpc/routers/categories.ts
index 3bf1e32..0e95076 100644
--- a/packages/trpc/routers/categories.ts
+++ b/packages/trpc/routers/categories.ts
@@ -27,7 +27,6 @@ async function createCategory(
) {
return ctx.db.transaction(async (trx) => {
- console.log("Creating a category", input);
try {
const result = await trx
.insert(categories)
@@ -54,7 +53,6 @@ async function createCategory(
};
} catch (e) {
- console.log(e);
if (e instanceof SqliteError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") {
throw new TRPCError({
@@ -196,8 +194,6 @@ export const categoriesAppRouter = router({
category.parentId = categoryId;
}
- console.log(category);
-
return category;
}),
create: authedProcedure
diff --git a/packages/trpc/routers/days.ts b/packages/trpc/routers/days.ts
index a9c5aca..43e8353 100644
--- a/packages/trpc/routers/days.ts
+++ b/packages/trpc/routers/days.ts
@@ -2,16 +2,17 @@ import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { z } from "zod";
-import { SqliteError } from "@lifetracker/db";
-import { categories, days, hours, } from "@lifetracker/db/schema";
+import { db, SqliteError } from "@lifetracker/db";
+import { categories, days, hours, users } from "@lifetracker/db/schema";
import {
zDaySchema, ZDay
} from "@lifetracker/shared/types/days";
import type { Context } from "../index";
import { authedProcedure, router } from "../index";
-import { dateFromInput } from "@lifetracker/shared/utils/days";
-import { format } from "date-fns";
+import { dateFromInput, hoursListInTimezone } from "@lifetracker/shared/utils/days";
+import { closestIndexTo, format } from "date-fns";
import { TZDate } from "@date-fns/tz";
+import { hoursAppRouter } from "./hours";
async function createDay(date: string, ctx: Context) {
return await ctx.db.transaction(async (trx) => {
@@ -30,21 +31,8 @@ async function createDay(date: string, ctx: Context) {
comment: days.comment,
});
- const dayId = dayRes[0].id;
-
- // Generate 24 "hour" objects
- const hoursData = Array.from({ length: 24 }, (_, hour) => ({
- userId: ctx.user!.id,
- dayId: dayId,
- time: hour
- }));
-
- // Insert the "hour" objects
- await trx.insert(hours).values(hoursData);
-
return dayRes;
} catch (e) {
- console.log(e);
if (e instanceof SqliteError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") {
throw new TRPCError({
@@ -61,6 +49,59 @@ async function createDay(date: string, ctx: Context) {
});
}
+async function createHour(day, time, ctx,) {
+ const newHour = (await ctx.db.insert(hours).values({
+ dayId: day.id,
+ time: time,
+ userId: ctx.user!.id,
+ }).returning());
+ console.log(newHour);
+ return newHour[0];
+}
+
+async function getTimezone(ctx: Context) {
+ const dbTimezone = await ctx.db.select({
+ timezone: users.timezone
+ }).from(users).where(eq(users.id, ctx.user!.id));
+
+ return dbTimezone[0].timezone as string;
+}
+
+async function getDay(input: { dateQuery: string }, ctx: Context, date: string) {
+ const dayRes = await ctx.db.select({
+ id: days.id,
+ date: days.date,
+ mood: days.mood,
+ comment: days.comment,
+ })
+ .from(days)
+ .where(eq(days.date, date));
+
+ const day = dayRes.length == 0
+ ? (await createDay(date, ctx))[0]
+ : (dayRes[0]);
+
+ const dayHours = await Promise.all(
+ Array.from({ length: 24 }).map(async function (_, i) {
+ const existing = await ctx.db.select({
+ dayId: hours.dayId,
+ time: hours.time,
+ userId: hours.userId,
+ }).from(hours)
+ .where(and(
+ eq(hours.userId, ctx.user!.id),
+ eq(hours.dayId, day.id),
+ eq(hours.time, i)
+ ))
+ return existing.length == 0 ? createHour(day, i, ctx) : existing[0];
+ }));
+
+ return {
+ hours: dayHours,
+ ...day
+ }
+}
+
export const daysAppRouter = router({
get: authedProcedure
.input(z.object({
@@ -68,47 +109,48 @@ export const daysAppRouter = router({
}))
.output(zDaySchema)
.query(async ({ input, ctx }) => {
- const date = dateFromInput({ timezone: ctx.user.timezone, ...input });
- console.log(ctx.user.timezone);
- // Fetch the day data
- let dayRes;
- dayRes = await ctx.db
- .select({
- id: days.id,
- date: days.date,
- mood: days.mood,
- comment: days.comment,
- })
- .from(days)
- .where(eq(days.date, date));
- if (dayRes.length === 0) {
- dayRes = await createDay(date, ctx);
- }
+ const timezone = await getTimezone(ctx);
+ const date = dateFromInput({
+ dateQuery: input.dateQuery,
+ timezone: timezone
+ });
- const day = dayRes[0];
+ const allHours = hoursListInTimezone({
+ timezone,
+ ...input
+ });
- // Fetch the hours data for the corresponding dayId
- const hoursRes = await ctx.db
- .select({
- id: hours.id,
- dayId: hours.dayId,
- time: hours.time,
- categoryId: hours.categoryId,
- categoryCode: categories.code,
- categoryName: categories.name,
- comment: hours.comment,
- })
- .from(hours)
- .where(eq(hours.dayId, day.id))
- .leftJoin(categories, eq(categories.id, hours.categoryId))
+ const dayRange = [...new Set(allHours.map(({ date: date, time: _time }) => date))];
- // Combine the day and hours data
- const result = {
- ...day,
- hours: hoursRes,
+ const allDayIds = await Promise.all(dayRange.map(async function (date) {
+ const dayObj = await getDay(input, ctx, date);
+ return {
+ id: dayObj.id,
+ date: dayObj.date
+ }
+ }));
+
+ const dayHours = await Promise.all(allHours.map(async function ({ date, time }) {
+ const dayId = allDayIds.find((_value: { id, date }) => date == date)!.id;
+
+
+ const hourMatch = await ctx.db.select().from(hours)
+ .where(and(
+ eq(hours.time, time),
+ eq(hours.dayId, dayId)));
+
+ // console.log("Search values:: ", `d: ${date}, t: ${time}, dayId: ${dayId}`)
+ // console.log("hourMatch", hourMatch[0]);
+
+ return hourMatch;
+ }));
+
+ return {
+ ...await getDay(input, ctx, date),
+ hours: dayHours.flat(),
};
- return result;
+
}),
update: authedProcedure
.input(
diff --git a/packages/trpc/routers/hours.ts b/packages/trpc/routers/hours.ts
index eeb4463..8991278 100644
--- a/packages/trpc/routers/hours.ts
+++ b/packages/trpc/routers/hours.ts
@@ -22,7 +22,6 @@ export const hoursAppRouter = router({
.output(zHourSchema)
.query(async ({ input, ctx }) => {
const date = dateFromInput(input);
- console.log(input);
const hourRes = await ctx.db
.select({
id: hours.id,
@@ -37,8 +36,6 @@ export const hoursAppRouter = router({
.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
-
- console.log(hourRes);
return {
date: format(date, "yyyy-MM-dd"),
...hourRes[0]
diff --git a/packages/trpc/routers/users.ts b/packages/trpc/routers/users.ts
index 9082cfa..6a07b8e 100644
--- a/packages/trpc/routers/users.ts
+++ b/packages/trpc/routers/users.ts
@@ -198,8 +198,6 @@ export const usersAppRouter = router({
timezone: input.newTimezone,
})
.where(eq(users.id, ctx.user.id));
-
- console.log("changeTimezone input", input.newTimezone);
return input.newTimezone;
}),
});
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0aed72d..28a39da 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
'@date-fns/tz':
specifier: ^1.2.0
version: 1.2.0
+ '@date-fns/utc':
+ specifier: ^2.1.0
+ version: 2.1.0
'@lifetracker/db':
specifier: workspace:*
version: link:../../packages/db
@@ -148,6 +151,9 @@ importers:
'@date-fns/tz':
specifier: ^1.2.0
version: 1.2.0
+ '@date-fns/utc':
+ specifier: ^2.1.0
+ version: 2.1.0
'@emoji-mart/data':
specifier: ^1.1.2
version: 1.2.1
@@ -478,6 +484,9 @@ importers:
'@date-fns/tz':
specifier: ^1.2.0
version: 1.2.0
+ '@date-fns/utc':
+ specifier: ^2.1.0
+ version: 2.1.0
date-fns:
specifier: ^4.1.0
version: 4.1.0
@@ -1935,6 +1944,9 @@ packages:
'@date-fns/tz@1.2.0':
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
+ '@date-fns/utc@2.1.0':
+ resolution: {integrity: sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA==}
+
'@discoveryjs/json-ext@0.5.7':
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'}
@@ -14384,6 +14396,8 @@ snapshots:
'@date-fns/tz@1.2.0': {}
+ '@date-fns/utc@2.1.0': {}
+
'@discoveryjs/json-ext@0.5.7': {}
'@docsearch/css@3.6.2': {}