forked from administration/panel
133 lines
3.7 KiB
TypeScript
133 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Textarea } from "../ui/textarea";
|
|
import { toast } from "../ui/use-toast";
|
|
import { SafetyNotes, fetchSafetyNote, updateSafetyNote } from "@/lib/db";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "../ui/card";
|
|
import { useSession } from "next-auth/react";
|
|
import dayjs from "dayjs";
|
|
import relativeTime from "dayjs/plugin/relativeTime";
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
|
|
dayjs.extend(relativeTime);
|
|
|
|
export default function SafetyNotesCard({
|
|
objectId,
|
|
type,
|
|
title,
|
|
}: {
|
|
objectId: string;
|
|
type: SafetyNotes["_id"]["type"];
|
|
title?: string;
|
|
}) {
|
|
const session = useSession();
|
|
const [draft, setDraft] = useState("");
|
|
const [value, setValue] = useState<SafetyNotes | null>(null);
|
|
const [ready, setReady] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [editing, setEditing] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchSafetyNote(objectId, type)
|
|
.then((note) => {
|
|
setDraft(note?.text || "");
|
|
setValue(note);
|
|
setReady(true);
|
|
})
|
|
.catch((e) => {
|
|
setError(String(e));
|
|
});
|
|
}, [objectId, type]);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
{title ?? type.charAt(0).toUpperCase() + type.slice(1) + " notes"}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{editing ? (
|
|
<Textarea
|
|
rows={8}
|
|
placeholder={
|
|
error
|
|
? error
|
|
: ready
|
|
? "Enter notes here... (save on unfocus)"
|
|
: "Fetching notes..."
|
|
}
|
|
className="!min-h-[80px] max-h-[50vh]"
|
|
disabled={!ready || error != null}
|
|
value={ready ? draft : undefined} // not defaulting to undefined causes next to complain
|
|
autoFocus
|
|
onChange={(e) => setDraft(e.currentTarget.value)}
|
|
onBlur={async () => {
|
|
if (draft === value?.text ?? "") return setEditing(false);
|
|
|
|
try {
|
|
await updateSafetyNote(objectId, type, draft);
|
|
setValue({
|
|
_id: { id: objectId, type: type },
|
|
edited_at: new Date(Date.now()),
|
|
edited_by: session.data?.user?.email || "",
|
|
text: draft,
|
|
});
|
|
setEditing(false);
|
|
toast({
|
|
title: "Updated notes",
|
|
});
|
|
} catch (err) {
|
|
setEditing(false);
|
|
toast({
|
|
title: "Failed to update notes",
|
|
description: String(err),
|
|
variant: "destructive",
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
) : (
|
|
<div onClick={() => setEditing(true)}>
|
|
{error ? (
|
|
<>{error}</>
|
|
) : value?.text ? (
|
|
<ReactMarkdown
|
|
className="prose prose-a:text-[#fd6671] prose-img:max-h-96 max-w-none"
|
|
remarkPlugins={[remarkGfm]}
|
|
>
|
|
{value.text}
|
|
</ReactMarkdown>
|
|
) : ready ? (
|
|
<i>Click to add a note</i>
|
|
) : (
|
|
<i>Fetching notes...</i>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
<CardFooter className="-my-2">
|
|
<CardDescription>
|
|
{value ? (
|
|
<>
|
|
Last edited {dayjs(value.edited_at).fromNow()} by{" "}
|
|
{value.edited_by}
|
|
</>
|
|
) : (
|
|
<>No object note set</>
|
|
)}
|
|
</CardDescription>
|
|
</CardFooter>
|
|
</Card>
|
|
);
|
|
}
|