From 441f8f11acb46fb94c103dfd57f68a0e99ade6ea Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Thu, 9 Jul 2026 17:08:00 +0200 Subject: [PATCH] Add dashboard module --- README.md | 12 ++ package.json | 33 ++++ pyproject.toml | 25 +++ src/govoplan_dashboard.egg-info/PKG-INFO | 22 +++ src/govoplan_dashboard.egg-info/SOURCES.txt | 12 ++ .../dependency_links.txt | 1 + .../entry_points.txt | 2 + src/govoplan_dashboard.egg-info/requires.txt | 2 + src/govoplan_dashboard.egg-info/top_level.txt | 1 + src/govoplan_dashboard/__init__.py | 6 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 261 bytes src/govoplan_dashboard/backend/__init__.py | 2 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 241 bytes .../__pycache__/manifest.cpython-313.pyc | Bin 0 -> 1694 bytes src/govoplan_dashboard/backend/manifest.py | 40 ++++ src/govoplan_dashboard/py.typed | 1 + webui/package.json | 29 +++ .../src/features/dashboard/DashboardPage.tsx | 186 ++++++++++++++++++ .../widgets/InstalledModulesWidget.tsx | 16 ++ webui/src/i18n/generatedTranslations.ts | 9 + webui/src/index.ts | 6 + webui/src/module.ts | 46 +++++ webui/src/styles/dashboard.css | 97 +++++++++ 23 files changed, 548 insertions(+) create mode 100644 README.md create mode 100644 package.json create mode 100644 pyproject.toml create mode 100644 src/govoplan_dashboard.egg-info/PKG-INFO create mode 100644 src/govoplan_dashboard.egg-info/SOURCES.txt create mode 100644 src/govoplan_dashboard.egg-info/dependency_links.txt create mode 100644 src/govoplan_dashboard.egg-info/entry_points.txt create mode 100644 src/govoplan_dashboard.egg-info/requires.txt create mode 100644 src/govoplan_dashboard.egg-info/top_level.txt create mode 100644 src/govoplan_dashboard/__init__.py create mode 100644 src/govoplan_dashboard/__pycache__/__init__.cpython-313.pyc create mode 100644 src/govoplan_dashboard/backend/__init__.py create mode 100644 src/govoplan_dashboard/backend/__pycache__/__init__.cpython-313.pyc create mode 100644 src/govoplan_dashboard/backend/__pycache__/manifest.cpython-313.pyc create mode 100644 src/govoplan_dashboard/backend/manifest.py create mode 100644 src/govoplan_dashboard/py.typed create mode 100644 webui/package.json create mode 100644 webui/src/features/dashboard/DashboardPage.tsx create mode 100644 webui/src/features/dashboard/widgets/InstalledModulesWidget.tsx create mode 100644 webui/src/i18n/generatedTranslations.ts create mode 100644 webui/src/index.ts create mode 100644 webui/src/module.ts create mode 100644 webui/src/styles/dashboard.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f9028b --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# GovOPlaN Dashboard + +Configurable dashboard module for GovOPlaN. + +The module owns the `/dashboard` route when installed. Core keeps only a minimal +fallback home for installations where this module is absent. + +Modules contribute widgets through the `dashboard.widgets` WebUI capability. +The first implementation stores personal widget visibility in browser storage; +server-side layout persistence can be added later without changing the widget +contract. + diff --git a/package.json b/package.json new file mode 100644 index 0000000..a76dd1e --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "@govoplan/dashboard-webui", + "version": "0.1.6", + "private": true, + "type": "module", + "main": "webui/src/index.ts", + "module": "webui/src/index.ts", + "types": "webui/src/index.ts", + "exports": { + ".": { + "types": "./webui/src/index.ts", + "import": "./webui/src/index.ts" + }, + "./styles/dashboard.css": "./webui/src/styles/dashboard.css" + }, + "files": [ + "webui/src", + "README.md" + ], + "peerDependencies": { + "@govoplan/core-webui": "^0.1.6", + "lucide-react": "^1.23.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.1.1" + }, + "peerDependenciesMeta": { + "@govoplan/core-webui": { + "optional": true + } + } +} + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..28ddf81 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "govoplan-dashboard" +version = "0.1.6" +description = "GovOPlaN configurable dashboard module." +readme = "README.md" +requires-python = ">=3.12" +authors = [{ name = "GovOPlaN" }] +dependencies = [ + "govoplan-core>=0.1.6", + "govoplan-access>=0.1.6", +] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +govoplan_dashboard = ["py.typed"] + +[project.entry-points."govoplan.modules"] +dashboard = "govoplan_dashboard.backend.manifest:get_manifest" + diff --git a/src/govoplan_dashboard.egg-info/PKG-INFO b/src/govoplan_dashboard.egg-info/PKG-INFO new file mode 100644 index 0000000..85f019c --- /dev/null +++ b/src/govoplan_dashboard.egg-info/PKG-INFO @@ -0,0 +1,22 @@ +Metadata-Version: 2.4 +Name: govoplan-dashboard +Version: 0.1.6 +Summary: GovOPlaN configurable dashboard module. +Author: GovOPlaN +Requires-Python: >=3.12 +Description-Content-Type: text/markdown +Requires-Dist: govoplan-core>=0.1.6 +Requires-Dist: govoplan-access>=0.1.6 + +# GovOPlaN Dashboard + +Configurable dashboard module for GovOPlaN. + +The module owns the `/dashboard` route when installed. Core keeps only a minimal +fallback home for installations where this module is absent. + +Modules contribute widgets through the `dashboard.widgets` WebUI capability. +The first implementation stores personal widget visibility in browser storage; +server-side layout persistence can be added later without changing the widget +contract. + diff --git a/src/govoplan_dashboard.egg-info/SOURCES.txt b/src/govoplan_dashboard.egg-info/SOURCES.txt new file mode 100644 index 0000000..59679ed --- /dev/null +++ b/src/govoplan_dashboard.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +README.md +pyproject.toml +src/govoplan_dashboard/__init__.py +src/govoplan_dashboard/py.typed +src/govoplan_dashboard.egg-info/PKG-INFO +src/govoplan_dashboard.egg-info/SOURCES.txt +src/govoplan_dashboard.egg-info/dependency_links.txt +src/govoplan_dashboard.egg-info/entry_points.txt +src/govoplan_dashboard.egg-info/requires.txt +src/govoplan_dashboard.egg-info/top_level.txt +src/govoplan_dashboard/backend/__init__.py +src/govoplan_dashboard/backend/manifest.py \ No newline at end of file diff --git a/src/govoplan_dashboard.egg-info/dependency_links.txt b/src/govoplan_dashboard.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/govoplan_dashboard.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/govoplan_dashboard.egg-info/entry_points.txt b/src/govoplan_dashboard.egg-info/entry_points.txt new file mode 100644 index 0000000..b0a7d51 --- /dev/null +++ b/src/govoplan_dashboard.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[govoplan.modules] +dashboard = govoplan_dashboard.backend.manifest:get_manifest diff --git a/src/govoplan_dashboard.egg-info/requires.txt b/src/govoplan_dashboard.egg-info/requires.txt new file mode 100644 index 0000000..cd1e2a8 --- /dev/null +++ b/src/govoplan_dashboard.egg-info/requires.txt @@ -0,0 +1,2 @@ +govoplan-core>=0.1.6 +govoplan-access>=0.1.6 diff --git a/src/govoplan_dashboard.egg-info/top_level.txt b/src/govoplan_dashboard.egg-info/top_level.txt new file mode 100644 index 0000000..f30f210 --- /dev/null +++ b/src/govoplan_dashboard.egg-info/top_level.txt @@ -0,0 +1 @@ +govoplan_dashboard diff --git a/src/govoplan_dashboard/__init__.py b/src/govoplan_dashboard/__init__.py new file mode 100644 index 0000000..e7b9f27 --- /dev/null +++ b/src/govoplan_dashboard/__init__.py @@ -0,0 +1,6 @@ +"""GovOPlaN dashboard module.""" + +__all__ = ["__version__"] + +__version__ = "0.1.6" + diff --git a/src/govoplan_dashboard/__pycache__/__init__.cpython-313.pyc b/src/govoplan_dashboard/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11a611e608d848d6258c8b5612903f1d7dd2eb0b GIT binary patch literal 261 zcmey&%ge<81Z*4qvqFIMV-N=h7@>^MLO{k;hG2#whG0g0#v;aGrXr?vW=)nVDfj#` z|A3rCKZTUU;*6yH#G(|1-29Z%oK(GA-0|^csYS(^`FZj2RjdYjhI(dxn#{M@`)wo?n(mCjKMF{F literal 0 HcmV?d00001 diff --git a/src/govoplan_dashboard/backend/__init__.py b/src/govoplan_dashboard/backend/__init__.py new file mode 100644 index 0000000..4d95d38 --- /dev/null +++ b/src/govoplan_dashboard/backend/__init__.py @@ -0,0 +1,2 @@ +"""Backend integration for the GovOPlaN dashboard module.""" + diff --git a/src/govoplan_dashboard/backend/__pycache__/__init__.cpython-313.pyc b/src/govoplan_dashboard/backend/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa7c0100e5db5d726c3a28d3addf6486084aee29 GIT binary patch literal 241 zcmey&%ge<81Z*4qv+RKMV-N=h7@>^M96-iYhG2#whIB?vrYbY1#N_PMycC7Zypq)P zqQsKS{5*xU{33;tj8p~p{4)Q5oJ2o`l*HnUr2NF96ouUUl+v73JwHvxTkP@iDf!9q z@hcfVgABjrt)H7$qVM7u;;5gVS)!kwUzT5xlbELq*Q{S$lnfV+M+hfDoS+{cpP83g m5+AQuP$N>PM&qZJW literal 0 HcmV?d00001 diff --git a/src/govoplan_dashboard/backend/__pycache__/manifest.cpython-313.pyc b/src/govoplan_dashboard/backend/__pycache__/manifest.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bceb1f318ad8e89a49fc81bd4a996b34c110f39e GIT binary patch literal 1694 zcmZWp&2JM&6yLRX*IvgTU?)%#qK;5o$pUPUC~YLvf}luEN~qW%+LCrP-W@vwyF1Iw zIH`T&)c*jfN2E%XdZV`gT@ALP*d|0bh4EUU1 z?>>6y7{+V;n0*UV#ytEPm}iD(c;=e1YH|~4zOYtUE%M^3#jRDF+p7+DR!h8uj7{g9 z;T1j0vpvTvZI<9whF8&Zy~>Jp#oV+e`ngSeqN#$swo(N7|17=winUn;tA=;NJLxsO zmN)M$c&EJ6-a9Mq71KMjQs!mvthcymJhHeuSZXi7(V*L=n!`!LB$kvVqHU|@Efx%8 zl1wEVETw^}-{vflBnj`Za2OF)o4xO`p(Lt472m-L?GqtYX&rBWE=jE15R0uI!#q^( ztr*5aS4XLW- z7{9~=(k}9O2nMiNoU#Nu!cR|wyYZwAJ`Ghh#ld4R_7fZv<#62(s3gJLK7|g5@lp=0 z10eczFyy!wk&9a_CgY`>$3$o-2uHd3_=k-xa`Z&goQUlt0!h#ukA`$KB!;|?1JXga z36?`n-ns}xzE7DTXorRaBDFWb9Bd(s63jWWqKC$siU>q7h0t z+omBA;2P6}#yCQK97R3o!4$&I7D*=P@-k+)DLC~6G_iy7F6W5ip^&IIsY!C$gC2%C z%0Op_VkO6c6c-R|0qTtqg_%sIj|A;Sum!V3`LqNIDJOx9Mjcg|b&G`Ug0gW4@Rg+- z4pT;Y%2ilzoJfDFDzlVuEExxDOC#V_FnXi+q3x;y4BnPh!X=c%Fpe=FDXYiAkzTkF z;i?rLzad|aQdo*I9)^@80Z|Q3A`CV9)0v9tUX}U#8i+O7q|!Fw0xnWjLz2Sbfggn! z3oO-liX;EHm@jL`a{uj-s@i|j2cXP96S$&bn1^*>o*55w#e48qunsp_j^AGKFUFx( zs0N#m_;qNz!wD>L!nLklQ5E0s5A|02K8FGfwKzlK>-oTO0h#sF0t0Sq0{_&; zBm^&O?usTlE&osxor`}2M3t{ieLvyX;HkSLegd*zG)?m_yY#oEiqyI((i)!N)`es|#7b?2aNxJy~%Le}i=7mS5(3;ULN^0UIeW3=AQ m(B*7lB|CdQYtA2>skxQ?Vx#38EG<_HPs{&Y2XOO~BK`$>(<4m) literal 0 HcmV?d00001 diff --git a/src/govoplan_dashboard/backend/manifest.py b/src/govoplan_dashboard/backend/manifest.py new file mode 100644 index 0000000..fc3b8e5 --- /dev/null +++ b/src/govoplan_dashboard/backend/manifest.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from govoplan_core.core.modules import DocumentationTopic, FrontendModule, FrontendRoute, ModuleManifest, NavItem + + +manifest = ModuleManifest( + id="dashboard", + name="Dashboard", + version="0.1.6", + dependencies=("access",), + optional_dependencies=("ops", "campaigns", "files", "mail", "tasks", "notifications", "reporting"), + nav_items=(NavItem(path="/dashboard", label="Dashboard", icon="dashboard", order=10),), + frontend=FrontendModule( + module_id="dashboard", + package_name="@govoplan/dashboard-webui", + routes=(FrontendRoute(path="/dashboard", component="DashboardPage", order=10),), + nav_items=(NavItem(path="/dashboard", label="Dashboard", icon="dashboard", order=10),), + ), + documentation=( + DocumentationTopic( + id="dashboard.configurable-home", + title="Configurable user dashboard", + summary="The dashboard module owns the configurable home surface. Feature modules expose widgets through a narrow dashboard.widgets capability.", + body=( + "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." + ), + layer="configured", + documentation_types=("admin", "user"), + audience=("user", "tenant_admin", "operator"), + related_modules=("core", "ops"), + order=20, + ), + ), +) + + +def get_manifest() -> ModuleManifest: + return manifest + diff --git a/src/govoplan_dashboard/py.typed b/src/govoplan_dashboard/py.typed new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/govoplan_dashboard/py.typed @@ -0,0 +1 @@ + diff --git a/webui/package.json b/webui/package.json new file mode 100644 index 0000000..9ad4430 --- /dev/null +++ b/webui/package.json @@ -0,0 +1,29 @@ +{ + "name": "@govoplan/dashboard-webui", + "version": "0.1.6", + "private": true, + "type": "module", + "main": "src/index.ts", + "module": "src/index.ts", + "types": "src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./styles/dashboard.css": "./src/styles/dashboard.css" + }, + "peerDependencies": { + "@govoplan/core-webui": "^0.1.6", + "lucide-react": "^1.23.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.1.1" + }, + "peerDependenciesMeta": { + "@govoplan/core-webui": { + "optional": true + } + } +} + diff --git a/webui/src/features/dashboard/DashboardPage.tsx b/webui/src/features/dashboard/DashboardPage.tsx new file mode 100644 index 0000000..30f1d70 --- /dev/null +++ b/webui/src/features/dashboard/DashboardPage.tsx @@ -0,0 +1,186 @@ +import { useEffect, useMemo, useState } from "react"; +import { RefreshCw, RotateCcw, SlidersHorizontal } from "lucide-react"; +import { + Button, + Card, + MetricCard, + PageTitle, + dashboardWidgetsForModules, + hasAnyScope, + hasScope, + usePlatformModules, + type ApiSettings, + type AuthInfo, + type DashboardWidgetContribution, + type DashboardWidgetSize +} from "@govoplan/core-webui"; + +type DashboardLayout = { + visible: string[]; + known: string[]; +}; + +export default function DashboardPage({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) { + const modules = usePlatformModules(); + const widgets = useMemo(() => dashboardWidgetsForModules(modules).filter((widget) => canUseWidget(auth, widget)), [auth, modules]); + const widgetSignature = widgets.map((widget) => widget.id).join("|"); + const storageKey = `govoplan.dashboard.layout:${auth.active_tenant?.id ?? auth.tenant.id}:${auth.user.id}`; + const [layout, setLayout] = useState(() => defaultLayout(widgets)); + const [configuring, setConfiguring] = useState(false); + const [refreshKey, setRefreshKey] = useState(0); + + useEffect(() => { + setLayout(readLayout(storageKey) ?? defaultLayout(widgets)); + }, [storageKey, widgetSignature]); + + const visibleWidgetIds = useMemo(() => { + const known = new Set(layout.known); + const visible = new Set(layout.visible); + for (const widget of widgets) { + if (!known.has(widget.id) && widget.defaultVisible !== false) visible.add(widget.id); + } + const availableIds = new Set(widgets.map((widget) => widget.id)); + return widgets.map((widget) => widget.id).filter((id) => availableIds.has(id) && visible.has(id)); + }, [layout, widgets]); + const visibleWidgets = widgets.filter((widget) => visibleWidgetIds.includes(widget.id)); + const hiddenCount = Math.max(widgets.length - visibleWidgets.length, 0); + + function saveLayout(next: DashboardLayout) { + setLayout(next); + writeLayout(storageKey, next); + } + + function setWidgetVisible(widgetId: string, visible: boolean) { + const allIds = widgets.map((widget) => widget.id); + const nextVisible = new Set(visibleWidgetIds); + if (visible) { + nextVisible.add(widgetId); + } else { + nextVisible.delete(widgetId); + } + saveLayout({ + known: allIds, + visible: allIds.filter((id) => nextVisible.has(id)) + }); + } + + function resetLayout() { + saveLayout(defaultLayout(widgets)); + } + + return ( +
+
+
+ i18n:govoplan-dashboard.dashboard.3f8b4df2 +

Personal workspace assembled from installed module widgets.

+
+
+ + +
+
+ +
+ + + + +
+ + {configuring && + Reset}> + {widgets.length === 0 ?

No installed module exposes dashboard widgets yet.

: +
+ {widgets.map((widget) => + + )} +
+ } +
+ } + + {visibleWidgets.length === 0 ? + +

Open Configure and select at least one widget. Modules can add more widgets by exposing the dashboard.widgets capability.

+
: +
+ {visibleWidgets.map((widget) => +
+ + {widget.render({ + settings, + auth, + modules, + widgetId: widget.id, + refreshKey, + size: widgetSize(widget) + })} + +
+ )} +
+ } +
); +} + +function canUseWidget(auth: AuthInfo, widget: DashboardWidgetContribution): boolean { + if (widget.allOf?.length && !widget.allOf.every((scope) => hasScope(auth, scope))) return false; + if (widget.anyOf?.length && !hasAnyScope(auth, widget.anyOf)) return false; + return true; +} + +function widgetSize(widget: DashboardWidgetContribution): DashboardWidgetSize { + return widget.defaultSize ?? "medium"; +} + +function defaultLayout(widgets: DashboardWidgetContribution[]): DashboardLayout { + const ids = widgets.map((widget) => widget.id); + return { + known: ids, + visible: widgets.filter((widget) => widget.defaultVisible !== false).map((widget) => widget.id) + }; +} + +function readLayout(storageKey: string): DashboardLayout | null { + if (typeof window === "undefined") return null; + try { + const raw = window.localStorage.getItem(storageKey); + if (!raw) return null; + const parsed = JSON.parse(raw) as Partial; + if (!Array.isArray(parsed.visible) || !Array.isArray(parsed.known)) return null; + return { + visible: parsed.visible.filter((id): id is string => typeof id === "string"), + known: parsed.known.filter((id): id is string => typeof id === "string") + }; + } catch { + return null; + } +} + +function writeLayout(storageKey: string, layout: DashboardLayout): void { + if (typeof window === "undefined") return; + try { + window.localStorage.setItem(storageKey, JSON.stringify(layout)); + } catch { + // localStorage may be unavailable in restricted browsing contexts. + } +} + +function moduleLabels(modules: Array<{ id: string }>): string { + if (!modules.length) return "Core shell only"; + return modules.slice(0, 4).map((module) => module.id).join(", "); +} + diff --git a/webui/src/features/dashboard/widgets/InstalledModulesWidget.tsx b/webui/src/features/dashboard/widgets/InstalledModulesWidget.tsx new file mode 100644 index 0000000..8486e05 --- /dev/null +++ b/webui/src/features/dashboard/widgets/InstalledModulesWidget.tsx @@ -0,0 +1,16 @@ +import { StatusBadge, type PlatformWebModule } from "@govoplan/core-webui"; + +export default function InstalledModulesWidget({ modules }: { modules: PlatformWebModule[] }) { + if (!modules.length) return

No WebUI modules are active.

; + + return ( +
+ {modules.map((module) => +
+
+
{module.label} v{module.version}
+
+ )} +
); +} + diff --git a/webui/src/i18n/generatedTranslations.ts b/webui/src/i18n/generatedTranslations.ts new file mode 100644 index 0000000..ce50435 --- /dev/null +++ b/webui/src/i18n/generatedTranslations.ts @@ -0,0 +1,9 @@ +export const generatedTranslations = { + en: { + "i18n:govoplan-dashboard.dashboard.3f8b4df2": "Dashboard" + }, + de: { + "i18n:govoplan-dashboard.dashboard.3f8b4df2": "Dashboard" + } +}; + diff --git a/webui/src/index.ts b/webui/src/index.ts new file mode 100644 index 0000000..220b096 --- /dev/null +++ b/webui/src/index.ts @@ -0,0 +1,6 @@ +export { default } from "./module"; +export * from "./module"; +export { default as DashboardPage } from "./features/dashboard/DashboardPage"; +export { default as InstalledModulesWidget } from "./features/dashboard/widgets/InstalledModulesWidget"; +export type { DashboardWidgetContribution, DashboardWidgetsUiCapability } from "@govoplan/core-webui"; + diff --git a/webui/src/module.ts b/webui/src/module.ts new file mode 100644 index 0000000..e236541 --- /dev/null +++ b/webui/src/module.ts @@ -0,0 +1,46 @@ +import { createElement, lazy } from "react"; +import type { DashboardWidgetsUiCapability, PlatformWebModule } from "@govoplan/core-webui"; +import InstalledModulesWidget from "./features/dashboard/widgets/InstalledModulesWidget"; +import { generatedTranslations } from "./i18n/generatedTranslations"; +import "./styles/dashboard.css"; + +const DashboardPage = lazy(() => import("./features/dashboard/DashboardPage")); + +const dashboardWidgets: DashboardWidgetsUiCapability = { + widgets: [ + { + id: "dashboard.installed-modules", + title: "Installed modules", + description: "Enabled WebUI modules in this browser session.", + moduleId: "dashboard", + category: "Platform", + order: 10, + defaultSize: "medium", + render: ({ modules }) => createElement(InstalledModulesWidget, { modules }) + } + ] +}; + +const translations = { + en: generatedTranslations.en, + de: generatedTranslations.de +}; + +export const dashboardModule: PlatformWebModule = { + id: "dashboard", + label: "i18n:govoplan-dashboard.dashboard.3f8b4df2", + version: "1.0.0", + dependencies: ["access"], + optionalDependencies: ["ops", "campaigns", "files", "mail", "tasks", "notifications", "reporting"], + translations, + navItems: [{ to: "/dashboard", label: "i18n:govoplan-dashboard.dashboard.3f8b4df2", iconName: "dashboard", order: 10 }], + routes: [ + { path: "/dashboard", order: 10, render: ({ settings, auth }) => createElement(DashboardPage, { settings, auth }) } + ], + uiCapabilities: { + "dashboard.widgets": dashboardWidgets + } +}; + +export default dashboardModule; + diff --git a/webui/src/styles/dashboard.css b/webui/src/styles/dashboard.css new file mode 100644 index 0000000..53b90a5 --- /dev/null +++ b/webui/src/styles/dashboard.css @@ -0,0 +1,97 @@ +.dashboard-page .card + .dashboard-widget-grid { + margin-top: 18px; +} + +.dashboard-summary-grid .metric-detail { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dashboard-widget-grid { + display: grid; + grid-template-columns: repeat(4, minmax(220px, 1fr)); + gap: 18px; + align-items: start; +} + +.dashboard-widget { + min-width: 0; +} + +.dashboard-widget-small { + grid-column: span 1; +} + +.dashboard-widget-medium { + grid-column: span 2; +} + +.dashboard-widget-wide, +.dashboard-widget-full { + grid-column: 1 / -1; +} + +.dashboard-widget-picker { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 12px; +} + +.dashboard-widget-toggle { + min-width: 0; + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 10px; + align-items: start; + border: 1px solid var(--line); + border-radius: var(--radius-sm); + background: var(--panel-soft); + padding: 12px; +} + +.dashboard-widget-toggle input { + margin-top: 3px; +} + +.dashboard-widget-toggle strong, +.dashboard-widget-toggle small { + display: block; +} + +.dashboard-widget-toggle small { + color: var(--muted); + margin-top: 4px; + line-height: 1.35; +} + +.dashboard-compact-list.detail-list div { + grid-template-columns: minmax(92px, auto) minmax(0, 1fr); +} + +.dashboard-widget-metrics.metric-grid { + grid-template-columns: repeat(3, minmax(120px, 1fr)); + margin-bottom: 0; +} + +@media (max-width: 1120px) { + .dashboard-widget-grid { + grid-template-columns: repeat(2, minmax(220px, 1fr)); + } +} + +@media (max-width: 900px) { + .dashboard-widget-grid, + .dashboard-widget-picker, + .dashboard-widget-metrics.metric-grid { + grid-template-columns: 1fr; + } + + .dashboard-widget-small, + .dashboard-widget-medium, + .dashboard-widget-wide, + .dashboard-widget-full { + grid-column: 1; + } +} +