ui: add protected dashboard CRUD screens and assignment docs

This commit is contained in:
Syahdan 2026-04-29 18:10:31 +07:00
parent bf42037619
commit cb0d4d7809
24 changed files with 2662 additions and 101 deletions

View file

@ -0,0 +1,103 @@
import { Button } from "@minmon/ui/components/button";
import { useMutation, useQuery } from "@tanstack/react-query";
import { createFileRoute, Link } from "@tanstack/react-router";
import { toast } from "sonner";
import { PageSection, QueryStateCard, ResourceListCard } from "@/components/dashboard-ui";
import { queryClient, trpc, trpcClient } from "@/utils/trpc";
export const Route = createFileRoute("/dashboard/notes")({
component: RouteComponent,
});
function RouteComponent() {
const notes = useQuery(trpc.notes.list.queryOptions());
const servers = useQuery(trpc.servers.list.queryOptions());
const removeNote = useMutation({
mutationFn: async (id: string) => trpcClient.notes.delete.mutate({ id }),
onSuccess: async () => {
toast.success("Note deleted");
await queryClient.invalidateQueries();
},
onError: (error) => toast.error(error.message),
});
if (notes.isLoading || servers.isLoading) {
return <QueryStateCard title="Loading notes" description="Fetching server documentation and internal notes." />;
}
if (notes.isError || servers.isError || !notes.data || !servers.data) {
return <QueryStateCard title="Notes unavailable" description="The note list could not be loaded." />;
}
const serverMap = new Map(servers.data.map((item) => [item.id, item.name]));
return (
<PageSection
title="Notes"
description="Manage documentation, incident notes, and internal reminders for each server."
action={
<Link to="/dashboard/notes/new">
<Button>New note</Button>
</Link>
}
>
<ResourceListCard
title="Server notes"
description="Each note belongs to one server and acts as lightweight internal documentation."
columns={[
{
key: "title",
header: "Title",
render: (item: (typeof notes.data)[number]) => (
<div className="grid gap-1">
<Link to="/dashboard/notes/$noteId" params={{ noteId: item.id }} className="font-medium hover:underline">
{item.title}
</Link>
<span className="text-xs text-muted-foreground line-clamp-2">{item.content}</span>
</div>
),
},
{
key: "server",
header: "Server",
render: (item: (typeof notes.data)[number]) => serverMap.get(item.serverId) || item.serverId,
},
{
key: "category",
header: "Category",
render: (item: (typeof notes.data)[number]) => item.category || "-",
},
{
key: "updatedAt",
header: "Updated",
render: (item: (typeof notes.data)[number]) => new Date(item.updatedAt).toLocaleString(),
},
{
key: "actions",
header: "Actions",
className: "w-28",
render: (item: (typeof notes.data)[number]) => (
<Button
type="button"
variant="ghost"
className="h-auto px-0 text-red-600 hover:text-red-700"
disabled={removeNote.isPending}
onClick={() => {
if (confirm(`Delete note \"${item.title}\"?`)) {
removeNote.mutate(item.id);
}
}}
>
Delete
</Button>
),
},
]}
items={notes.data}
emptyTitle="No notes yet"
emptyDescription="Add a note to document maintenance, incidents, or general server information."
/>
</PageSection>
);
}