feat: redesign persisted session summaries

This commit is contained in:
2026-07-23 15:35:31 +02:00
parent 12c7a976d7
commit 0db85e4e4e
12 changed files with 1090 additions and 71 deletions
+51 -19
View File
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from "react";
import { Check, Copy, TriangleAlert } from "lucide-react";
import { highlightSql } from "./highlight";
import { statusBadgeClass } from "./statusBadge";
@@ -21,6 +22,7 @@ const STATUS_LABELS: Record<string, string> = {
function Block({ block }: { block: SqlBlock }) {
const [open, setOpen] = useState(true);
const [html, setHtml] = useState<string>("");
const [copyState, setCopyState] = useState<"idle" | "copied" | "failed">("idle");
useEffect(() => {
let cancelled = false;
@@ -37,28 +39,58 @@ function Block({ block }: { block: SqlBlock }) {
? statusBadgeClass(block.testStatus)
: "";
async function copySql() {
try {
if (!navigator.clipboard?.writeText) throw new Error("Clipboard unavailable");
await navigator.clipboard.writeText(block.sql);
setCopyState("copied");
} catch {
setCopyState("failed");
}
}
return (
<div className="sql-block rounded-lg border border-border/70 mb-2">
<button
className="w-full flex items-center gap-2 px-3 py-2 text-left font-mono text-sm hover:bg-muted/30"
onClick={() => setOpen((o) => !o)}
aria-expanded={open}
>
<span className="select-none">{open ? "▾" : "▸"}</span>
<span className="font-semibold">{block.name}</span>
{block.fields !== undefined && (
<span className="text-muted-foreground text-xs">
{block.fields} campi
<div className="flex items-stretch">
<button
className="flex min-w-0 flex-1 items-center gap-2 px-3 py-2 text-left font-mono text-sm hover:bg-muted/30"
onClick={() => setOpen((o) => !o)}
aria-expanded={open}
>
<span className="select-none">{open ? "▾" : "▸"}</span>
<span className="truncate font-semibold">{block.name}</span>
{block.fields !== undefined && (
<span className="shrink-0 text-xs text-muted-foreground">
{block.fields} campi
</span>
)}
{badge && (
<span
className={`ml-auto shrink-0 rounded-md px-1.5 py-0.5 text-xs ${badgeClass}`}
>
{badge}
</span>
)}
</button>
<button
type="button"
aria-label={`Copy SQL for ${block.name}`}
title="Copy SQL"
onClick={copySql}
className="flex shrink-0 items-center gap-1.5 border-l border-border/70 px-2.5 text-xs text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground"
>
{copyState === "copied" ? (
<Check className="size-3.5 text-[oklch(0.47_0.12_165)]" aria-hidden="true" />
) : copyState === "failed" ? (
<TriangleAlert className="size-3.5 text-destructive" aria-hidden="true" />
) : (
<Copy className="size-3.5" aria-hidden="true" />
)}
<span role="status" aria-live="polite">
{copyState === "copied" ? "Copied" : copyState === "failed" ? "Copy failed" : ""}
</span>
)}
{badge && (
<span
className={`ml-auto text-xs px-1.5 py-0.5 rounded-md ${badgeClass}`}
>
{badge}
</span>
)}
</button>
</button>
</div>
{open && (
<div className="sql-block-body px-3 pb-3">