135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
|
|
import { useQuery } from "convex/react";
|
|
import { FunctionReturnType } from "convex/server";
|
|
import { Files, SquarePen } from "lucide-react";
|
|
import Link from "next/link";
|
|
|
|
import { api } from "@/convex/_generated/api";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
type AuditsListResult = FunctionReturnType<typeof api.audits.list>;
|
|
type AuditRow = NonNullable<AuditsListResult>[number];
|
|
|
|
const statusText: Record<string, string> = {
|
|
draft: "Entwurf",
|
|
approved: "Freigegeben",
|
|
published: "Veröffentlicht",
|
|
deactivated: "Deaktiviert",
|
|
};
|
|
|
|
const fallbackStatus = "Unbekannt";
|
|
|
|
function formatPageCount(pages: AuditRow["checkedPages"]) {
|
|
return `${pages.length} Seite${pages.length === 1 ? "" : "n"}`;
|
|
}
|
|
|
|
function getStatusLabel(status: AuditRow["status"]) {
|
|
return statusText[status] ?? fallbackStatus;
|
|
}
|
|
|
|
function AuditsBoardLoading() {
|
|
return (
|
|
<section className="space-y-4">
|
|
<header className="space-y-2">
|
|
<p className="text-sm text-muted-foreground">Interne Audit-Übersicht</p>
|
|
<h1 className="text-2xl font-semibold tracking-normal">Audits</h1>
|
|
<p className="text-sm text-muted-foreground">Audits werden geladen...</p>
|
|
</header>
|
|
<div className="rounded-lg border">
|
|
<div className="grid gap-2 p-3">
|
|
{Array.from({ length: 4 }, (_, index) => (
|
|
<Skeleton className="h-20 rounded-md" key={index} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export function AuditsBoard() {
|
|
const audits = useQuery(api.audits.list, { limit: 100 });
|
|
const rows = useMemo(() => {
|
|
if (!audits) {
|
|
return [];
|
|
}
|
|
|
|
return [...audits].sort((a, b) => b.createdAt - a.createdAt);
|
|
}, [audits]);
|
|
|
|
if (audits === undefined) {
|
|
return <AuditsBoardLoading />;
|
|
}
|
|
|
|
if (rows.length === 0) {
|
|
return (
|
|
<section className="space-y-4">
|
|
<header className="space-y-2">
|
|
<p className="text-sm text-muted-foreground">Interne Audit-Übersicht</p>
|
|
<h1 className="text-2xl font-semibold tracking-normal">Audits</h1>
|
|
</header>
|
|
|
|
<article className="rounded-lg border p-4">
|
|
<h2 className="text-sm font-medium">Noch keine Audits</h2>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
Sobald neue Audits angelegt wurden, erscheinen sie hier als kompakte
|
|
Zeilen.
|
|
</p>
|
|
</article>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<section className="space-y-4">
|
|
<header className="space-y-2">
|
|
<p className="text-sm text-muted-foreground">Interne Audit-Übersicht</p>
|
|
<h1 className="text-2xl font-semibold tracking-normal">Audits</h1>
|
|
</header>
|
|
|
|
<section className="space-y-2">
|
|
<div className="grid grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_120px_120px_auto] gap-2 rounded-md border bg-muted/40 px-3 py-2 text-xs font-medium text-muted-foreground">
|
|
<span>Slug</span>
|
|
<span>Domain</span>
|
|
<span>Status</span>
|
|
<span>Seitenanzahl</span>
|
|
<span className="text-right">Aktion</span>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{rows.map((audit: AuditRow) => (
|
|
<article
|
|
className="grid grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_120px_120px_auto] items-center gap-2 rounded-lg border px-3 py-2 text-sm"
|
|
key={audit._id}
|
|
>
|
|
<div className="min-w-0">
|
|
<p className="truncate font-medium">{audit.slug}</p>
|
|
</div>
|
|
<p className="truncate text-muted-foreground">{audit.checkedDomain}</p>
|
|
<Badge variant="secondary">{getStatusLabel(audit.status)}</Badge>
|
|
<p className="text-muted-foreground">
|
|
<span className="inline-flex items-center gap-1">
|
|
<Files className="size-3.5" />
|
|
{formatPageCount(audit.checkedPages)}
|
|
</span>
|
|
</p>
|
|
<div className="flex justify-end">
|
|
<Link
|
|
className="inline-flex min-h-8 items-center gap-1 text-sm text-primary"
|
|
href={`/dashboard/audits/${audit._id}`}
|
|
>
|
|
<SquarePen className="size-4" />
|
|
Öffnen
|
|
</Link>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</section>
|
|
);
|
|
}
|