feat(frontend): schema-linking types + columns modal

This commit is contained in:
2026-07-06 23:25:00 +02:00
committed by Marco Pancotti
parent 9de87d2d51
commit 1d877d98ec
3 changed files with 126 additions and 0 deletions
+20
View File
@@ -7,6 +7,24 @@ export interface WidgetOption {
opens?: WidgetDescriptor;
}
export interface SchemaColumn {
name: string;
description?: string;
type?: string;
pk?: boolean;
suggested?: boolean;
}
export interface SchemaTable {
id: string;
name: string;
kind: "promote" | "exclude";
recommended?: boolean;
description?: string;
rationale?: string;
columns: SchemaColumn[];
}
export interface WidgetDescriptor {
id: string;
schema_version?: number;
@@ -21,6 +39,7 @@ export interface WidgetDescriptor {
artifact?: { kind: string; content?: string; [k: string]: unknown };
level?: "info" | "warning" | "error";
text?: string;
tables?: SchemaTable[];
[k: string]: unknown;
}
@@ -31,6 +50,7 @@ export interface UiResponse {
text?: string;
decision?: { type: string };
control?: string;
tables?: { id: string; enacted: boolean; columns?: string[] }[];
}
export type StreamEvent =
@@ -0,0 +1,48 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { SchemaColumnsDialog } from "./SchemaColumnsDialog";
import type { SchemaTable } from "../api/types";
const table: SchemaTable = {
id: "t-pat",
name: "dim_patient",
kind: "promote",
columns: [
{ name: "cod_paz", description: "Codice paziente", suggested: true },
{ name: "nome", description: "Nome", suggested: false },
],
};
test("selected column renders bold; unselected is normal; toggle fires", async () => {
const onToggle = vi.fn();
render(
<SchemaColumnsDialog
table={table}
open
readOnly={false}
selected={new Set(["cod_paz"])}
onToggle={onToggle}
onClose={() => {}}
/>
);
expect(screen.getByText("cod_paz").className).toContain("font-bold");
expect(screen.getByText("nome").className).not.toContain("font-bold");
await userEvent.click(screen.getByRole("checkbox", { name: "nome" }));
expect(onToggle).toHaveBeenCalledWith("nome");
});
test("readOnly renders no checkboxes and no bold", () => {
render(
<SchemaColumnsDialog
table={table}
open
readOnly
selected={new Set()}
onToggle={() => {}}
onClose={() => {}}
/>
);
expect(screen.queryByRole("checkbox")).toBeNull();
expect(screen.getByText("cod_paz")).toBeInTheDocument();
expect(screen.getByText("cod_paz").className).not.toContain("font-bold");
});
@@ -0,0 +1,58 @@
import type { ReactElement } from "react";
import type { SchemaTable } from "../api/types";
import { Dialog, DialogContent, DialogTitle } from "../components/ui/dialog";
export function SchemaColumnsDialog({
table,
open,
readOnly,
selected,
onToggle,
onClose,
}: {
table: SchemaTable;
open: boolean;
readOnly: boolean;
selected: Set<string>;
onToggle: (column: string) => void;
onClose: () => void;
}): ReactElement {
return (
<Dialog open={open} onOpenChange={(o) => { if (!o) onClose(); }}>
<DialogContent className="grid max-h-[70vh] w-[70vw] max-w-[46rem] grid-rows-[auto_1fr] gap-3 sm:max-w-[46rem]">
<DialogTitle className="font-mono text-sm">{table.name}</DialogTitle>
<div className="min-h-0 overflow-auto">
<ul className="flex flex-col divide-y divide-border/50">
{table.columns.map((col) => {
const on = selected.has(col.name);
const strong = on && !readOnly;
return (
<li key={col.name} className="flex items-start gap-3 py-2">
{!readOnly && (
<input
type="checkbox"
className="mt-0.5 size-4 shrink-0 accent-[oklch(var(--primary))]"
checked={on}
onChange={() => onToggle(col.name)}
aria-label={col.name}
/>
)}
<div className="min-w-0">
<div className={`font-mono text-sm ${strong ? "font-bold text-foreground" : "text-foreground/90"}`}>
{col.name}
</div>
{col.description ? (
<p className={`text-sm ${strong ? "font-semibold text-foreground" : "text-muted-foreground"}`}>
{col.description}
</p>
) : null}
</div>
</li>
);
})}
</ul>
</div>
</DialogContent>
</Dialog>
);
}