Surface audit generations on dashboard audits
This commit is contained in:
@@ -4,15 +4,16 @@ import { useMemo } from "react";
|
||||
|
||||
import { useQuery } from "convex/react";
|
||||
import { FunctionReturnType } from "convex/server";
|
||||
import { Files, SquarePen } from "lucide-react";
|
||||
import { Activity, 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];
|
||||
type AuditDashboardRowsResult = FunctionReturnType<typeof api.audits.listDashboardRows>;
|
||||
type AuditRow = Extract<NonNullable<AuditDashboardRowsResult>[number], { kind: "audit" }>;
|
||||
type AuditDashboardRow = NonNullable<AuditDashboardRowsResult>[number];
|
||||
|
||||
const statusText: Record<string, string> = {
|
||||
draft: "Entwurf",
|
||||
@@ -23,14 +24,48 @@ const statusText: Record<string, string> = {
|
||||
|
||||
const fallbackStatus = "Unbekannt";
|
||||
|
||||
function formatPageCount(pages: AuditRow["checkedPages"]) {
|
||||
return `${pages.length} Seite${pages.length === 1 ? "" : "n"}`;
|
||||
const generationStageText: Record<string, string> = {
|
||||
audit_generation: "Audit-Generierung",
|
||||
classification: "Klassifikation",
|
||||
multimodalAudit: "Multimodale Analyse",
|
||||
germanCopy: "Deutsche Texte",
|
||||
qualityReview: "Qualitätsprüfung",
|
||||
};
|
||||
|
||||
function formatPageCount(pageCount: number) {
|
||||
return `${pageCount} Seite${pageCount === 1 ? "" : "n"}`;
|
||||
}
|
||||
|
||||
function getStatusLabel(status: AuditRow["status"]) {
|
||||
return statusText[status] ?? fallbackStatus;
|
||||
}
|
||||
|
||||
function getGenerationStatusLabel(
|
||||
row: Extract<AuditDashboardRow, { kind: "generation" }>,
|
||||
) {
|
||||
if (row.status === "pending") {
|
||||
return "Wartet auf Start";
|
||||
}
|
||||
|
||||
if (row.status === "failed") {
|
||||
return "Fehlgeschlagen";
|
||||
}
|
||||
|
||||
if (row.status === "canceled") {
|
||||
return "Abgebrochen";
|
||||
}
|
||||
|
||||
if (row.status === "succeeded") {
|
||||
return "Wartet auf finales Audit";
|
||||
}
|
||||
|
||||
return "Generierung läuft";
|
||||
}
|
||||
|
||||
function getStageLabel(stage: string) {
|
||||
return generationStageText[stage] ?? stage;
|
||||
}
|
||||
|
||||
function AuditsBoardLoading() {
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
@@ -51,16 +86,16 @@ function AuditsBoardLoading() {
|
||||
}
|
||||
|
||||
export function AuditsBoard() {
|
||||
const audits = useQuery(api.audits.list, { limit: 100 });
|
||||
const dashboardRows = useQuery(api.audits.listDashboardRows, { limit: 100 });
|
||||
const rows = useMemo(() => {
|
||||
if (!audits) {
|
||||
if (!dashboardRows) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...audits].sort((a, b) => b.createdAt - a.createdAt);
|
||||
}, [audits]);
|
||||
return [...dashboardRows].sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
}, [dashboardRows]);
|
||||
|
||||
if (audits === undefined) {
|
||||
if (dashboardRows === undefined) {
|
||||
return <AuditsBoardLoading />;
|
||||
}
|
||||
|
||||
@@ -75,8 +110,8 @@ export function AuditsBoard() {
|
||||
<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.
|
||||
Sobald neue Audits oder laufende Audit-Generierungen angelegt
|
||||
wurden, erscheinen sie hier als kompakte Zeilen.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
@@ -90,40 +125,70 @@ export function AuditsBoard() {
|
||||
<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">
|
||||
<section className="space-y-2 overflow-x-auto">
|
||||
<div className="grid min-w-[840px] grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_170px_150px_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>Seiten/Phase</span>
|
||||
<span className="text-right">Aktion</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{rows.map((audit: AuditRow) => (
|
||||
{rows.map((row: AuditDashboardRow) => (
|
||||
<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}
|
||||
className="grid min-w-[840px] grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_170px_150px_auto] items-center gap-2 rounded-lg border px-3 py-2 text-sm"
|
||||
key={row.id}
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="truncate font-medium">{audit.slug}</p>
|
||||
<p className="truncate font-medium">{row.title}</p>
|
||||
{row.kind === "generation" ? (
|
||||
<p className="truncate text-xs text-muted-foreground">
|
||||
Run {row.runId}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="truncate text-muted-foreground">{audit.checkedDomain}</p>
|
||||
<Badge variant="secondary">{getStatusLabel(audit.status)}</Badge>
|
||||
<p className="truncate text-muted-foreground">{row.checkedDomain}</p>
|
||||
<Badge
|
||||
className="h-auto min-h-6 justify-center whitespace-normal text-center"
|
||||
variant="secondary"
|
||||
>
|
||||
{row.kind === "audit"
|
||||
? getStatusLabel(row.status)
|
||||
: getGenerationStatusLabel(row)}
|
||||
</Badge>
|
||||
<p className="text-muted-foreground">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Files className="size-3.5" />
|
||||
{formatPageCount(audit.checkedPages)}
|
||||
{row.kind === "audit" ? (
|
||||
<>
|
||||
<Files className="size-3.5" />
|
||||
{formatPageCount(row.pageCount)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Activity className="size-3.5" />
|
||||
{getStageLabel(row.latestStage)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
{row.kind === "generation" && row.errorSummary ? (
|
||||
<span className="mt-1 block truncate text-xs">
|
||||
{row.errorSummary}
|
||||
</span>
|
||||
) : null}
|
||||
</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>
|
||||
{row.kind === "audit" ? (
|
||||
<Link
|
||||
className="inline-flex min-h-8 items-center gap-1 text-sm text-primary"
|
||||
href={row.detailHref}
|
||||
>
|
||||
<SquarePen className="size-4" />
|
||||
Öffnen
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">Pipeline</span>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user