183 lines
6.9 KiB
TypeScript
183 lines
6.9 KiB
TypeScript
"use client";
|
|
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { toast } from "@/components/ui/use-toast";
|
|
import { cn } from "@/lib/utils";
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
import { useUpdateHour } from "@lifetracker/shared-react/hooks/days";
|
|
import { EditableText } from "@/components/dashboard/EditableText";
|
|
import { format } from "date-fns";
|
|
import { TZDate } from "@date-fns/tz";
|
|
import { ZHour } from "@lifetracker/shared/types/days";
|
|
import { MessageCircle, Pencil, Plus } from "lucide-react";
|
|
import { ButtonWithTooltip } from "@/components/ui/button";
|
|
import { EditableHourCode } from "./EditableHourCode";
|
|
import { EditableHourComment } from "./EditableHourComment";
|
|
import spacetime from 'spacetime';
|
|
import { eq, is } from "drizzle-orm";
|
|
import HourMeasurementsDialog from "@/components/dashboard/hours/HourMeasurementsDialog";
|
|
import { ActionButtonWithTooltip } from "@/components/ui/action-button";
|
|
import { ZMetric } from "@lifetracker/shared/types/metrics";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Icon } from "@/components/ui/icon";
|
|
import { titleCase } from "title-case";
|
|
import { useDecrementCount } from "@lifetracker/shared-react/hooks/measurements";
|
|
|
|
export default function EditableHour({
|
|
hour: initialHour,
|
|
i,
|
|
metrics,
|
|
className,
|
|
}: {
|
|
hour: ZHour,
|
|
i: number,
|
|
metrics: ZMetric[],
|
|
className?: string;
|
|
}) {
|
|
const [hour, setHour] = useState(initialHour);
|
|
const { mutate: updateHour, isPending } = useUpdateHour({
|
|
onSuccess: (res, req, meta) => {
|
|
const { categoryCode: oldCode, comment: oldComment } = hour;
|
|
const newHour = {
|
|
categoryCode: parseInt(req.code!),
|
|
comment: oldComment,
|
|
...res,
|
|
};
|
|
setHour(newHour);
|
|
// Only show toast if client screen is larger than mobile
|
|
if (window.innerWidth > 640) {
|
|
toast({
|
|
description: "Hour updated!",
|
|
});
|
|
}
|
|
},
|
|
});
|
|
const { mutate: decrementCount } = useDecrementCount({
|
|
onSuccess: (res, req) => {
|
|
const oldMeasurementIndex = hour.measurements.findIndex(m => m.metricId === req.metricId);
|
|
let newMeasurements;
|
|
|
|
if (oldMeasurementIndex !== -1) {
|
|
if (res === undefined) {
|
|
// Remove the measurement if res is undefined
|
|
newMeasurements = [
|
|
...hour.measurements.slice(0, oldMeasurementIndex),
|
|
...hour.measurements.slice(oldMeasurementIndex + 1)
|
|
];
|
|
} else {
|
|
// Update the measurement
|
|
newMeasurements = [
|
|
...hour.measurements.slice(0, oldMeasurementIndex),
|
|
res,
|
|
...hour.measurements.slice(oldMeasurementIndex + 1)
|
|
];
|
|
}
|
|
} else {
|
|
// Add the new measurement
|
|
newMeasurements = [...hour.measurements, res];
|
|
}
|
|
|
|
const newHour = {
|
|
...hour,
|
|
measurements: newMeasurements,
|
|
};
|
|
|
|
setHour(newHour);
|
|
toast({
|
|
description: res === undefined ? "Measurement removed!" : "Measurement updated!",
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
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) {
|
|
const now = new TZDate();
|
|
const isCurrentHour = ((now.getHours() - localDateTime.hour()) == 0);
|
|
const isToday = (localDateTime.format("iso-short") == format(now, "yyyy-MM-dd"));
|
|
return isToday && isCurrentHour;
|
|
}
|
|
|
|
function reload(newHour: ZHour) {
|
|
setHour(newHour);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
data-hourid={hour.id}
|
|
className={cn(
|
|
"p-4 grid justify-between",
|
|
)}
|
|
style={{
|
|
background: hour.background!, color: hour.foreground!, fontFamily: "inherit",
|
|
gridTemplateColumns: window.innerWidth > 640 ? "50px 100px 3fr 2fr" : "50px 100px 1fr", // Known issue: This won't work if the screen is resized, only on reload
|
|
}}
|
|
>
|
|
<span className="text-right">
|
|
{/* {isActiveHour(hour) && "--> "} */}
|
|
{hour.datetime}
|
|
</span>
|
|
<div className="flex justify-center">
|
|
<EditableHourCode
|
|
className={"w-8 border-b"}
|
|
originalText={hour.categoryCode}
|
|
hour={hour}
|
|
onSubmit={updateHour}
|
|
i={i}
|
|
/>
|
|
</div>
|
|
<span className="hidden sm:block">
|
|
{hour.categoryCode != undefined ?
|
|
<EditableHourComment
|
|
originalText={hour.comment ?? hour.categoryName}
|
|
hour={hour}
|
|
onSubmit={updateHour}
|
|
i={i}
|
|
/>
|
|
: ""}
|
|
</span>
|
|
<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="hidden sm:flex items-center justify-end">
|
|
|
|
{hour.categoryCode != undefined ?
|
|
|
|
<div className="flex items-center gap-2">
|
|
{hour.measurements?.map(m =>
|
|
Array.from({ length: m.value }).map((_, index) =>
|
|
<div key={`${m.id}-${index}`} className="hover:cursor-no-drop" onClick={(e) => {
|
|
decrementCount({ metricId: m.metricId, hourId: hour.id });
|
|
}}>
|
|
<Icon name={titleCase(m.icon)} size={24} color={hour.foreground} />
|
|
</div>
|
|
)
|
|
)}
|
|
{(hour.measurements.length > 0) ?
|
|
(<span className="mx-2 opacity-50">|</span>)
|
|
: ""
|
|
}
|
|
|
|
<HourMeasurementsDialog hour={hour} metrics={metrics} reload={reload}>
|
|
<Plus size={16} className="opacity-50 hover:cursor-pointer" />
|
|
</HourMeasurementsDialog>
|
|
</div>
|
|
: ""}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |