Adopt shared WebUI layout primitives

This commit is contained in:
2026-08-18 10:42:50 +02:00
parent 176c1d826f
commit eb2e497f9a
4 changed files with 19 additions and 19 deletions
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Download, Eye, Play, Plus, RefreshCw, Search, ShieldCheck } from "lucide-react"; import { Download, Eye, Play, Plus, RefreshCw, Search, ShieldCheck } from "lucide-react";
import type { ApiSettings, DataGridColumn } from "@govoplan/core-webui"; import type { FormGrid, ApiSettings, DataGridColumn } from "@govoplan/core-webui";
import { import {
AdminIconButton, AdminIconButton,
AdminPageLayout, AdminPageLayout,
@@ -378,17 +378,17 @@ export default function DataSubjectRequestsPanel({
</> : <DismissibleAlert tone="info">{I18N.noSelection}</DismissibleAlert>} </> : <DismissibleAlert tone="info">{I18N.noSelection}</DismissibleAlert>}
</AdminPageLayout> </AdminPageLayout>
<Dialog <Dialog variant="administration" size="wide"
open={creating} open={creating}
title={I18N.createTitle} title={I18N.createTitle}
className="admin-dialog admin-dialog-wide" className=""
portal portal
closeDisabled={busy} closeDisabled={busy}
onClose={() => setCreating(false)} onClose={() => setCreating(false)}
footer={<><Button onClick={() => setCreating(false)} disabled={busy}>{I18N.cancel}</Button><Button variant="primary" onClick={() => void create()} disabled={busy || !draft.reference.trim() || !draft.purpose.trim()}>{I18N.save}</Button></>} footer={<><Button onClick={() => setCreating(false)} disabled={busy}>{I18N.cancel}</Button><Button variant="primary" onClick={() => void create()} disabled={busy || !draft.reference.trim() || !draft.purpose.trim()}>{I18N.save}</Button></>}
helpContextId="admin.privacy.data-subject-requests" helpContextId="admin.privacy.data-subject-requests"
> >
<div className="admin-form-grid two-columns"> <FormGrid columns={2} gap="small" collapseAt="workspace" className="">
<FormField label={I18N.reference}><input value={draft.reference} onChange={(event) => setDraft({ ...draft, reference: event.target.value })} /></FormField> <FormField label={I18N.reference}><input value={draft.reference} onChange={(event) => setDraft({ ...draft, reference: event.target.value })} /></FormField>
<FormField label={I18N.kindLabel}><select value={draft.requestKind} onChange={(event) => setDraft({ ...draft, requestKind: event.target.value as Draft["requestKind"] })}><option value="access">{I18N.accessRequest}</option><option value="erasure">{I18N.erasureRequest}</option><option value="access_and_erasure">{I18N.combinedRequest}</option></select></FormField> <FormField label={I18N.kindLabel}><select value={draft.requestKind} onChange={(event) => setDraft({ ...draft, requestKind: event.target.value as Draft["requestKind"] })}><option value="access">{I18N.accessRequest}</option><option value="erasure">{I18N.erasureRequest}</option><option value="access_and_erasure">{I18N.combinedRequest}</option></select></FormField>
<FormField label={I18N.email}><input type="email" value={draft.email} onChange={(event) => setDraft({ ...draft, email: event.target.value })} /></FormField> <FormField label={I18N.email}><input type="email" value={draft.email} onChange={(event) => setDraft({ ...draft, email: event.target.value })} /></FormField>
@@ -400,13 +400,13 @@ export default function DataSubjectRequestsPanel({
<FormField label={I18N.legalBasis}><input value={draft.legalBasis} onChange={(event) => setDraft({ ...draft, legalBasis: event.target.value })} /></FormField> <FormField label={I18N.legalBasis}><input value={draft.legalBasis} onChange={(event) => setDraft({ ...draft, legalBasis: event.target.value })} /></FormField>
<FormField label={I18N.purpose}><textarea rows={3} value={draft.purpose} onChange={(event) => setDraft({ ...draft, purpose: event.target.value })} /></FormField> <FormField label={I18N.purpose}><textarea rows={3} value={draft.purpose} onChange={(event) => setDraft({ ...draft, purpose: event.target.value })} /></FormField>
<FormField label={I18N.notes}><textarea rows={3} value={draft.notes} onChange={(event) => setDraft({ ...draft, notes: event.target.value })} /></FormField> <FormField label={I18N.notes}><textarea rows={3} value={draft.notes} onChange={(event) => setDraft({ ...draft, notes: event.target.value })} /></FormField>
</div> </FormGrid>
</Dialog> </Dialog>
<Dialog <Dialog variant="administration" size="large"
open={executing && Boolean(selected)} open={executing && Boolean(selected)}
title={I18N.executeTitle} title={I18N.executeTitle}
className="admin-dialog" className=""
portal portal
role="alertdialog" role="alertdialog"
closeDisabled={busy} closeDisabled={busy}
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Search, Pencil, Plus, Trash2 } from "lucide-react"; import { Search, Pencil, Plus, Trash2 } from "lucide-react";
import type { ApiSettings } from "@govoplan/core-webui"; import type { FormGrid, ApiSettings } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui"; import { Button } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui"; import { ConfirmDialog } from "@govoplan/core-webui";
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui"; import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
@@ -216,19 +216,19 @@ export default function GovernanceTemplatesPanel({
</div> </div>
</AdminPageLayout> </AdminPageLayout>
<Dialog <Dialog variant="administration" size="wide"
open={editing !== null} open={editing !== null}
title={editing === "new" ? kind === "group" ? "i18n:govoplan-admin.create_group_template.72407248" : "i18n:govoplan-admin.create_tenant_role.f58db104" : kind === "group" ? "i18n:govoplan-admin.edit_group_template.9bc72d21" : "i18n:govoplan-admin.edit_tenant_role.c15a260d"} title={editing === "new" ? kind === "group" ? "i18n:govoplan-admin.create_group_template.72407248" : "i18n:govoplan-admin.create_tenant_role.f58db104" : kind === "group" ? "i18n:govoplan-admin.edit_group_template.9bc72d21" : "i18n:govoplan-admin.edit_tenant_role.c15a260d"}
onClose={() => !busy && setEditing(null)} onClose={() => !busy && setEditing(null)}
className="admin-dialog admin-dialog-wide" className=""
footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : undefined}>i18n:govoplan-admin.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-admin.saving.56a2285c" : kind === "group" ? "i18n:govoplan-admin.save_template.0885fab2" : "i18n:govoplan-admin.save_tenant_role.8fc0d37d"}</Button></>}> footer={<><Button onClick={() => setEditing(null)} disabled={busy} disabledReason={busy ? ADMIN_INTERFACE_I18N.busy : undefined}>i18n:govoplan-admin.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, complete: Boolean(draft.name.trim() && draft.slug.trim()) })}>{busy ? "i18n:govoplan-admin.saving.56a2285c" : kind === "group" ? "i18n:govoplan-admin.save_template.0885fab2" : "i18n:govoplan-admin.save_tenant_role.8fc0d37d"}</Button></>}>
<div className="admin-form-grid two-columns"> <FormGrid columns={2} gap="small" collapseAt="workspace" className="">
<FormField label="i18n:govoplan-admin.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField> <FormField label="i18n:govoplan-admin.name.709a2322"><input value={draft.name} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-admin.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField> <FormField label="i18n:govoplan-admin.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new"} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-admin.status.bae7d5be"><select value={draft.isActive ? "active" : "inactive"} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-admin.active.a733b809</option><option value="inactive">i18n:govoplan-admin.inactive.09af574c</option></select></FormField> <FormField label="i18n:govoplan-admin.status.bae7d5be"><select value={draft.isActive ? "active" : "inactive"} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-admin.active.a733b809</option><option value="inactive">i18n:govoplan-admin.inactive.09af574c</option></select></FormField>
<FormField label="i18n:govoplan-admin.description.55f8ebc8"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField> <FormField label="i18n:govoplan-admin.description.55f8ebc8"><textarea rows={3} value={draft.description} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
</div> </FormGrid>
{kind === "role" && <div className="form-field"><span className="form-label">i18n:govoplan-admin.tenant_permissions.246294bc</span><AdminSelectionList options={permissions.map((permission) => ({ id: permission.scope, label: permission.label, description: permission.description }))} selected={draft.permissions} onChange={(next) => setDraft({ ...draft, permissions: next })} /></div>} {kind === "role" && <div className="form-field"><span className="form-label">i18n:govoplan-admin.tenant_permissions.246294bc</span><AdminSelectionList options={permissions.map((permission) => ({ id: permission.scope, label: permission.label, description: permission.description }))} selected={draft.permissions} onChange={(next) => setDraft({ ...draft, permissions: next })} /></div>}
<div className="form-field"> <div className="form-field">
<span className="form-label">i18n:govoplan-admin.tenant_availability.067a4f31</span> <span className="form-label">i18n:govoplan-admin.tenant_availability.067a4f31</span>
@@ -239,7 +239,7 @@ export default function GovernanceTemplatesPanel({
</div> </div>
</Dialog> </Dialog>
<Dialog open={Boolean(viewing)} title={viewing?.name || "i18n:govoplan-admin.template_details.d5d75e4d"} onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-admin.close.bbfa773e</Button>}> <Dialog variant="administration" size="wide" open={Boolean(viewing)} title={viewing?.name || "i18n:govoplan-admin.template_details.d5d75e4d"} onClose={() => setViewing(null)} className="" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-admin.close.bbfa773e</Button>}>
{viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-admin.kind.e00ac23f</dt><dd>{viewing.kind}</dd></div><div><dt>i18n:govoplan-admin.slug.094da9b9</dt><dd>{viewing.slug}</dd></div><div><dt>i18n:govoplan-admin.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-admin.active.a733b809" : "i18n:govoplan-admin.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-admin.tenants.1f7ae776</dt><dd>{viewing.assignments.length || "i18n:govoplan-admin.none.6eef6648"}</dd></div><div><dt>i18n:govoplan-admin.description.55f8ebc8</dt><dd>{viewing.description || "—"}</dd></div><div><dt>i18n:govoplan-admin.permissions.d06d5557</dt><dd>{viewing.permissions.length ? <PermissionDetails scopes={viewing.permissions} permissionsByScope={permissionsByScope} /> : "—"}</dd></div></dl>} {viewing && <dl className="admin-details-grid"><div><dt>i18n:govoplan-admin.kind.e00ac23f</dt><dd>{viewing.kind}</dd></div><div><dt>i18n:govoplan-admin.slug.094da9b9</dt><dd>{viewing.slug}</dd></div><div><dt>i18n:govoplan-admin.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-admin.active.a733b809" : "i18n:govoplan-admin.inactive.09af574c"}</dd></div><div><dt>i18n:govoplan-admin.tenants.1f7ae776</dt><dd>{viewing.assignments.length || "i18n:govoplan-admin.none.6eef6648"}</dd></div><div><dt>i18n:govoplan-admin.description.55f8ebc8</dt><dd>{viewing.description || "—"}</dd></div><div><dt>i18n:govoplan-admin.permissions.d06d5557</dt><dd>{viewing.permissions.length ? <PermissionDetails scopes={viewing.permissions} permissionsByScope={permissionsByScope} /> : "—"}</dd></div></dl>}
</Dialog> </Dialog>
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import type { ApiSettings } from "@govoplan/core-webui"; import type { ActionToolbar, ApiSettings } from "@govoplan/core-webui";
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, Button, ConfirmDialog, dispatchPlatformModulesChanged, DocumentationHelpLink, formatDateTime, MetricCard, SegmentedControl, StageRail, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard, type FormatDateTimeOptions } from "@govoplan/core-webui"; import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, Button, ConfirmDialog, dispatchPlatformModulesChanged, DocumentationHelpLink, formatDateTime, MetricCard, SegmentedControl, StageRail, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard, type FormatDateTimeOptions } from "@govoplan/core-webui";
import { Check, Clock, FileText, Pencil, Send } from "lucide-react"; import { Check, Clock, FileText, Pencil, Send } from "lucide-react";
import { import {
@@ -520,7 +520,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
{packageCatalog.warnings.map((warning) => <p key={warning} className="alert warning">{warning}</p>)} {packageCatalog.warnings.map((warning) => <p key={warning} className="alert warning">{warning}</p>)}
{!packageCatalog.configured && <div className="module-install-plan-empty">i18n:govoplan-admin.no_package_catalog_configured_set_govoplan_modul.2c5fceb3</div>} {!packageCatalog.configured && <div className="module-install-plan-empty">i18n:govoplan-admin.no_package_catalog_configured_set_govoplan_modul.2c5fceb3</div>}
{packageCatalog.configured && packageCatalog.modules.length === 0 && !packageCatalog.error && <div className="module-install-plan-empty">i18n:govoplan-admin.package_catalog_is_configured_but_contains_no_en.b6b053cb</div>} {packageCatalog.configured && packageCatalog.modules.length === 0 && !packageCatalog.error && <div className="module-install-plan-empty">i18n:govoplan-admin.package_catalog_is_configured_but_contains_no_en.b6b053cb</div>}
{packageCatalog.modules.length > 0 && <div className="module-package-catalog-toolbar"> {packageCatalog.modules.length > 0 && <ActionToolbar className="module-package-catalog-toolbar">
<label className="module-package-catalog-search"> <label className="module-package-catalog-search">
<span>i18n:govoplan-admin.search_module_directory.7c8f1001</span> <span>i18n:govoplan-admin.search_module_directory.7c8f1001</span>
<input <input
@@ -542,7 +542,7 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
{ id: "blocked", label: "i18n:govoplan-admin.blocked.7c8f1007" }, { id: "blocked", label: "i18n:govoplan-admin.blocked.7c8f1007" },
]} /> ]} />
</div>} </ActionToolbar>}
{packageCatalog.modules.length > 0 && visibleCatalogModules.length === 0 && <div className="module-install-plan-empty">i18n:govoplan-admin.no_catalog_entries_match.7c8f1008</div>} {packageCatalog.modules.length > 0 && visibleCatalogModules.length === 0 && <div className="module-install-plan-empty">i18n:govoplan-admin.no_catalog_entries_match.7c8f1008</div>}
{packageCatalog.modules.length > 0 && <div className="module-package-catalog-list"> {packageCatalog.modules.length > 0 && <div className="module-package-catalog-list">
{visibleCatalogModules.map((item) => {visibleCatalogModules.map((item) =>
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import type { ApiSettings } from "@govoplan/core-webui"; import type { FormGrid, ApiSettings } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui"; import { Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui"; import { FormField } from "@govoplan/core-webui";
@@ -163,7 +163,7 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
selected={draft.enabled_language_codes} selected={draft.enabled_language_codes}
onChange={setEnabledLanguages} onChange={setEnabledLanguages}
/> />
<div className="form-grid"> <FormGrid columns={1} collapseAt="standard" className="">
<FormField label="i18n:govoplan-admin.language_code"> <FormField label="i18n:govoplan-admin.language_code">
<input value={packageDraft.code} disabled={!canWrite || busy} placeholder="fr" onChange={(event) => setPackageDraft({ ...packageDraft, code: event.target.value })} /> <input value={packageDraft.code} disabled={!canWrite || busy} placeholder="fr" onChange={(event) => setPackageDraft({ ...packageDraft, code: event.target.value })} />
</FormField> </FormField>
@@ -176,7 +176,7 @@ export default function SystemSettingsPanel({ settings, canWrite, canAccessMaint
<div className="button-row compact-actions"> <div className="button-row compact-actions">
<Button onClick={installLanguagePackage} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, complete: Boolean(normalizeLanguageCode(packageDraft.code) && packageDraft.label.trim()) })}>i18n:govoplan-admin.install_language_package</Button> <Button onClick={installLanguagePackage} disabledReason={mutationDisabledReason({ busy, permitted: canWrite, complete: Boolean(normalizeLanguageCode(packageDraft.code) && packageDraft.label.trim()) })}>i18n:govoplan-admin.install_language_package</Button>
</div> </div>
</div> </FormGrid>
<p className="muted small-note">i18n:govoplan-admin.language_packages_help</p> <p className="muted small-note">i18n:govoplan-admin.language_packages_help</p>
</Card> </Card>
<Card title="i18n:govoplan-admin.tenant_administration_capabilities.5d265972"> <Card title="i18n:govoplan-admin.tenant_administration_capabilities.5d265972">