Compare commits

5 Commits

15 changed files with 184 additions and 71 deletions

View File

@@ -0,0 +1,35 @@
---
name: "Bug"
about: "Report a reproducible defect, regression, or incorrect behavior"
title: "[Bug] "
labels:
- type/bug
- status/triage
- module/dashboard
---
## Scope
- Repository:
- Area/module:
- Affected version or commit:
## Behavior
Expected:
Actual:
## Reproduction
1.
2.
3.
## Evidence
Logs, screenshots, traces, or failing test output:
## Verification Target
Command or workflow that should pass when fixed:

View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,27 @@
---
name: "Docs / workflow"
about: "Request documentation, process, or developer workflow changes"
title: "[Docs] "
labels:
- type/docs
- status/triage
- module/dashboard
- area/docs
---
## Scope
- Repository:
- Document or workflow:
## Current State
What is missing, unclear, duplicated, or stale?
## Desired State
What should the docs or workflow make clear?
## Verification Target
How should this be checked?

View File

@@ -0,0 +1,32 @@
---
name: "Feature"
about: "Propose new user-visible behavior or platform capability"
title: "[Feature] "
labels:
- type/feature
- status/triage
- module/dashboard
---
## Problem
What user, operator, or developer problem should this solve?
## Proposed Capability
What should exist when this is done?
## Ownership
- Owning repository:
- Related module repositories:
- Extension point or integration boundary:
## Acceptance Criteria
- [ ]
- [ ]
## Verification Target
Command, scenario, or UI flow that should prove completion:

View File

@@ -0,0 +1,28 @@
---
name: "Task"
about: "Track implementation, maintenance, or migration work"
title: "[Task] "
labels:
- type/task
- status/triage
- module/dashboard
---
## Objective
What needs to be completed?
## Scope
- Owning repository:
- In-scope:
- Out-of-scope:
## Checklist
- [ ]
- [ ]
## Verification Target
Command or manual check:

View File

@@ -0,0 +1,25 @@
---
name: "Tech debt"
about: "Track cleanup, refactoring, risk reduction, or deferred engineering work"
title: "[Debt] "
labels:
- type/debt
- status/triage
- module/dashboard
---
## Current Cost
What does this make harder, riskier, slower, or more fragile?
## Desired Shape
What should the code, tests, or architecture look like afterwards?
## Constraints
What behavior, compatibility, or module boundary must be preserved?
## Verification Target
Focused checks that should pass:

View File

@@ -0,0 +1,15 @@
## Issue
Closes #
## Summary
-
## Verification
-
## Notes
Follow-up issues:

View File

@@ -1,5 +1,9 @@
# GovOPlaN Dashboard # GovOPlaN Dashboard
<!-- govoplan-repository-type:start -->
**Repository type:** module (platform).
<!-- govoplan-repository-type:end -->
Configurable dashboard module for GovOPlaN. Configurable dashboard module for GovOPlaN.
The module owns the `/dashboard` route when installed. Core keeps only a minimal The module owns the `/dashboard` route when installed. Core keeps only a minimal
@@ -9,4 +13,3 @@ Modules contribute widgets through the `dashboard.widgets` WebUI capability.
The first implementation stores personal widget visibility in browser storage; The first implementation stores personal widget visibility in browser storage;
server-side layout persistence can be added later without changing the widget server-side layout persistence can be added later without changing the widget
contract. contract.

View File

@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/dashboard-webui", "name": "@govoplan/dashboard-webui",
"version": "0.1.6", "version": "0.1.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "webui/src/index.ts", "main": "webui/src/index.ts",
@@ -18,7 +18,7 @@
"README.md" "README.md"
], ],
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.6", "@govoplan/core-webui": "^0.1.8",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
@@ -30,4 +30,3 @@
} }
} }
} }

View File

@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "govoplan-dashboard" name = "govoplan-dashboard"
version = "0.1.6" version = "0.1.8"
description = "GovOPlaN configurable dashboard module." description = "GovOPlaN configurable dashboard module."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
authors = [{ name = "GovOPlaN" }] authors = [{ name = "GovOPlaN" }]
dependencies = [ dependencies = [
"govoplan-core>=0.1.6", "govoplan-core>=0.1.8",
"govoplan-access>=0.1.6", "govoplan-access>=0.1.8",
] ]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]

View File

@@ -2,5 +2,4 @@
__all__ = ["__version__"] __all__ = ["__version__"]
__version__ = "0.1.6" __version__ = "0.1.8"

View File

@@ -7,7 +7,7 @@ from govoplan_core.core.modules import DocumentationTopic, FrontendModule, Front
manifest = ModuleManifest( manifest = ModuleManifest(
id="dashboard", id="dashboard",
name="Dashboard", name="Dashboard",
version="0.1.6", version="0.1.8",
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR), required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
optional_dependencies=("ops", "campaigns", "files", "mail", "tasks", "notifications", "reporting"), optional_dependencies=("ops", "campaigns", "files", "mail", "tasks", "notifications", "reporting"),
nav_items=(NavItem(path="/dashboard", label="Dashboard", icon="dashboard", order=10),), nav_items=(NavItem(path="/dashboard", label="Dashboard", icon="dashboard", order=10),),

View File

@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/dashboard-webui", "name": "@govoplan/dashboard-webui",
"version": "0.1.6", "version": "0.1.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
@@ -14,7 +14,7 @@
"./styles/dashboard.css": "./src/styles/dashboard.css" "./styles/dashboard.css": "./src/styles/dashboard.css"
}, },
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.6", "@govoplan/core-webui": "^0.1.8",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
@@ -26,4 +26,3 @@
} }
} }
} }

View File

@@ -1,6 +1,7 @@
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { RefreshCw, RotateCcw, SlidersHorizontal } from "lucide-react"; import { RefreshCw, RotateCcw, SlidersHorizontal } from "lucide-react";
import { import {
AdminSelectionList,
Button, Button,
Card, Card,
MetricCard, MetricCard,
@@ -50,14 +51,9 @@ export default function DashboardPage({ settings, auth }: { settings: ApiSetting
writeLayout(storageKey, next); writeLayout(storageKey, next);
} }
function setWidgetVisible(widgetId: string, visible: boolean) { function setVisibleWidgets(selected: string[]) {
const allIds = widgets.map((widget) => widget.id); const allIds = widgets.map((widget) => widget.id);
const nextVisible = new Set(visibleWidgetIds); const nextVisible = new Set(selected);
if (visible) {
nextVisible.add(widgetId);
} else {
nextVisible.delete(widgetId);
}
saveLayout({ saveLayout({
known: allIds, known: allIds,
visible: allIds.filter((id) => nextVisible.has(id)) visible: allIds.filter((id) => nextVisible.has(id))
@@ -93,21 +89,11 @@ export default function DashboardPage({ settings, auth }: { settings: ApiSetting
title="Dashboard widgets" title="Dashboard widgets"
actions={<Button onClick={resetLayout}><RotateCcw size={16} /> Reset</Button>}> actions={<Button onClick={resetLayout}><RotateCcw size={16} /> Reset</Button>}>
{widgets.length === 0 ? <p className="muted">No installed module exposes dashboard widgets yet.</p> : {widgets.length === 0 ? <p className="muted">No installed module exposes dashboard widgets yet.</p> :
<div className="dashboard-widget-picker"> <AdminSelectionList
{widgets.map((widget) => options={widgets.map((widget) => ({ id: widget.id, label: widget.title, description: widget.description ?? widget.id }))}
<label key={widget.id} className="dashboard-widget-toggle"> selected={visibleWidgetIds}
<input onChange={setVisibleWidgets}
type="checkbox" />
checked={visibleWidgetIds.includes(widget.id)}
onChange={(event) => setWidgetVisible(widget.id, event.currentTarget.checked)}
/>
<span>
<strong>{widget.title}</strong>
<small>{widget.description ?? widget.id}</small>
</span>
</label>
)}
</div>
} }
</Card> </Card>
} }
@@ -183,4 +169,3 @@ function moduleLabels(modules: Array<{ id: string }>): string {
if (!modules.length) return "Core shell only"; if (!modules.length) return "Core shell only";
return modules.slice(0, 4).map((module) => module.id).join(", "); return modules.slice(0, 4).map((module) => module.id).join(", ");
} }

View File

@@ -32,39 +32,6 @@
grid-column: 1 / -1; 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 { .dashboard-compact-list.detail-list div {
grid-template-columns: minmax(92px, auto) minmax(0, 1fr); grid-template-columns: minmax(92px, auto) minmax(0, 1fr);
} }
@@ -82,7 +49,6 @@
@media (max-width: 900px) { @media (max-width: 900px) {
.dashboard-widget-grid, .dashboard-widget-grid,
.dashboard-widget-picker,
.dashboard-widget-metrics.metric-grid { .dashboard-widget-metrics.metric-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -94,4 +60,3 @@
grid-column: 1; grid-column: 1;
} }
} }