Frontend updates to Day and Month views

This commit is contained in:
Ryan Pandya 2024-12-04 12:20:32 -08:00
parent 4a434b59e4
commit 020b75b72e
10 changed files with 135 additions and 109 deletions

View File

@ -4,7 +4,7 @@ import Sidebar from "@/components/dashboard/sidebar/Sidebar";
import DemoModeBanner from "@/components/DemoModeBanner"; import DemoModeBanner from "@/components/DemoModeBanner";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import ValidAccountCheck from "@/components/utils/ValidAccountCheck"; import ValidAccountCheck from "@/components/utils/ValidAccountCheck";
import { cn } from "@/lib/utils";
import serverConfig from "@lifetracker/shared/config"; import serverConfig from "@lifetracker/shared/config";
export default async function Dashboard({ export default async function Dashboard({
@ -29,7 +29,7 @@ export default async function Dashboard({
<Separator /> <Separator />
</div> </div>
{modal} {modal}
<div className="min-h-30 container p-4">{children}</div> <div className={cn("min-h-30 p-4 container")}>{children}</div>
</main> </main>
</div> </div>
</div> </div>

View File

@ -6,6 +6,7 @@ import { EditableHourCode } from "@/components/dashboard/hours/EditableHourCode"
import { useUpdateHour } from "@lifetracker/shared-react/hooks/days"; import { useUpdateHour } from "@lifetracker/shared-react/hooks/days";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { getHourFromTime } from "@lifetracker/shared/utils/hours"; import { getHourFromTime } from "@lifetracker/shared/utils/hours";
import MonthView from "@/components/dashboard/timelines/MonthView";
async function fetchDays(view: string, dateQuery: string) { async function fetchDays(view: string, dateQuery: string) {
const timezone = await api.users.getTimezone(); const timezone = await api.users.getTimezone();
@ -16,7 +17,7 @@ async function fetchDays(view: string, dateQuery: string) {
const days = []; const days = [];
for (let i = 0; i < 20; i++) { for (let i = 0; i < 20; i++) {
const dayDate = firstDay.add(i, "day").format("iso-short"); const dayDate = firstDay.add(i, "day").format("iso-short");
console.log(dayDate); // console.log(dayDate);
const dayRes = await api.days.get({ const dayRes = await api.days.get({
dateQuery: dayDate, dateQuery: dayDate,
timezone: timezone, timezone: timezone,
@ -33,44 +34,7 @@ export default async function TimelinePage({ params }: { params: { view: string,
return ( return (
<> <>
<div className="grid" style={{ "gridTemplateColumns": "repeat(25, 1fr)" }}> <MonthView days={days} />
<div className="flex items-center">Date</div>
{Array.from({ length: 24 }, (_, i) => (
<div key={i} className="flex text-center items-center justify-center font-mono">
{getHourFromTime(i, "twelves")}
</div>
))}
</div>
{days.map((day, index) => (
<div
key={index}
className="grid"
style={{ "gridTemplateColumns": "repeat(25, 1fr)" }}
>
<div className="text-right font-mono">
{spacetime(day.date).format("{iso-month}/{date-pad}")}
</div>
{day.hours.map((hour, i) => (
<EditableHourCode
key={i}
originalText={hour.categoryCode}
hour={hour}
i={i}
/>
// <div key={i}
// datetime={day.date + "T" + hour.time + ":00:00"}
// className="flex items-center justify-center font-mono"
// style={{
// backgroundColor: hour.background ?? "white",
// color: hour.foreground ?? "black",
// }}
// >
// {hour.categoryCode ?? "_"}
// </div>
))}
</div>
))}
</> </>
); );
} }

View File

@ -26,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 pr-4"> <div className="flex justify-between pr-4 flex-col gap-4 md:flex-row">
<div className="flex"> <div className="flex">
<Link <Link
href={`/dashboard/day/${prevDay}`} href={`/dashboard/day/${prevDay}`}
@ -38,9 +38,12 @@ export default async function DayView({
<ArrowLeftSquare size={18} /> <ArrowLeftSquare size={18} />
</div> </div>
</Link> </Link>
<span className="text-2xl flex-1"> <span className="text-2xl flex-1 hidden lg:block">
{spacetime(day.date).format("{day}, {month} {date}, {year}")} {spacetime(day.date).format("{day}, {month} {date}, {year}")}
</span> </span>
<span className="text-2xl flex-1 lg:hidden block text-center">
{spacetime(day.date).format("{day-short}, {month-short} {date}, {year}")}
</span>
<Link <Link
href={`/dashboard/day/${nextDay}`} href={`/dashboard/day/${nextDay}`}
className={cn( className={cn(
@ -52,7 +55,7 @@ export default async function DayView({
</div> </div>
</Link> </Link>
</div> </div>
<div className="flex items-center"> <div className="flex justify-center">
<MoodStars <MoodStars
day={day} day={day}
/> />

View File

@ -10,7 +10,7 @@ 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 { MessageCircle } from "lucide-react"; import { MessageCircle, Pencil } from "lucide-react";
import { ButtonWithTooltip } from "@/components/ui/button"; import { ButtonWithTooltip } from "@/components/ui/button";
import { EditableHourCode } from "./EditableHourCode"; import { EditableHourCode } from "./EditableHourCode";
import { EditableHourComment } from "./EditableHourComment"; import { EditableHourComment } from "./EditableHourComment";
@ -27,22 +27,23 @@ export default function EditableHour({
i: number, i: number,
className?: string; className?: string;
}) { }) {
const router = useRouter();
const currentPath = usePathname();
const [hour, setHour] = useState(initialHour); const [hour, setHour] = useState(initialHour);
const { mutate: updateHour, isPending } = useUpdateHour({ const { mutate: updateHour, isPending } = useUpdateHour({
onSuccess: (res, req, meta) => { onSuccess: (res, req, meta) => {
const { categoryCode: oldCode, comment: oldComment } = hour; const { categoryCode: oldCode, comment: oldComment } = hour;
const newHour = { const newHour = {
categoryCode: req.code, categoryCode: parseInt(req.code!),
comment: oldComment, comment: oldComment,
...res, ...res,
}; };
console.log(res); console.log(res);
setHour(newHour); setHour(newHour);
toast({ // Only show toast if client screen is larger than mobile
description: "Hour updated!", if (window.innerWidth > 640) {
}); toast({
description: "Hour updated!",
});
}
}, },
}); });
const tzOffset = spacetime().offset() / 60; const tzOffset = spacetime().offset() / 60;
@ -64,28 +65,29 @@ export default function EditableHour({
} }
return ( return (
<div <div
hourid={hour.id} data-hourid={hour.id}
className={cn( className={cn(
"p-4 grid justify-between", "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: window.innerWidth > 640 ? "50px 100px 1fr 50px" : "50px 100px 1fr", // Known issue: This won't work if the screen is resized, only on reload
}} }}
> >
<span className="text-right"> <span className="text-right">
{isActiveHour(hour) && "--> "} {/* {isActiveHour(hour) && "--> "} */}
{hour.datetime} {hour.datetime}
</span> </span>
<div className="flex justify-center"> <div className="flex justify-center">
<EditableHourCode <EditableHourCode
className={"w-8 border-b"}
originalText={hour.categoryCode} originalText={hour.categoryCode}
hour={hour} hour={hour}
onSubmit={updateHour} onSubmit={updateHour}
i={i} i={i}
/> />
</div> </div>
<span> <span className="hidden sm:block">
<EditableHourComment <EditableHourComment
originalText={hour.categoryDesc} originalText={hour.categoryDesc}
hour={hour} hour={hour}
@ -93,18 +95,26 @@ export default function EditableHour({
i={i} i={i}
/> />
</span> </span>
<div className="text-right"> <span className="block sm:hidden">
<div className="w-full text-left edit-hour-comment"
style={{
background: hour.background ?? "inherit", color: hour.foreground ?? "inherit", fontFamily: "inherit",
}}>
{hour.categoryName}
</div>
</span>
<div className="text-right hidden sm:block">
<ButtonWithTooltip <ButtonWithTooltip
delayDuration={500} delayDuration={500}
tooltip="Add Comment" tooltip="Edit"
size="none" size="none"
variant="ghost" variant="ghost"
className="align-middle text-gray-400" className="align-middle text-gray-400 hover:bg-active"
onClick={() => { onClick={() => {
console.log("Pushed edit") console.log("Pushed edit")
}} }}
> >
<MessageCircle className="size-4" /> <Pencil className="size-4" />
</ButtonWithTooltip> </ButtonWithTooltip>
</div> </div>
</div> </div>

View File

@ -1,8 +1,10 @@
"use client"; "use client";
import { or } from "drizzle-orm";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import {
cn
} from "@/lib/utils";
function selectHourCode(time: number) { function selectHourCode(time: number) {
document.getElementById("hour-" + (time).toString())?.getElementsByClassName("edit-hour-code")[0].focus(); document.getElementById("hour-" + (time).toString())?.getElementsByClassName("edit-hour-code")[0].focus();
} }
@ -11,6 +13,7 @@ function selectHourComment(time: number) {
} }
export function EditableHourCode({ export function EditableHourCode({
className = "",
originalText, originalText,
hour, hour,
onSubmit, onSubmit,
@ -50,6 +53,7 @@ export function EditableHourCode({
dayId: hour.dayId, dayId: hour.dayId,
code: newCode ?? "", code: newCode ?? "",
comment: hour.comment, comment: hour.comment,
i: i
}) })
selectHourCode(i + 1); selectHourCode(i + 1);
}; };
@ -62,7 +66,7 @@ export function EditableHourCode({
</div> </div>
<input <input
className="w-8 border-b text-center edit-hour-code" className={cn("text-center edit-hour-code", className)}
style={{ style={{
background: hour.background ?? "inherit", color: hour.foreground ?? "inherit", fontFamily: "inherit", background: hour.background ?? "inherit", color: hour.foreground ?? "inherit", fontFamily: "inherit",
borderColor: hour.foreground ?? "inherit" borderColor: hour.foreground ?? "inherit"

View File

@ -54,9 +54,10 @@ export function EditableHourComment({
}; };
return ( return (
<input <input
className="w-full text-left edit-hour-comment" className="w-full text-left edit-hour-comment hover:border-b hover:border-dashed"
style={{ style={{
background: hour.background ?? "inherit", color: hour.foreground ?? "inherit", fontFamily: "inherit", background: hour.background ?? "inherit", color: hour.foreground ?? "inherit", fontFamily: "inherit",
borderColor: hour.foreground ?? "inherit"
}} }}
ref={ref} ref={ref}
value={originalText ?? ""} value={originalText ?? ""}

View File

@ -1,18 +1,18 @@
import MobileSidebarItem from "@/components/shared/sidebar/ModileSidebarItem"; import MobileSidebarItem from "@/components/shared/sidebar/ModileSidebarItem";
import HoarderLogoIcon from "@/public/icons/logo-icon.svg"; import HoarderLogoIcon from "@/public/icons/logo-icon.svg";
import { ClipboardList, Search, Tag } from "lucide-react"; import { CheckCheck, ClipboardList, GaugeCircleIcon, Home, HomeIcon, Search, Tag } from "lucide-react";
export default async function MobileSidebar() { export default async function MobileSidebar() {
return ( return (
<aside className="w-full"> <aside className="w-full">
<ul className="flex justify-between space-x-2 border-b-black px-5 py-2 pt-5"> <ul className="flex justify-between space-x-2 border-b-black px-5 py-2 pt-5">
<MobileSidebarItem <MobileSidebarItem
logo={<HoarderLogoIcon className="w-5 fill-foreground" />} logo={<Home />}
path="/dashboard/bookmarks" path="/dashboard/day/today"
/> />
<MobileSidebarItem logo={<Search />} path="/dashboard/search" /> <MobileSidebarItem logo={<Tag />} path="/dashboard/categories" />
<MobileSidebarItem logo={<ClipboardList />} path="/dashboard/lists" /> <MobileSidebarItem logo={<CheckCheck />} path="/dashboard/habits" />
<MobileSidebarItem logo={<Tag />} path="/dashboard/tags" /> <MobileSidebarItem logo={<GaugeCircleIcon />} path="/analytics" />
</ul> </ul>
</aside> </aside>
); );

View File

@ -3,7 +3,7 @@ import SidebarItem from "@/components/shared/sidebar/SidebarItem";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { api } from "@/server/api/client"; import { api } from "@/server/api/client";
import { getServerAuthSession } from "@/server/auth"; import { getServerAuthSession } from "@/server/auth";
import { Archive, Calendar, Home, Search, Tag } from "lucide-react"; import { Archive, Calendar, CheckCheck, Home, Search, Tag } from "lucide-react";
import serverConfig from "@lifetracker/shared/config"; import serverConfig from "@lifetracker/shared/config";
import AllLists from "./AllLists"; import AllLists from "./AllLists";
@ -43,6 +43,11 @@ export default async function Sidebar() {
path: "/dashboard/timeline/month", path: "/dashboard/timeline/month",
}, },
...searchItem, ...searchItem,
{
name: "Habits",
icon: <CheckCheck size={18} />,
path: "/dashboard/habits",
},
{ {
name: "Categories", name: "Categories",
icon: <Tag size={18} />, icon: <Tag size={18} />,

View File

@ -1,57 +1,96 @@
'use client';
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { getServerAuthSession } from "@/server/auth";
import { ZDay } from "@lifetracker/shared/types/days"; import { ZDay } from "@lifetracker/shared/types/days";
import EditableDayComment from "./EditableDayComment"; import { EditableHourCode } from "../hours/EditableHourCode";
import { MoodStars } from "./MoodStars"; import { getHourFromTime } from "@lifetracker/shared/utils/hours";
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 spacetime from "spacetime"; import spacetime from "spacetime";
import EditableHour from "@/components/dashboard/hours/EditableHour"; import EditableHour from "@/components/dashboard/hours/EditableHour";
import { useUpdateHour } from "@lifetracker/shared-react/hooks/days";
import { useState } from "react";
export default async function MonthView({ export default function MonthView({
dateRange, days: initialDays,
}: { }: {
dateRange: string[]; days: ZDay[];
}) { }) {
const session = await getServerAuthSession();
if (!session) { // Hacky, but it works
redirect("/"); // Remove "container" class from parent div
const parent = document.querySelector(".container");
if (parent) {
parent.classList.remove("container");
} }
const [days, setDays] = useState(initialDays);
const { mutate: updateHour, isPending } = useUpdateHour({
onSuccess: (res, req, meta) => {
const hourDay = days.find(day => day.date === req.date);
// Replace the relevant hour within hourDay
hourDay.hours[req.i] = res;
// Place the new hourDay within the days array
setDays([...days]);
},
});
return ( return (
<div className="flex flex-col gap-3"> <>
<div className="flex justify-between pr-4"> <div className="grid font-mono border-b-2" style={{ gridTemplateColumns: "repeat(26, 1fr)", borderColor: "white" }}>
<div className="flex"> <div className="flex text-center justify-center">DATE</div>
<Link <div className="flex text-center justify-center border-r-2 border-inherit">DAY</div>
href={`/dashboard/timeline/month/last`} {Array.from({ length: 24 }, (_, i) => (
className={cn( <div key={i} className={cn("flex text-center items-center justify-center",
"flex-0 items-center rounded-[inherit] px-3 py-2", i == spacetime().hour() ? "bg-white text-black" : ""
)} )}>
> {getHourFromTime(i, "all")}
<div className="flex w-full justify-between"> </div>
<ArrowLeftSquare size={18} /> ))}
</div>
</Link>
<span className="text-2xl flex-1">
{spacetime().format("{day}, {month} {date}, {year}")}
</span>
<Link
href={`/dashboard/timeline/month/next`}
className={cn(
"flex-0 items-center rounded-[inherit] px-3 py-2",
)}
>
<div className="flex w-full justify-between">
<ArrowRightSquare size={18} />
</div>
</Link>
</div>
</div> </div>
{
days.map((day, index) => (
<div
key={index}
className="grid font-mono"
style={{ gridTemplateColumns: "repeat(26, 1fr)", borderColor: "white" }}
>
<div className={cn("text-center",
spacetime(day.date).diff(spacetime.now()).hours < 24 && spacetime(day.date).diff(spacetime.now()).hours > 0 ? "bg-white text-black" : ""
)}>
{spacetime(day.date).format("{iso-month}/{date-pad}")}
</div>
<div className={cn("text-center",
spacetime(day.date).diff(spacetime.now()).hours < 24 && spacetime(day.date).diff(spacetime.now()).hours > 0 ? "bg-white text-black" : ""
)}>
{spacetime(day.date).format("{day-short}")}
</div>
{day.hours.map((hour, i) => (
<EditableHourCode
className="w-full h-full hover:cursor-default"
key={i}
originalText={hour.categoryCode}
hour={hour}
i={i}
onSubmit={updateHour}
/>
// <div key={i}
// datetime={day.date + "T" + hour.time + ":00:00"}
// className="flex items-center justify-center font-mono"
// style={{
// backgroundColor: hour.background ?? "white",
// color: hour.foreground ?? "black",
// }}
// >
// {hour.categoryCode ?? "_"}
// </div>
))}
</div>
<Separator /> ))
</div> }
</>
); );
} }

View File

@ -24,7 +24,7 @@ export function dateFromInput(input: { dateQuery: string, timezone: string }) {
throw new Error("Invalid dateQuery"); throw new Error("Invalid dateQuery");
} }
} }
console.log(`dateFromInput(${input.dateQuery}, ${input.timezone}) = ${t}`); // console.log(`dateFromInput(${input.dateQuery}, ${input.timezone}) = ${t}`);
return t; return t;
} }