forked from administration/panel
feat: render notes as markdown
parent
915459f955
commit
a14ed14761
|
@ -8,6 +8,9 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import remarkGfm from 'remark-gfm';
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
export default function SafetyNotesCard({ objectId, type, title }: {
|
export default function SafetyNotesCard({ objectId, type, title }: {
|
||||||
|
@ -20,6 +23,7 @@ export default function SafetyNotesCard({ objectId, type, title }: {
|
||||||
const [value, setValue] = useState<SafetyNotes | null>(null);
|
const [value, setValue] = useState<SafetyNotes | null>(null);
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSafetyNote(objectId, type)
|
fetchSafetyNote(objectId, type)
|
||||||
|
@ -39,41 +43,63 @@ export default function SafetyNotesCard({ objectId, type, title }: {
|
||||||
<CardTitle>{title ?? type.charAt(0).toUpperCase() + type.slice(1) + " notes"}</CardTitle>
|
<CardTitle>{title ?? type.charAt(0).toUpperCase() + type.slice(1) + " notes"}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Textarea
|
{
|
||||||
placeholder={
|
editing
|
||||||
error
|
? <Textarea
|
||||||
? error
|
placeholder={
|
||||||
: ready
|
error
|
||||||
? "Enter notes here... (save on unfocus)"
|
? error
|
||||||
: "Fetching notes..."
|
: ready
|
||||||
}
|
? "Enter notes here... (save on unfocus)"
|
||||||
className="!min-h-[80px] max-h-[50vh]"
|
: "Fetching notes..."
|
||||||
disabled={!ready || error != null}
|
|
||||||
value={ready ? draft : undefined} // not defaulting to undefined causes next to complain
|
|
||||||
onChange={(e) => setDraft(e.currentTarget.value)}
|
|
||||||
onBlur={async () => {
|
|
||||||
if (draft === value?.text ?? "") return;
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
toast({
|
|
||||||
title: "Updated notes",
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
toast({
|
|
||||||
title: "Failed to update notes",
|
|
||||||
description: String(err),
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
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"
|
||||||
|
remarkPlugins={[remarkGfm]}
|
||||||
|
>
|
||||||
|
{value.text}
|
||||||
|
</ReactMarkdown>
|
||||||
|
: ready
|
||||||
|
? <i>Click to add a note</i>
|
||||||
|
: <i>Fetching notes...</i>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="-my-2">
|
<CardFooter className="-my-2">
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
|
|
|
@ -39,7 +39,9 @@
|
||||||
"postcss": "8.4.27",
|
"postcss": "8.4.27",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-markdown": "^8.0.7",
|
||||||
"redis": "^4.6.7",
|
"redis": "^4.6.7",
|
||||||
|
"remark-gfm": "^3.0.1",
|
||||||
"revolt-api": "^0.6.5",
|
"revolt-api": "^0.6.5",
|
||||||
"sass": "^1.64.1",
|
"sass": "^1.64.1",
|
||||||
"tailwind-merge": "^1.14.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
|
@ -49,6 +51,7 @@
|
||||||
"ulid": "^2.3.0"
|
"ulid": "^2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"revolt.js": "7.0.0-beta.9"
|
"revolt.js": "7.0.0-beta.9"
|
||||||
}
|
}
|
||||||
|
|
754
pnpm-lock.yaml
754
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -72,5 +72,5 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
|
||||||
}
|
}
|
Loading…
Reference in New Issue