refactor(webui): use central data grids

This commit is contained in:
2026-07-21 12:06:44 +02:00
parent b4ef4cd038
commit 0c9997593c

View File

@@ -3,6 +3,7 @@ import { Link, useLocation } from "react-router-dom";
import { ChevronDown, ChevronRight, RefreshCw } from "lucide-react";
import {
Button,
DataGrid,
DismissibleAlert,
ExplorerTree,
LoadingFrame,
@@ -12,7 +13,8 @@ import {
adminErrorMessage,
useGuardedNavigate,
usePlatformLanguage,
type ApiSettings
type ApiSettings,
type DataGridColumn
} from "@govoplan/core-webui";
import {
fetchDocsContext,
@@ -404,34 +406,18 @@ function ReferenceDetails({ topic, showTechnical, documentationType, topicById }
}
function ReferenceFieldTable({ id, fields, showTechnical }: { id: string; fields: Record<string, unknown>[]; showTechnical: boolean }) {
const columns: DataGridColumn<Record<string, unknown>>[] = [
{ id: "field", header: "i18n:govoplan-docs.field.7558c082", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (field) => metadataString(field, "label"), render: (field) => <strong>{metadataString(field, "label")}</strong> },
{ id: "meaning", header: "i18n:govoplan-docs.meaning.584d8aa0", width: "minmax(260px, 1.3fr)", minWidth: 220, resizable: true, filterable: true, value: (field) => [metadataString(field, "user_description"), metadataString(field, "admin_description"), metadataString(field, "provenance")].join(" "), render: (field) => <div>{metadataString(field, "user_description")}{showTechnical && metadataString(field, "admin_description") && <span className="muted block">{metadataString(field, "admin_description")}</span>}{showTechnical && metadataString(field, "provenance") && <span className="muted block">{metadataString(field, "provenance")}</span>}</div> },
...(showTechnical ? [
{ id: "api", header: "i18n:govoplan-docs.api_mapping.e969f6f7", width: "minmax(190px, .8fr)", minWidth: 170, resizable: true, filterable: true, value: (field: Record<string, unknown>) => `${metadataString(field, "api_path")} ${metadataString(field, "api_field")}`, render: (field: Record<string, unknown>) => <div>{metadataString(field, "api_path")}<span className="muted block">{metadataString(field, "api_field")}</span></div> },
{ id: "permission", header: "i18n:govoplan-docs.permission.d71c9448", width: 190, filterable: true, value: (field: Record<string, unknown>) => metadataString(field, "permission_scope") || "-", render: (field: Record<string, unknown>) => metadataString(field, "permission_scope") || "-" }
] satisfies DataGridColumn<Record<string, unknown>>[] : []),
{ id: "validation", header: "i18n:govoplan-docs.validation.ef7f6d9c", width: "minmax(180px, .7fr)", minWidth: 160, resizable: true, filterable: true, value: (field) => metadataString(field, "validation") || "-", render: (field) => metadataString(field, "validation") || "-" }
];
return (
<div className="admin-table-wrap" id={id}>
<table className="admin-table">
<thead>
<tr>
<th>i18n:govoplan-docs.field.7558c082</th>
<th>i18n:govoplan-docs.meaning.584d8aa0</th>
{showTechnical && <th>i18n:govoplan-docs.api_mapping.e969f6f7</th>}
{showTechnical && <th>i18n:govoplan-docs.permission.d71c9448</th>}
<th>i18n:govoplan-docs.validation.ef7f6d9c</th>
</tr>
</thead>
<tbody>
{fields.map((field) =>
<tr key={metadataString(field, "field_id") || metadataString(field, "label")}>
<td><strong>{metadataString(field, "label")}</strong></td>
<td>
{metadataString(field, "user_description")}
{showTechnical && metadataString(field, "admin_description") && <span className="muted block">{metadataString(field, "admin_description")}</span>}
{showTechnical && metadataString(field, "provenance") && <span className="muted block">{metadataString(field, "provenance")}</span>}
</td>
{showTechnical && <td>{metadataString(field, "api_path")}<span className="muted block">{metadataString(field, "api_field")}</span></td>}
{showTechnical && <td>{metadataString(field, "permission_scope") || "-"}</td>}
<td>{metadataString(field, "validation") || "-"}</td>
</tr>
)}
</tbody>
</table>
<div id={id}>
<DataGrid id={`${id}-grid`} rows={fields} columns={columns} getRowKey={(field) => metadataString(field, "field_id") || metadataString(field, "label")} />
</div>
);
}
@@ -861,49 +847,29 @@ function metadataRecords(metadata: Record<string, unknown>, key: string): Record
function ModuleTable({ modules }: { modules: DocsModule[] }) {
if (!modules.length) return <p className="muted">i18n:govoplan-docs.no_configured_modules_found.f6f9ce24</p>;
const columns: DataGridColumn<DocsModule>[] = [
{ id: "module", header: "i18n:govoplan-docs.module.b8ff0289", width: "minmax(220px, 1fr)", minWidth: 190, resizable: true, sortable: true, filterable: true, value: (module) => `${module.name} ${module.id} ${module.version}`, render: (module) => <div><strong>{module.name}</strong><span className="muted block">{module.id} {module.version}</span></div> },
{ id: "routes", header: "i18n:govoplan-docs.routes.03730e58", width: 170, sortable: true, value: (module) => module.route_count, render: (module) => <>{module.route_count} i18n:govoplan-docs.route.200e2a66 {module.nav_count} nav</> },
{ id: "permissions", header: "i18n:govoplan-docs.permissions.d06d5557", width: 130, sortable: true, value: (module) => module.permission_count },
{ id: "frontend", header: "i18n:govoplan-docs.frontend.152d1cf2", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (module) => module.frontend_package || "-", render: (module) => module.frontend_package || "-" },
{ id: "capabilities", header: "i18n:govoplan-docs.capabilities.ca09c54b", width: "minmax(260px, 1.2fr)", minWidth: 220, resizable: true, filterable: true, value: (module) => module.capabilities.join(" "), render: (module) => <div>{module.capabilities.length ? module.capabilities.join(", ") : "-"}{module.documentation_count || module.documentation_provider_count ? <span className="muted block">{module.documentation_count} i18n:govoplan-docs.docs.5dc1e9b8 {module.documentation_provider_count} provider</span> : null}</div> }
];
return (
<div className="admin-table-wrap">
<table className="admin-table">
<thead>
<tr><th>i18n:govoplan-docs.module.b8ff0289</th><th>i18n:govoplan-docs.routes.03730e58</th><th>i18n:govoplan-docs.permissions.d06d5557</th><th>i18n:govoplan-docs.frontend.152d1cf2</th><th>i18n:govoplan-docs.capabilities.ca09c54b</th></tr>
</thead>
<tbody>
{modules.map((module) =>
<tr key={module.id}>
<td><strong>{module.name}</strong><span className="muted block">{module.id} {module.version}</span></td>
<td>{module.route_count} i18n:govoplan-docs.route.200e2a66 {module.nav_count} nav</td>
<td>{module.permission_count}</td>
<td>{module.frontend_package || "-"}</td>
<td>{module.capabilities.length ? module.capabilities.join(", ") : "-"}{module.documentation_count || module.documentation_provider_count ? <span className="muted block">{module.documentation_count} i18n:govoplan-docs.docs.5dc1e9b8 {module.documentation_provider_count} provider</span> : null}</td>
</tr>
)}
</tbody>
</table>
</div>
<DataGrid id="docs-configured-modules" rows={modules} columns={columns} getRowKey={(module) => module.id} />
);
}
function RouteTable({ routes, emptyText }: { routes: DocsRoute[]; emptyText: string }) {
if (!routes.length) return <p className="muted">{emptyText}</p>;
const columns: DataGridColumn<DocsRoute>[] = [
{ id: "route", header: "i18n:govoplan-docs.route.4999528e", width: "minmax(240px, 1fr)", minWidth: 200, resizable: true, sortable: true, filterable: true, value: (route) => `${route.label} ${route.path}`, render: (route) => <div><strong>{route.label}</strong><span className="muted block">{route.path}</span></div> },
{ id: "module", header: "i18n:govoplan-docs.module.b8ff0289", width: 160, sortable: true, filterable: true, value: (route) => route.module_id },
{ id: "source", header: "i18n:govoplan-docs.source.6da13add", width: 160, sortable: true, filterable: true, value: (route) => route.source },
{ id: "requirements", header: "i18n:govoplan-docs.requirements.09a428f9", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (route) => routeRequirements(route) },
{ id: "status", header: "i18n:govoplan-docs.status.bae7d5be", width: 160, sortable: true, filterable: true, value: (route) => route.visible ? "visible" : route.reason, render: (route) => <StatusBadge status={route.visible ? "success" : "warning"} label={route.visible ? "visible" : route.reason} /> }
];
return (
<div className="admin-table-wrap">
<table className="admin-table">
<thead>
<tr><th>i18n:govoplan-docs.route.4999528e</th><th>i18n:govoplan-docs.module.b8ff0289</th><th>i18n:govoplan-docs.source.6da13add</th><th>i18n:govoplan-docs.requirements.09a428f9</th><th>i18n:govoplan-docs.status.bae7d5be</th></tr>
</thead>
<tbody>
{routes.map((route) =>
<tr key={`${route.source}-${route.module_id}-${route.path}`}>
<td><strong>{route.label}</strong><span className="muted block">{route.path}</span></td>
<td>{route.module_id}</td>
<td>{route.source}</td>
<td>{routeRequirements(route)}</td>
<td><StatusBadge status={route.visible ? "success" : "warning"} label={route.visible ? "visible" : route.reason} /></td>
</tr>
)}
</tbody>
</table>
</div>
<DataGrid id={`docs-routes-${emptyText}`} rows={routes} columns={columns} getRowKey={(route) => `${route.source}-${route.module_id}-${route.path}`} />
);
}