2023-04-04 09:11:34 -06:00
|
|
|
import { useEffect, useState } from "react";
|
2023-04-04 15:44:06 -06:00
|
|
|
import { ago } from "../utils/ago";
|
|
|
|
import { TimerResponse, Friend, TimersFilter } from "../utils/types";
|
|
|
|
|
|
|
|
const replaceReferencedFriendsInName = (
|
|
|
|
name: string,
|
|
|
|
referencedFriends: Friend[],
|
|
|
|
onSelect: (select?: TimersFilter) => void
|
|
|
|
) => {
|
|
|
|
const friendIdToFriend = referencedFriends.reduce(
|
|
|
|
(friendMap: Record<string, Friend>, friend) => {
|
|
|
|
friendMap[friend.id.toString()] = friend;
|
|
|
|
return friendMap;
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
);
|
2023-04-04 09:11:34 -06:00
|
|
|
|
2023-04-04 15:44:06 -06:00
|
|
|
return name.split(/(@\<\d+\>)/g).map((s: string) => {
|
2023-04-04 13:27:33 -06:00
|
|
|
const matches = /@\<(\d+)\>/g.exec(s);
|
|
|
|
if (matches) {
|
|
|
|
const [_match, id] = matches;
|
|
|
|
const name = friendIdToFriend[id].name;
|
|
|
|
|
2023-04-04 15:44:06 -06:00
|
|
|
return <a onClick={() => onSelect({ friendId: Number(id) })}>{name}</a>;
|
2023-04-04 13:27:33 -06:00
|
|
|
}
|
|
|
|
return s;
|
|
|
|
});
|
2023-04-04 09:33:14 -06:00
|
|
|
};
|
|
|
|
|
2023-04-05 00:30:03 -06:00
|
|
|
const refreshTimer = (id: number) =>
|
|
|
|
fetch(`/api/timers/${id}/refresh`, {
|
|
|
|
method: "POST",
|
|
|
|
});
|
|
|
|
|
2023-04-04 15:44:06 -06:00
|
|
|
export type TimerCardProps = {
|
|
|
|
timer: TimerResponse;
|
|
|
|
onSelect: (select?: TimersFilter) => void;
|
|
|
|
};
|
|
|
|
|
|
|
|
export default function TimerCard({ timer, onSelect }: TimerCardProps) {
|
2023-04-05 00:30:03 -06:00
|
|
|
const [since, setSince] = useState<string>("");
|
2023-04-04 09:11:34 -06:00
|
|
|
|
|
|
|
useEffect(() => {
|
2023-04-05 00:30:03 -06:00
|
|
|
const start = new Date(timer.start);
|
2023-04-04 15:44:06 -06:00
|
|
|
let updateTimersInterval: ReturnType<typeof setInterval>;
|
2023-04-05 00:30:03 -06:00
|
|
|
const msTillNextSecond = 1000 - (start.getTime() % 1000);
|
|
|
|
|
|
|
|
setSince(ago(start));
|
2023-04-04 09:11:34 -06:00
|
|
|
|
|
|
|
setTimeout(() => {
|
2023-04-05 00:30:03 -06:00
|
|
|
updateTimersInterval = setInterval(() => setSince(ago(start)), 1_000);
|
2023-04-04 09:11:34 -06:00
|
|
|
}, msTillNextSecond);
|
|
|
|
|
|
|
|
return () => clearInterval(updateTimersInterval);
|
2023-04-05 00:30:03 -06:00
|
|
|
}, [timer.start]);
|
2023-04-04 09:11:34 -06:00
|
|
|
|
|
|
|
return (
|
2023-04-05 00:30:03 -06:00
|
|
|
<div className="card grid-card">
|
|
|
|
<div>
|
|
|
|
<header>
|
|
|
|
<h4 className="is-center">
|
|
|
|
<code>{since || "..."}</code>
|
|
|
|
</h4>
|
|
|
|
</header>
|
|
|
|
<p>
|
|
|
|
{replaceReferencedFriendsInName(
|
|
|
|
timer.name,
|
|
|
|
timer.referenced_friends,
|
|
|
|
onSelect
|
|
|
|
).map((element: JSX.Element | string, i: number) => (
|
|
|
|
<span style={{ overflowWrap: "anywhere", hyphens: "auto" }} key={i}>
|
|
|
|
{element}
|
|
|
|
</span>
|
|
|
|
))}
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div className="timer-metadata text-grey italic">
|
|
|
|
<div>
|
|
|
|
<a
|
|
|
|
onClick={() =>
|
|
|
|
onSelect({ friendId: timer.timer_refreshes[0].refreshed_by.id })
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{" "}
|
|
|
|
{timer.created_by.name}
|
|
|
|
</a>{" "}
|
|
|
|
is tracking this
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
{timer.timer_refreshes && timer.timer_refreshes.length ? (
|
|
|
|
<span>
|
|
|
|
<a
|
|
|
|
onClick={() =>
|
|
|
|
onSelect({
|
|
|
|
friendId: timer.timer_refreshes[0].refreshed_by.id,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{timer.timer_refreshes[0].refreshed_by.name}
|
|
|
|
</a>{" "}
|
|
|
|
refreshed it last
|
|
|
|
</span>
|
|
|
|
) : (
|
|
|
|
"has not yet been refreshed..."
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<button
|
|
|
|
onClick={() => refreshTimer(timer.id)}
|
|
|
|
className="button outline"
|
|
|
|
>
|
|
|
|
Refresh
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-04-04 09:11:34 -06:00
|
|
|
);
|
2023-04-03 23:14:07 -06:00
|
|
|
}
|