Maybe have frontend web working with codes at least.
This commit is contained in:
parent
a46a8a9399
commit
5625e9a6fd
@ -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}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
102
apps/web/components/dashboard/hours/EditableHourCode.tsx
Normal file
102
apps/web/components/dashboard/hours/EditableHourCode.tsx
Normal 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 ?? ""}
|
||||||
|
// />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
83
apps/web/components/dashboard/hours/EditableHourComment.tsx
Normal file
83
apps/web/components/dashboard/hours/EditableHourComment.tsx
Normal 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();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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,
|
||||||
@ -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": {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
@ -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) {
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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 })));
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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]
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user