Files
ITWorx_MCP_Hub-Public/apps/web/src/components/DataTable.tsx
T

108 lines
6.8 KiB
TypeScript

import { Fragment, useState } from "react";
import type { ResourceKind, ResourcePage, ResourceRecord } from "../contracts";
import { Modal } from "./Modal";
import { StatusPill } from "./StatusPill";
export type Density = "comfortable" | "compact";
export type ColumnKey = "name" | "status" | "project" | "summary" | "risk" | "decision" | "actions";
const ALL_COLUMNS: Record<ResourceKind, ColumnKey[]> = {
projects: ["name", "status", "project", "summary"],
servers: ["name", "status", "project", "summary"],
tools: ["name", "status", "project", "summary", "risk", "actions"],
connectors: ["name", "status", "project", "summary"],
clients: ["name", "status", "project", "summary", "actions"],
policies: ["name", "status", "project", "summary"],
audit: ["name", "status", "project", "summary", "decision"],
};
export function columnsFor(kind: ResourceKind): ColumnKey[] { return ALL_COLUMNS[kind]; }
export function DataTable({
data, kind, density, visibleColumns, onClientAction, onConfig, onTest, navigate, busyClientId,
}: {
data: ResourcePage;
kind: ResourceKind;
density: Density;
visibleColumns: ColumnKey[];
onClientAction: (id: string, action: "disable" | "revoke") => void;
onConfig: (id: string) => void;
onTest: (row: ResourceRecord) => void;
navigate?: (next: string) => void;
busyClientId?: string | null;
}) {
const show = (column: ColumnKey) => visibleColumns.includes(column);
const [confirmingAction, setConfirmingAction] = useState<{ id: string; action: "disable" | "revoke"; name: string } | null>(null);
const actions = (row: ResourceRecord) => {
if (kind === "clients" && show("actions")) {
const disabled = busyClientId === row.id;
const resume = `/clients?onboard=${encodeURIComponent(row.id)}`;
const secondary = <><button type="button" className="button button-small button-secondary" onClick={() => setConfirmingAction({ id: row.id, action: "disable", name: row.name })} disabled={disabled || row.status !== "healthy"}>Disable</button><button type="button" className="button button-small button-danger" onClick={() => setConfirmingAction({ id: row.id, action: "revoke", name: row.name })} disabled={disabled || row.status === "unavailable"}>Revoke</button>{row.onboardingStage ? <a className="button button-small button-secondary" href={resume} onClick={navigate ? (event) => { event.preventDefault(); navigate(resume); } : undefined}>Onboarding</a> : null}</>;
return <div className="inline-actions client-actions"><button type="button" className="button button-small button-secondary" disabled={disabled} onClick={() => onConfig(row.id)}>{disabled ? "Working…" : "Config"}</button><div className="desktop-client-actions">{secondary}</div><details className="mobile-client-actions"><summary>More actions</summary><div>{secondary}</div></details></div>;
}
if (kind === "tools" && show("actions")) return <div className="inline-actions"><button className="button button-small button-secondary" onClick={() => onTest(row)}>Readiness</button></div>;
return null;
};
const name = (row: ResourceRecord) => {
const href = kind === "projects" ? `/projects/${encodeURIComponent(row.id)}` : kind === "connectors" ? `/connectors/${encodeURIComponent(row.id)}` : kind === "clients" ? row.onboardingStage ? `/clients?onboard=${encodeURIComponent(row.id)}` : `/clients?q=${encodeURIComponent(row.id)}` : null;
return href ? <a className="record-link" href={href} onClick={navigate ? (event) => { event.preventDefault(); navigate(href); } : undefined}><strong className="long-name">{row.name}</strong></a> : <strong className="long-name">{row.name}</strong>;
};
return (
<Fragment>
<div className={`table-wrap desktop-data-table table-kind-${kind} density-${density}`} role="region" tabIndex={0} aria-label={`${kind} records table`}>
<table>
<caption className="sr-only">{kind} records</caption>
<thead>
<tr>
{show("name") && <th>Name</th>}
{show("status") && <th>Status</th>}
{show("project") && <th>Project</th>}
{show("summary") && <th>Summary</th>}
{kind === "tools" && show("risk") && <th>Risk</th>}
{kind === "audit" && show("decision") && <th>Decision / trace</th>}
{(kind === "clients" || kind === "tools") && show("actions") && <th>Actions</th>}
</tr>
</thead>
<tbody>
{data.rows.map((row) => (
<tr key={row.id}>
{show("name") && <td>{name(row)}<small>{row.id}</small></td>}
{show("status") && <td><StatusPill value={row.status} /></td>}
{show("project") && <td>{row.project ?? "—"}</td>}
{show("summary") && <td>{row.summary}{row.onboardingStage ? <small>{row.onboardingStage}</small> : null}</td>}
{kind === "tools" && show("risk") && <td>{row.risk ?? "R0"}</td>}
{kind === "audit" && show("decision") && <td>{row.decision ?? "unknown"}{row.traceId && <small>{row.traceId}</small>}</td>}
{(kind === "clients" || kind === "tools") && show("actions") && <td>{actions(row)}</td>}
</tr>
))}
</tbody>
</table>
</div>
<div className="mobile-record-list" aria-label={`${kind} records`}>
{data.rows.map((row) => <article className="mobile-record" key={row.id}><div className="mobile-record-heading"><div>{name(row)}<small>{row.id}</small></div>{show("status") && <StatusPill value={row.status} />}</div><dl>{show("project") && <><dt>Project</dt><dd>{row.project ?? "—"}</dd></>}{show("summary") && <><dt>Summary</dt><dd>{row.summary}{row.onboardingStage ? <small>{row.onboardingStage}</small> : null}</dd></>}{kind === "tools" && show("risk") && <><dt>Risk</dt><dd><span className="risk-label" title="Risk classification">{row.risk ?? "R0"}</span></dd></>}{kind === "audit" && show("decision") && <><dt>Decision / trace</dt><dd>{row.decision ?? "unknown"}{row.traceId && <small>{row.traceId}</small>}</dd></>}</dl>{actions(row)}</article>)}
</div>
<Modal
open={confirmingAction !== null}
title={`${confirmingAction?.action === "disable" ? "Disable" : "Revoke"} ${confirmingAction?.name}?`}
onClose={() => setConfirmingAction(null)}
>
<p>This action is logged and takes effect immediately.</p>
<div className="state-actions">
<button
className="button"
data-testid="confirm-action"
onClick={() => {
if (!confirmingAction) return;
onClientAction(confirmingAction.id, confirmingAction.action);
setConfirmingAction(null);
}}
>
Confirm
</button>
<button className="button button-secondary" onClick={() => setConfirmingAction(null)}>Cancel</button>
</div>
</Modal>
</Fragment>
);
}