Checkpoint: Can actually log time (track life, if you will) using CLI and Web.
This commit is contained in:
parent
a85e8f294a
commit
d0c07fd8b8
@ -66,9 +66,9 @@ export const daysCmd = new Command()
|
|||||||
const day = (await api.days.get.query({ dateQuery: dateQuery, timezone: timezone }));
|
const day = (await api.days.get.query({ dateQuery: dateQuery, timezone: timezone }));
|
||||||
|
|
||||||
console.log(`Snagged day with id '${day.id}'`);
|
console.log(`Snagged day with id '${day.id}'`);
|
||||||
if (getGlobalOptions().json) {
|
// if (getGlobalOptions().json) {
|
||||||
printObject(day);
|
// printObject(day);
|
||||||
} else {
|
// } else {
|
||||||
|
|
||||||
const moodStr = moodToStars(day.mood);
|
const moodStr = moodToStars(day.mood);
|
||||||
const dateStr = format(day.date, "EEEE, MMMM do", { in: utc });
|
const dateStr = format(day.date, "EEEE, MMMM do", { in: utc });
|
||||||
@ -76,6 +76,8 @@ export const daysCmd = new Command()
|
|||||||
[`Time (${timezones[timezone]})`, "Category", "Comment"]
|
[`Time (${timezones[timezone]})`, "Category", "Comment"]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
console.log(day.hours.map((h) => h.date));
|
||||||
|
|
||||||
day.hours.forEach((h, i) => {
|
day.hours.forEach((h, i) => {
|
||||||
data.push([
|
data.push([
|
||||||
getHourFromTime(i, 'twelves', `(${timezones[timezone]})`),
|
getHourFromTime(i, 'twelves', `(${timezones[timezone]})`),
|
||||||
@ -85,7 +87,7 @@ export const daysCmd = new Command()
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (flags?.hour) {
|
if (flags?.hour) {
|
||||||
doHour(day.hours, day, flags.hour, timezone);
|
doHour(day, flags.hour, timezone);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
@ -100,7 +102,7 @@ export const daysCmd = new Command()
|
|||||||
return (lineIndex < 1 || lineIndex === 2 || lineIndex === 3 || lineIndex === rowCount);
|
return (lineIndex < 1 || lineIndex === 2 || lineIndex === 3 || lineIndex === rowCount);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
printErrorMessageWithReason("Failed", error as object);
|
printErrorMessageWithReason("Failed", error as object);
|
||||||
|
|||||||
@ -26,11 +26,12 @@ export const hoursCmd = new Command()
|
|||||||
return "Hello";
|
return "Hello";
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function doHour(hours: ZHour[], day: ZDay, hourFlags: string[], timezone = "UTC") {
|
export async function doHour(day: ZDay, hourFlags: string[], timezone = "UTC") {
|
||||||
const hourNum = getTimeFromHour(hourFlags.shift()!);
|
const hourNum = getTimeFromHour(hourFlags.shift()!);
|
||||||
const hour = hours[hourNum];
|
const hour = day.hours[hourNum];
|
||||||
|
|
||||||
if (hourFlags.length == 0) {
|
if (hourFlags.length == 0) {
|
||||||
|
console.log("No new values to set; printing hour.")
|
||||||
printHour(hour, hourNum, day, timezone);
|
printHour(hour, hourNum, day, timezone);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -48,6 +49,7 @@ export async function doHour(hours: ZHour[], day: ZDay, hourFlags: string[], tim
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const props = { dateQuery: hour.date!, time: hour.time, code: hourFlags[0], comment: hourFlags[1] || null };
|
const props = { dateQuery: hour.date!, time: hour.time, code: hourFlags[0], comment: hourFlags[1] || null };
|
||||||
|
// console.log(props);
|
||||||
res = await api.hours.update.mutate({ ...props });
|
res = await api.hours.update.mutate({ ...props });
|
||||||
printHour(res as ZHour, hourNum, day, timezone);
|
printHour(res as ZHour, hourNum, day, timezone);
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ 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 { useTimezone } from "@lifetracker/shared-react/hooks/timezones";
|
||||||
|
|
||||||
export default async function DayPage({
|
export default async function DayPage({
|
||||||
params,
|
params,
|
||||||
@ -9,9 +11,12 @@ export default async function DayPage({
|
|||||||
params: { dateQuery: string };
|
params: { dateQuery: string };
|
||||||
}) {
|
}) {
|
||||||
let day;
|
let day;
|
||||||
|
const tzName = await api.users.getTimezone();
|
||||||
|
console.log(`Displaying ${spacetime.now(tzName).format()} in ${tzName}.`);
|
||||||
try {
|
try {
|
||||||
day = await api.days.get({
|
day = await api.days.get({
|
||||||
dateQuery: params.dateQuery,
|
dateQuery: spacetime(params.dateQuery, tzName).format(),
|
||||||
|
timezone: tzName,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof TRPCError) {
|
if (e instanceof TRPCError) {
|
||||||
@ -23,8 +28,10 @@ export default async function DayPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<DayView
|
<DayView
|
||||||
day={day}
|
day={day}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export default function DemoModeBanner() {
|
export default function DemoModeBanner() {
|
||||||
return (
|
return (
|
||||||
<div className="h-min w-full rounded bg-yellow-100 px-4 py-2 text-center text-black">
|
<div className="h-min w-full rounded bg-yellow-100 px-4 py-2 text-center text-black">
|
||||||
Demo mode is on. All modifications are disabled.
|
YO
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,9 @@ 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 { UTCDate, utc } from "@date-fns/utc";
|
||||||
|
import spacetime from "spacetime";
|
||||||
|
import EditableHour from "@/components/hours/EditableHour";
|
||||||
|
|
||||||
export default async function DayView({
|
export default async function DayView({
|
||||||
day,
|
day,
|
||||||
}: {
|
}: {
|
||||||
@ -22,8 +25,8 @@ export default async function DayView({
|
|||||||
redirect("/");
|
redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevDay = format(addDays(day.date, -1), "yyyy-MM-dd");
|
const prevDay = spacetime(day.date).subtract(1, "day").format("iso-short");
|
||||||
const nextDay = format(addDays(day.date, 1), "yyyy-MM-dd");
|
const nextDay = spacetime(day.date).add(1, "day").format("iso-short");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
@ -41,7 +44,7 @@ export default async function DayView({
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<span className="text-2xl flex-1">
|
<span className="text-2xl flex-1">
|
||||||
{format(day.date, "EEEE, MMMM do", { in: utc })}
|
{spacetime(day.date).format("{day}, {month} {date}, {year}")}
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href={`/dashboard/day/${nextDay}`}
|
href={`/dashboard/day/${nextDay}`}
|
||||||
@ -70,9 +73,29 @@ export default async function DayView({
|
|||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div className={cn(
|
||||||
|
"p-4 grid justify-between",
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
fontFamily: "inherit",
|
||||||
|
gridTemplateColumns: "100px 1fr 200px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-right">
|
||||||
|
Time
|
||||||
|
</span>
|
||||||
|
<span className="text-center">
|
||||||
|
Category
|
||||||
|
</span>
|
||||||
|
<div className="text-right">
|
||||||
|
Actions
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{day.hours.map((hour) => (
|
{day.hours.map((hour) => (
|
||||||
<li key={hour.time}>
|
<li key={hour.time} id={"hour-" + hour.time.toString()}>
|
||||||
{hour.time}: {hour.categoryName} {hour.comment}
|
<EditableHour hour={hour} />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -8,50 +8,73 @@ import { useTimezone } from "@lifetracker/shared-react/hooks/timezones";
|
|||||||
import LoadingSpinner from "@/components/ui/spinner";
|
import LoadingSpinner from "@/components/ui/spinner";
|
||||||
import { db } from "@lifetracker/db";
|
import { db } from "@lifetracker/db";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import spacetime from "spacetime";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function TimezoneDisplay() {
|
export default function TimezoneDisplay() {
|
||||||
|
const clientTime = spacetime.now();
|
||||||
const dbTimezone = useTimezone();
|
|
||||||
const [timezone, setTimezone] = useState(dbTimezone);
|
|
||||||
|
|
||||||
// Update timezone state when dbTimezone changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (dbTimezone !== undefined) {
|
|
||||||
setTimezone(dbTimezone);
|
|
||||||
}
|
|
||||||
}, [dbTimezone]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleTzChange = (event) => {
|
|
||||||
setTimezone(event.detail.timezone);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('timezoneUpdated', handleTzChange);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('timezoneUpdated', handleTzChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (timezone === undefined) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center w-full">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className="text-center w-full">
|
<div className="text-center w-full">
|
||||||
<Link href="/settings/app">
|
<Link href="/settings/app">
|
||||||
<b className="whitespace-nowrap">{format(TZDate.tz(timezone), 'MMM dd, hh:mm aa')}</b>
|
<b className="whitespace-nowrap">
|
||||||
|
{clientTime.format('nice')}
|
||||||
|
</b>
|
||||||
<br />
|
<br />
|
||||||
<span>in </span>
|
<span>in </span>
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
<b>{timezones[timezone]}</b>
|
<b>
|
||||||
|
{timezones[clientTime.timezone().name] || clientTime.timezone().name}
|
||||||
|
</b>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export default function TimezoneDisplay() {
|
||||||
|
|
||||||
|
// const dbTimezone = useTimezone();
|
||||||
|
// const [timezone, setTimezone] = useState(dbTimezone);
|
||||||
|
|
||||||
|
// // Update timezone state when dbTimezone changes
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (dbTimezone !== undefined) {
|
||||||
|
// setTimezone(dbTimezone);
|
||||||
|
// }
|
||||||
|
// }, [dbTimezone]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const handleTzChange = (event) => {
|
||||||
|
// setTimezone(event.detail.timezone);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// window.addEventListener('timezoneUpdated', handleTzChange);
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// window.removeEventListener('timezoneUpdated', handleTzChange);
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// if (timezone === undefined) {
|
||||||
|
// return (
|
||||||
|
// <div className="flex flex-col items-center w-full">
|
||||||
|
// <LoadingSpinner />
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return (
|
||||||
|
// <div className="text-center w-full">
|
||||||
|
// <Link href="/settings/app">
|
||||||
|
// <b className="whitespace-nowrap">{format(TZDate.tz(timezone), 'MMM dd, hh:mm aa')}</b>
|
||||||
|
// <br />
|
||||||
|
// <span>in </span>
|
||||||
|
// <span className="whitespace-nowrap">
|
||||||
|
// <b>{timezones[timezone]}</b>
|
||||||
|
// </span>
|
||||||
|
// </Link>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|||||||
137
apps/web/components/hours/EditableHour.tsx
Normal file
137
apps/web/components/hours/EditableHour.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
|
import { toast } from "@/components/ui/use-toast";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
import { useUpdateHour } from "@lifetracker/shared-react/hooks/days";
|
||||||
|
import { EditableText } from "../dashboard/EditableText";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { TZDate } from "@date-fns/tz";
|
||||||
|
import { ZHour } from "@lifetracker/shared/types/days";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
function EditMode({
|
||||||
|
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 == "") {
|
||||||
|
newCode = null;
|
||||||
|
}
|
||||||
|
// console.log(hour);
|
||||||
|
onSubmit({
|
||||||
|
dateQuery: hour.date,
|
||||||
|
time: hour.time,
|
||||||
|
code: newCode,
|
||||||
|
})
|
||||||
|
document.getElementById("hour-" + (hour.time + 1).toString())?.getElementsByClassName("edit-hour-code")[0].focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className="w-8 border-b-2 text-center edit-hour-code"
|
||||||
|
ref={ref}
|
||||||
|
value={originalText}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
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({
|
||||||
|
hour,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
hour: ZHour,
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
const currentPath = usePathname();
|
||||||
|
const { mutate: updateHour, isPending } = useUpdateHour({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
description: "Hour updated!",
|
||||||
|
});
|
||||||
|
if (currentPath.includes("dashboard")) {
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function isActiveHour(hour: ZHour) {
|
||||||
|
const now = new TZDate();
|
||||||
|
return (hour.date == format(now, "yyyy-MM-dd")) && (((now.getHours()) + (now.getTimezoneOffset() / 60) - (parseInt(hour.time))) == 0)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={cn(
|
||||||
|
"p-4 grid justify-between",
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
background: hour.background, color: hour.foreground, fontFamily: "inherit",
|
||||||
|
gridTemplateColumns: "100px 100px 1fr 200px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-right">
|
||||||
|
{isActiveHour(hour) && "--> "}
|
||||||
|
{hour.datetime}
|
||||||
|
</span>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<EditMode
|
||||||
|
originalText={hour.categoryCode}
|
||||||
|
hour={hour}
|
||||||
|
onSubmit={updateHour}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
{hour.categoryDesc || " "}
|
||||||
|
</span>
|
||||||
|
<div className="text-right">
|
||||||
|
<EditableText
|
||||||
|
originalText={hour.comment || " "}
|
||||||
|
viewClassName="text-right"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { timezones } from '@/lib/timezones';
|
import { timezones } from "@lifetracker/shared/utils/timezones";
|
||||||
import TimezoneSelect, { type ITimezone } from 'react-timezone-select'
|
import TimezoneSelect, { type ITimezone } from 'react-timezone-select'
|
||||||
import { useUpdateUserTimezone } from "@lifetracker/shared-react/hooks/timezones";
|
import { useUpdateUserTimezone } from "@lifetracker/shared-react/hooks/timezones";
|
||||||
|
|
||||||
|
|||||||
@ -77,6 +77,7 @@
|
|||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"request-ip": "^3.3.0",
|
"request-ip": "^3.3.0",
|
||||||
"sharp": "^0.33.3",
|
"sharp": "^0.33.3",
|
||||||
|
"spacetime": "^7.6.2",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
"tailwind-merge": "^2.2.1",
|
"tailwind-merge": "^2.2.1",
|
||||||
"title-case": "^4.3.2",
|
"title-case": "^4.3.2",
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import dbConfig from "./drizzle.config";
|
|||||||
const sqlite = new Database(dbConfig.dbCredentials.url);
|
const sqlite = new Database(dbConfig.dbCredentials.url);
|
||||||
export const db = drizzle(sqlite, {
|
export const db = drizzle(sqlite, {
|
||||||
schema,
|
schema,
|
||||||
logger: false
|
logger: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getInMemoryDB(runMigrations: boolean) {
|
export function getInMemoryDB(runMigrations: boolean) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ 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) => {
|
||||||
@ -13,30 +14,15 @@ export function useUpdateDay(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteLabel(
|
export function useUpdateHour(
|
||||||
...opts: Parameters<typeof api.labels.delete.useMutation>
|
...opts: Parameters<typeof api.hours.update.useMutation>
|
||||||
) {
|
) {
|
||||||
const apiUtils = api.useUtils();
|
const apiUtils = api.useUtils();
|
||||||
|
// console.log(opts[0]);
|
||||||
return api.labels.delete.useMutation({
|
return api.hours.update.useMutation({
|
||||||
...opts[0],
|
...opts[0],
|
||||||
onSuccess: (res, req, meta) => {
|
onSuccess: (res, req, meta) => {
|
||||||
apiUtils.labels.list.invalidate();
|
apiUtils.days.get.invalidate({ dateQuery: req.dateQuery });
|
||||||
// apiUtils.bookmarks.getBookmark.invalidate();
|
|
||||||
return opts[0]?.onSuccess?.(res, req, meta);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteUnusedTags(
|
|
||||||
...opts: Parameters<typeof api.tags.deleteUnused.useMutation>
|
|
||||||
) {
|
|
||||||
const apiUtils = api.useUtils();
|
|
||||||
|
|
||||||
return api.tags.deleteUnused.useMutation({
|
|
||||||
...opts[0],
|
|
||||||
onSuccess: (res, req, meta) => {
|
|
||||||
apiUtils.tags.list.invalidate();
|
|
||||||
return opts[0]?.onSuccess?.(res, req, meta);
|
return opts[0]?.onSuccess?.(res, req, meta);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export function useUpdateUserTimezone(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useTimezone() {
|
export function useTimezone() {
|
||||||
const res = api.users.getTimezone.useQuery().data;
|
const res = api.users.getTimezone.useQuery();
|
||||||
|
console.log("react hook useTimezone", res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -12,6 +12,8 @@ export const zHourSchema = z.object({
|
|||||||
categoryName: z.string().nullish(),
|
categoryName: z.string().nullish(),
|
||||||
categoryDesc: z.string().nullish(),
|
categoryDesc: z.string().nullish(),
|
||||||
comment: z.string().nullish(),
|
comment: z.string().nullish(),
|
||||||
|
background: z.string().nullish(),
|
||||||
|
foreground: z.string().nullish(),
|
||||||
});
|
});
|
||||||
export type ZHour = z.infer<typeof zHourSchema>;
|
export type ZHour = z.infer<typeof zHourSchema>;
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-orm": "^0.33.0",
|
"drizzle-orm": "^0.33.0",
|
||||||
|
"spacetime": "^7.6.2",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
"tiny-invariant": "^1.3.3",
|
"tiny-invariant": "^1.3.3",
|
||||||
"title-case": "^4.3.2",
|
"title-case": "^4.3.2",
|
||||||
|
|||||||
@ -13,6 +13,9 @@ 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 } from "./hours";
|
||||||
|
import spacetime from "spacetime";
|
||||||
|
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) => {
|
||||||
@ -87,7 +90,9 @@ async function getDay(input: { dateQuery: string }, ctx: Context, date: string)
|
|||||||
dayId: hours.dayId,
|
dayId: hours.dayId,
|
||||||
time: hours.time,
|
time: hours.time,
|
||||||
userId: hours.userId,
|
userId: hours.userId,
|
||||||
|
date: days.date
|
||||||
}).from(hours)
|
}).from(hours)
|
||||||
|
.leftJoin(days, eq(days.id, hours.dayId)) // Ensure days table is joined first
|
||||||
.where(and(
|
.where(and(
|
||||||
eq(hours.userId, ctx.user!.id),
|
eq(hours.userId, ctx.user!.id),
|
||||||
eq(hours.dayId, day.id),
|
eq(hours.dayId, day.id),
|
||||||
@ -132,7 +137,7 @@ export const daysAppRouter = router({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const dayHours = await Promise.all(allHours.map(async function (map: { date, time }) {
|
const dayHours = await Promise.all(allHours.map(async function (map: { date, time }, i) {
|
||||||
const dayId = allDayIds.find((dayIds: { id, date }) => map.date == dayIds.date)!.id;
|
const dayId = allDayIds.find((dayIds: { id, date }) => map.date == dayIds.date)!.id;
|
||||||
|
|
||||||
const hourMatch = await ctx.db.select({
|
const hourMatch = await ctx.db.select({
|
||||||
@ -144,8 +149,10 @@ export const daysAppRouter = router({
|
|||||||
categoryName: categories.name,
|
categoryName: categories.name,
|
||||||
categoryDesc: categories.description,
|
categoryDesc: categories.description,
|
||||||
comment: hours.comment,
|
comment: hours.comment,
|
||||||
|
date: days.date
|
||||||
}).from(hours)
|
}).from(hours)
|
||||||
.leftJoin(categories, eq(categories.id, hours.categoryId))
|
.leftJoin(categories, eq(categories.id, hours.categoryId))
|
||||||
|
.leftJoin(days, eq(days.id, hours.dayId))
|
||||||
.where(and(
|
.where(and(
|
||||||
eq(hours.time, map.time),
|
eq(hours.time, map.time),
|
||||||
eq(hours.dayId, dayId)));
|
eq(hours.dayId, dayId)));
|
||||||
@ -155,15 +162,20 @@ export const daysAppRouter = router({
|
|||||||
// });
|
// });
|
||||||
// console.log("Search values:: ", `allDayIds: ${allDayIds}, d: ${date}, t: ${time}, dayId: ${dayId}`)
|
// console.log("Search values:: ", `allDayIds: ${allDayIds}, d: ${date}, t: ${time}, dayId: ${dayId}`)
|
||||||
// console.log("hourMatch", hourMatch[0]);
|
// console.log("hourMatch", hourMatch[0]);
|
||||||
|
// console.log(hourMatch[0].categoryDesc);
|
||||||
|
|
||||||
const dayHour = {
|
const dayHour = {
|
||||||
...hourMatch[0],
|
...hourMatch[0],
|
||||||
|
...(await hourColors(hourMatch[0], ctx)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const localDateTime = spacetime(date, timezone).add(i, "hour");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...dayHour,
|
...dayHour,
|
||||||
date: map.date,
|
datetime: `${localDateTime.format('{hour} {ampm}')}`,
|
||||||
|
// datetime: `${localDateTime.format('{nice}')} ${timezone} (${localDateTime.goto("UTC").format('{nice}')} UTC)`,
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { and, desc, eq, inArray, notExists } from "drizzle-orm";
|
|||||||
import { date, z } from "zod";
|
import { date, z } from "zod";
|
||||||
|
|
||||||
import { SqliteError } from "@lifetracker/db";
|
import { SqliteError } from "@lifetracker/db";
|
||||||
import { categories, days, hours, } from "@lifetracker/db/schema";
|
import { categories, days, hours, colors } from "@lifetracker/db/schema";
|
||||||
import {
|
import {
|
||||||
zDaySchema, ZDay, ZHour, zHourSchema
|
zDaySchema, ZDay, ZHour, zHourSchema
|
||||||
} from "@lifetracker/shared/types/days";
|
} from "@lifetracker/shared/types/days";
|
||||||
@ -13,6 +13,32 @@ 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";
|
||||||
|
|
||||||
|
|
||||||
|
export async function hourColors(hour: ZHour, ctx: Context) {
|
||||||
|
|
||||||
|
const categoryColor = await ctx.db.select()
|
||||||
|
.from(colors)
|
||||||
|
.leftJoin(categories, eq(categories.id, hour.categoryId))
|
||||||
|
.where(and(
|
||||||
|
eq(colors.id, categories.colorId),
|
||||||
|
eq(colors.userId, ctx.user!.id)
|
||||||
|
))
|
||||||
|
|
||||||
|
// console.log(categoryColor);
|
||||||
|
if (!categoryColor[0]) {
|
||||||
|
return {
|
||||||
|
background: "inherit",
|
||||||
|
foreground: "inherit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
background: categoryColor[0].color.hexcode,
|
||||||
|
foreground: categoryColor[0].color.inverse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const hoursAppRouter = router({
|
export const hoursAppRouter = router({
|
||||||
get: authedProcedure
|
get: authedProcedure
|
||||||
.input(z.object({
|
.input(z.object({
|
||||||
@ -30,6 +56,7 @@ export const hoursAppRouter = router({
|
|||||||
categoryId: hours.categoryId,
|
categoryId: hours.categoryId,
|
||||||
categoryCode: categories.code,
|
categoryCode: categories.code,
|
||||||
categoryName: categories.name,
|
categoryName: categories.name,
|
||||||
|
categoryDesc: categories.description,
|
||||||
comment: hours.comment,
|
comment: hours.comment,
|
||||||
})
|
})
|
||||||
.from(hours)
|
.from(hours)
|
||||||
@ -44,6 +71,8 @@ export const hoursAppRouter = router({
|
|||||||
update: authedProcedure
|
update: authedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
hourId: z.string().optional(),
|
||||||
|
dayId: z.string().optional(),
|
||||||
dateQuery: z.string(),
|
dateQuery: z.string(),
|
||||||
time: z.number(),
|
time: z.number(),
|
||||||
code: z.string().optional(),
|
code: z.string().optional(),
|
||||||
@ -53,7 +82,13 @@ export const hoursAppRouter = router({
|
|||||||
.output(zHourSchema)
|
.output(zHourSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { dateQuery, time, code, ...updatedProps } = input;
|
const { dateQuery, time, code, ...updatedProps } = input;
|
||||||
var date = dateFromInput({ dateQuery: dateQuery });
|
let dateCondition;
|
||||||
|
if (input.dayId) {
|
||||||
|
dateCondition = eq(days.id, input.dayId)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dateCondition = eq(days.date, dateFromInput({ dateQuery: dateQuery }));
|
||||||
|
}
|
||||||
|
|
||||||
const category =
|
const category =
|
||||||
code == "" ? [{
|
code == "" ? [{
|
||||||
@ -64,6 +99,7 @@ export const hoursAppRouter = router({
|
|||||||
{
|
{
|
||||||
id: categories.id,
|
id: categories.id,
|
||||||
name: categories.name,
|
name: categories.name,
|
||||||
|
description: categories.description
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.from(categories)
|
.from(categories)
|
||||||
@ -74,14 +110,11 @@ export const hoursAppRouter = router({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const day = await ctx.db.select()
|
||||||
const day = await ctx.db.select(
|
|
||||||
{ id: days.id }
|
|
||||||
)
|
|
||||||
.from(days)
|
.from(days)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(days.date, date),
|
dateCondition,
|
||||||
eq(days.userId, ctx.user!.id),
|
eq(days.userId, ctx.user!.id),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -107,8 +140,9 @@ export const hoursAppRouter = router({
|
|||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: format(date, "yyyy-MM-dd"),
|
date: format(day[0].date, "yyyy-MM-dd"),
|
||||||
categoryName: category[0].name,
|
categoryName: category[0].name,
|
||||||
|
categoryDesc: category[0].description,
|
||||||
...hourRes[0]
|
...hourRes[0]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -180,7 +180,6 @@ export const usersAppRouter = router({
|
|||||||
)
|
)
|
||||||
.query(async ({ ctx }) => {
|
.query(async ({ ctx }) => {
|
||||||
const res = await ctx.db.select({ timezone: users.timezone }).from(users).where(eq(users.id, ctx.user.id));
|
const res = await ctx.db.select({ timezone: users.timezone }).from(users).where(eq(users.id, ctx.user.id));
|
||||||
|
|
||||||
return res[0].timezone;
|
return res[0].timezone;
|
||||||
}),
|
}),
|
||||||
changeTimezone: authedProcedure
|
changeTimezone: authedProcedure
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@ -337,6 +337,9 @@ importers:
|
|||||||
sharp:
|
sharp:
|
||||||
specifier: ^0.33.3
|
specifier: ^0.33.3
|
||||||
version: 0.33.5
|
version: 0.33.5
|
||||||
|
spacetime:
|
||||||
|
specifier: ^7.6.2
|
||||||
|
version: 7.6.2
|
||||||
superjson:
|
superjson:
|
||||||
specifier: ^2.2.1
|
specifier: ^2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
@ -616,6 +619,9 @@ importers:
|
|||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.33.0
|
specifier: ^0.33.0
|
||||||
version: 0.33.0(@types/better-sqlite3@7.6.11)(@types/react@18.2.61)(better-sqlite3@11.5.0)(expo-sqlite@14.0.6(expo@51.0.36(@babel/core@7.26.0)(@babel/preset-env@7.25.7(@babel/core@7.26.0))(encoding@0.1.13)))(react@18.3.1)(sqlite3@5.1.7)
|
version: 0.33.0(@types/better-sqlite3@7.6.11)(@types/react@18.2.61)(better-sqlite3@11.5.0)(expo-sqlite@14.0.6(expo@51.0.36(@babel/core@7.26.0)(@babel/preset-env@7.25.7(@babel/core@7.26.0))(encoding@0.1.13)))(react@18.3.1)(sqlite3@5.1.7)
|
||||||
|
spacetime:
|
||||||
|
specifier: ^7.6.2
|
||||||
|
version: 7.6.2
|
||||||
superjson:
|
superjson:
|
||||||
specifier: ^2.2.1
|
specifier: ^2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user