Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
944401d53b |
@@ -273,6 +273,9 @@ manifest = ModuleManifest(
|
||||
title="Configurable user dashboard",
|
||||
summary="The dashboard module owns the configurable home surface. Feature modules expose widgets through a narrow dashboard.widgets capability.",
|
||||
body=(
|
||||
"The user documentation book sits immediately to the right of Dashboard in both display and "
|
||||
"configuration mode. Widget configuration help sits beside its dialog title. "
|
||||
"A widget's contributed documentation book sits beside its existing card title, not in the widget footer. "
|
||||
"Core only provides a minimal fallback home when the dashboard module is absent. "
|
||||
"Dashboard widgets must be contributed through core contracts, not by importing sibling module components directly. "
|
||||
"Personal layouts are stored per tenant, account, and active View. The active interface-module count includes only "
|
||||
@@ -314,6 +317,9 @@ manifest = ModuleManifest(
|
||||
"Das Dashboard-Modul führt die konfigurierbare Startoberfläche; Fachmodule stellen Widgets über die enge Fähigkeit dashboard.widgets bereit."
|
||||
),
|
||||
"body": (
|
||||
"Das Buch für die Benutzerdokumentation steht im Anzeige- und Konfigurationsmodus unmittelbar "
|
||||
"rechts neben Übersicht (Dashboard). Die Hilfe zur Widget-Konfiguration steht neben ihrem Dialogtitel. "
|
||||
"Das beigetragene Dokumentationsbuch eines Widgets steht neben seinem vorhandenen Kartentitel, nicht in der Fußzeile. "
|
||||
"Core stellt nur dann eine minimale Ersatzstartseite bereit, wenn das Dashboard-Modul fehlt. Dashboard-Widgets "
|
||||
"müssen über Core-Verträge beigetragen werden und dürfen Komponenten anderer Module nicht direkt importieren. "
|
||||
"Persönliche Layouts werden je Mandant, Konto und aktivem View gespeichert. Die Anzahl aktiver Oberflächenmodule "
|
||||
@@ -348,8 +354,10 @@ manifest = ModuleManifest(
|
||||
"Configuring changes only a local draft until Save layout is selected; Cancel or "
|
||||
"Discard restores the last saved arrangement. The page action bar always reports whether "
|
||||
"the draft is saved, unsaved, or currently saving; Save and Cancel remain visible in stable "
|
||||
"positions and are disabled with an explanation when no draft change exists. Leaving while "
|
||||
"dirty invokes the shared save-or-discard guard. Widget removal removes only the placement, "
|
||||
"positions. Cancel exits configuration even when nothing has changed; Save is disabled until "
|
||||
"there are changes. Cancel asks before discarding an edited draft. Both actions are temporarily "
|
||||
"disabled while a save is in progress. Leaving while dirty invokes the shared save-or-discard "
|
||||
"guard. Widget removal removes only the placement, "
|
||||
"not the module data represented by the widget. Reset restores the defaults announced by "
|
||||
"currently active modules and remains reversible until save. A widget is offered only when "
|
||||
"its module, focused View surface, and permission contract are available. Widgets never grant "
|
||||
@@ -389,8 +397,10 @@ manifest = ModuleManifest(
|
||||
"Ein Dashboard-Layout gehört zum aktiven Mandanten, Konto und fokussierten View. Konfigurationen ändern "
|
||||
"zunächst nur einen lokalen Entwurf; erst Layout speichern übernimmt sie. Abbrechen oder Verwerfen stellt "
|
||||
"die zuletzt gespeicherte Anordnung wieder her. Die Seitenaktionsleiste zeigt stets, ob der Entwurf gespeichert, "
|
||||
"ungespeichert oder in Speicherung ist; Speichern und Abbrechen bleiben an stabilen Positionen und sind mit "
|
||||
"Erklärung deaktiviert, wenn keine Änderung vorliegt. Beim Verlassen eines geänderten Entwurfs greift die gemeinsame "
|
||||
"ungespeichert oder in Speicherung ist; Speichern und Abbrechen bleiben an stabilen Positionen. Abbrechen beendet "
|
||||
"die Konfiguration auch ohne Änderungen; Speichern wird erst bei Änderungen verfügbar. Bei einem geänderten "
|
||||
"Entwurf fragt Abbrechen vor dem Verwerfen nach. Nur während einer laufenden Speicherung sind beide Aktionen "
|
||||
"vorübergehend deaktiviert. Beim Verlassen eines geänderten Entwurfs greift die gemeinsame "
|
||||
"Speichern-oder-Verwerfen-Sicherung. Das Entfernen eines Widgets entfernt nur seine Platzierung, nicht die dargestellten "
|
||||
"Moduldaten. Zurücksetzen übernimmt die von aktuell aktiven Modulen angekündigten Standardwerte und bleibt bis zum "
|
||||
"Speichern umkehrbar. Ein Widget wird nur angeboten, wenn Modul, fokussierte View-Oberfläche und Berechtigungsvertrag "
|
||||
|
||||
@@ -92,12 +92,28 @@ class DashboardInterfaceDocumentationContractTests(unittest.TestCase):
|
||||
REPO_ROOT / "webui/src/features/dashboard/WidgetLibrary.tsx"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
self.assertIn("DocumentationHelpLink", page)
|
||||
self.assertEqual(
|
||||
1,
|
||||
page.count("titleHelp={<DocumentationHelpLink reference={DASHBOARD_DOCUMENTATION} />}"),
|
||||
)
|
||||
self.assertNotIn("helpAction=", page)
|
||||
self.assertIn("useUnsavedDraftGuard", page)
|
||||
self.assertIn("useUnsavedDraftGuard", dialog)
|
||||
self.assertIn("DASHBOARD_LAYOUT_DOCUMENTATION", dialog)
|
||||
self.assertIn(
|
||||
"titleHelp={<DocumentationHelpLink reference={DASHBOARD_LAYOUT_DOCUMENTATION} />}",
|
||||
dialog,
|
||||
)
|
||||
self.assertIn("disabled={atCapacity}", library)
|
||||
|
||||
def test_widget_help_uses_provider_reference_at_the_existing_title(self) -> None:
|
||||
grid = (REPO_ROOT / "webui/src/features/dashboard/DashboardGrid.tsx").read_text(encoding="utf-8")
|
||||
self.assertEqual(
|
||||
2,
|
||||
grid.count("titleHelp={widget.documentation && <DocumentationHelpLink reference={widget.documentation} />}"),
|
||||
"Regular cards and height-preserving drag cards retain provider documentation beside their existing title.",
|
||||
)
|
||||
self.assertNotIn("helpAction=", grid)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Card,
|
||||
DocumentationHelpLink,
|
||||
IconButton,
|
||||
type ApiSettings,
|
||||
type AuthInfo,
|
||||
@@ -154,7 +155,7 @@ export default function DashboardGrid({
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
onDrop={onDropPreview}
|
||||
>
|
||||
<Card title={widget.title}>
|
||||
<Card title={widget.title} titleHelp={widget.documentation && <DocumentationHelpLink reference={widget.documentation} />}>
|
||||
<DashboardWidgetContent
|
||||
widget={widget}
|
||||
placement={placement}
|
||||
@@ -186,6 +187,7 @@ export default function DashboardGrid({
|
||||
>
|
||||
<Card
|
||||
title={widget.title}
|
||||
titleHelp={widget.documentation && <DocumentationHelpLink reference={widget.documentation} />}
|
||||
collapsible={!configuring}
|
||||
collapseKey={`dashboard-widget:${placement.instanceId}`}
|
||||
actions={
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
type DragEvent as ReactDragEvent
|
||||
} from "react";
|
||||
import {
|
||||
RefreshCw,
|
||||
Save,
|
||||
SlidersHorizontal,
|
||||
X
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
isApiError,
|
||||
useEffectiveView,
|
||||
usePlatformModules,
|
||||
useUnsavedChanges,
|
||||
useUnsavedDraftGuard,
|
||||
type ApiSettings,
|
||||
type AuthInfo,
|
||||
@@ -185,6 +185,7 @@ export default function DashboardPage({
|
||||
]);
|
||||
|
||||
const dirty = configuring && !layoutsEqual(savedLayout, draftLayout);
|
||||
const { requestDiscard } = useUnsavedChanges();
|
||||
useUnsavedDraftGuard({
|
||||
dirty,
|
||||
onSave: persistLayout,
|
||||
@@ -253,12 +254,21 @@ export default function DashboardPage({
|
||||
|
||||
function discardChanges() {
|
||||
setDraftLayout(savedLayout);
|
||||
exitConfiguration();
|
||||
}
|
||||
|
||||
function exitConfiguration() {
|
||||
setConfiguring(false);
|
||||
setEditingInstanceId(null);
|
||||
setDragItem(null);
|
||||
setDropTarget(null);
|
||||
}
|
||||
|
||||
function cancelConfiguration() {
|
||||
if (dirty) requestDiscard(exitConfiguration);
|
||||
else exitConfiguration();
|
||||
}
|
||||
|
||||
function beginConfiguration() {
|
||||
setDraftLayout(savedLayout);
|
||||
setConfiguring(true);
|
||||
@@ -447,6 +457,7 @@ export default function DashboardPage({
|
||||
archetype={configuring ? "editor" : "overview"}
|
||||
className="dashboard-page"
|
||||
title="i18n:govoplan-dashboard.dashboard.3f8b4df2"
|
||||
titleHelp={<DocumentationHelpLink reference={DASHBOARD_DOCUMENTATION} />}
|
||||
description="Personal workspace assembled from installed module widgets."
|
||||
error={error}
|
||||
success={error ? "" : notice}
|
||||
@@ -454,8 +465,7 @@ export default function DashboardPage({
|
||||
<PageActionBar
|
||||
variant="editor"
|
||||
state={saving ? "saving" : dirty ? "dirty" : "clean"}
|
||||
helpAction={<DocumentationHelpLink reference={DASHBOARD_DOCUMENTATION} />}
|
||||
discardAction={{ label: <><X size={16} /> Cancel</>, onClick: discardChanges }}
|
||||
discardAction={{ label: <><X size={16} /> Cancel</>, behavior: "exit", onClick: cancelConfiguration }}
|
||||
saveAction={{ label: <><Save size={16} /> Save layout</>, onClick: () => void persistLayout() }}
|
||||
/>
|
||||
) : (
|
||||
@@ -467,7 +477,6 @@ export default function DashboardPage({
|
||||
loading,
|
||||
disabledReason: loading ? DASHBOARD_I18N.loading : undefined
|
||||
}}
|
||||
helpAction={<DocumentationHelpLink reference={DASHBOARD_DOCUMENTATION} />}
|
||||
primaryActions={(
|
||||
<Button
|
||||
onClick={beginConfiguration}
|
||||
|
||||
@@ -118,6 +118,7 @@ export default function WidgetConfigurationDialog({
|
||||
}
|
||||
|
||||
function reset() {
|
||||
if (!widget) return;
|
||||
setSize(defaultWidgetSize(widget));
|
||||
setConfiguration({ ...(widget.defaultConfiguration ?? {}) });
|
||||
}
|
||||
@@ -126,6 +127,7 @@ export default function WidgetConfigurationDialog({
|
||||
<Dialog
|
||||
open={open}
|
||||
title={`Configure ${widget.title}`}
|
||||
titleHelp={<DocumentationHelpLink reference={DASHBOARD_LAYOUT_DOCUMENTATION} />}
|
||||
onClose={close}
|
||||
className="dashboard-widget-config-dialog"
|
||||
footer={
|
||||
@@ -145,7 +147,6 @@ export default function WidgetConfigurationDialog({
|
||||
</>
|
||||
}
|
||||
>
|
||||
<DocumentationHelpLink reference={DASHBOARD_LAYOUT_DOCUMENTATION} />
|
||||
{supportedSizes.length > 1 && (
|
||||
<FormField label="Widget size" documentation={DASHBOARD_LAYOUT_DOCUMENTATION}>
|
||||
<SegmentedControl
|
||||
|
||||
@@ -186,10 +186,13 @@
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.dashboard-widget-grid.is-configuring .card-header > h2 {
|
||||
.dashboard-widget-grid.is-configuring .card-header > .card-title-with-help {
|
||||
order: 2;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-widget-grid.is-configuring .card-title-with-help > h2 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
Reference in New Issue
Block a user