From 1d877d98eceb4603d310bd98f6b207719c5cd8c6 Mon Sep 17 00:00:00 2001 From: mptyl Date: Mon, 6 Jul 2026 18:17:29 +0200 Subject: [PATCH] feat(frontend): schema-linking types + columns modal --- frontend/src/api/types.ts | 20 +++++++ .../src/widgets/SchemaColumnsDialog.test.tsx | 48 +++++++++++++++ frontend/src/widgets/SchemaColumnsDialog.tsx | 58 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 frontend/src/widgets/SchemaColumnsDialog.test.tsx create mode 100644 frontend/src/widgets/SchemaColumnsDialog.tsx diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index db794623..3ca60f46 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -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 = diff --git a/frontend/src/widgets/SchemaColumnsDialog.test.tsx b/frontend/src/widgets/SchemaColumnsDialog.test.tsx new file mode 100644 index 00000000..e913434e --- /dev/null +++ b/frontend/src/widgets/SchemaColumnsDialog.test.tsx @@ -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( + {}} + /> + ); + 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( + {}} + onClose={() => {}} + /> + ); + expect(screen.queryByRole("checkbox")).toBeNull(); + expect(screen.getByText("cod_paz")).toBeInTheDocument(); + expect(screen.getByText("cod_paz").className).not.toContain("font-bold"); +}); diff --git a/frontend/src/widgets/SchemaColumnsDialog.tsx b/frontend/src/widgets/SchemaColumnsDialog.tsx new file mode 100644 index 00000000..997123bd --- /dev/null +++ b/frontend/src/widgets/SchemaColumnsDialog.tsx @@ -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; + onToggle: (column: string) => void; + onClose: () => void; +}): ReactElement { + return ( + { if (!o) onClose(); }}> + + {table.name} +
+
    + {table.columns.map((col) => { + const on = selected.has(col.name); + const strong = on && !readOnly; + return ( +
  • + {!readOnly && ( + onToggle(col.name)} + aria-label={col.name} + /> + )} +
    +
    + {col.name} +
    + {col.description ? ( +

    + {col.description} +

    + ) : null} +
    +
  • + ); + })} +
+
+
+
+ ); +}