feat: Enhance Dashboard-Lab with widget catalog integration and layout updates
- Integrated a new API endpoint for fetching the widget catalog in the Dashboard-Lab. - Updated the dashboard layout schema to utilize the widget catalog for dynamic widget management. - Refactored DashboardLabPage and PilotVizPage to leverage the new widget rendering system. - Removed deprecated widget metadata from the frontend, streamlining the widget management process. - Bumped app_dashboard version to 1.1.0 to reflect the new features and improvements.
This commit is contained in:
parent
e5f6e6c10d
commit
f6c5f96768
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Dashboard-Layout v1 (Nutzer-Lab): Validierung und Standard-Layout.
|
||||
|
||||
Single Source für erlaubte Widget-IDs (sync mit Frontend widgetRegistry).
|
||||
Erlaubte Widget-IDs und Standard-Reihenfolge: widget_catalog.WIDGET_CATALOG
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -9,27 +9,22 @@ from typing import Any, Literal
|
|||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
ALLOWED_WIDGET_IDS: frozenset[str] = frozenset(
|
||||
{
|
||||
"welcome",
|
||||
"quick_capture",
|
||||
"kpi_board",
|
||||
"body_overview",
|
||||
"activity_overview",
|
||||
}
|
||||
)
|
||||
from widget_catalog import ALLOWED_WIDGET_IDS, WIDGET_CATALOG
|
||||
|
||||
# Abwärtskompatibel (Tests importieren weiterhin aus diesem Modul)
|
||||
__all__ = [
|
||||
"ALLOWED_WIDGET_IDS",
|
||||
"DashboardLayoutPayload",
|
||||
"DashboardWidgetEntry",
|
||||
"coalesce_effective_layout",
|
||||
"default_layout_dict",
|
||||
]
|
||||
|
||||
|
||||
def default_layout_dict() -> dict[str, Any]:
|
||||
return {
|
||||
"version": 1,
|
||||
"widgets": [
|
||||
{"id": "welcome", "enabled": True},
|
||||
{"id": "quick_capture", "enabled": True},
|
||||
{"id": "kpi_board", "enabled": True},
|
||||
{"id": "body_overview", "enabled": True},
|
||||
{"id": "activity_overview", "enabled": True},
|
||||
],
|
||||
"widgets": [{"id": e["id"], "enabled": True} for e in WIDGET_CATALOG],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,18 @@ from auth import require_auth
|
|||
from dashboard_layout_schema import DashboardLayoutPayload, coalesce_effective_layout, default_layout_dict
|
||||
from db import get_cursor, get_db
|
||||
from routers.profiles import get_pid
|
||||
from widget_catalog import catalog_response
|
||||
|
||||
router = APIRouter(prefix="/api/app", tags=["app-dashboard-lab"])
|
||||
|
||||
|
||||
@router.get("/widgets/catalog")
|
||||
def get_widgets_catalog(session: dict = Depends(require_auth)) -> dict[str, Any]:
|
||||
"""Metadaten aller registrierbaren Dashboard-Widgets (IDs, Titel)."""
|
||||
_ = session
|
||||
return catalog_response()
|
||||
|
||||
|
||||
@router.get("/dashboard-layout")
|
||||
def get_dashboard_layout(
|
||||
x_profile_id: Optional[str] = Header(default=None),
|
||||
|
|
|
|||
25
backend/tests/test_widget_catalog.py
Normal file
25
backend/tests/test_widget_catalog.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"""Widget-Katalog: Konsistenz (IDs, Default-Layout, Katalog-Response)."""
|
||||
|
||||
from dashboard_layout_schema import default_layout_dict
|
||||
from widget_catalog import ALLOWED_WIDGET_IDS, WIDGET_CATALOG, catalog_response
|
||||
|
||||
|
||||
def test_catalog_ids_unique_and_match_allowed():
|
||||
ids = [e["id"] for e in WIDGET_CATALOG]
|
||||
assert len(ids) == len(set(ids))
|
||||
assert frozenset(ids) == ALLOWED_WIDGET_IDS
|
||||
|
||||
|
||||
def test_default_layout_follows_catalog_order():
|
||||
d = default_layout_dict()
|
||||
assert d["version"] == 1
|
||||
got = [w["id"] for w in d["widgets"]]
|
||||
assert got == [e["id"] for e in WIDGET_CATALOG]
|
||||
assert all(w["enabled"] is True for w in d["widgets"])
|
||||
|
||||
|
||||
def test_catalog_response_shape():
|
||||
r = catalog_response()
|
||||
assert r["catalog_version"] == 1
|
||||
assert len(r["widgets"]) == len(WIDGET_CATALOG)
|
||||
assert {w["id"] for w in r["widgets"]} == ALLOWED_WIDGET_IDS
|
||||
|
|
@ -30,7 +30,7 @@ MODULE_VERSIONS = {
|
|||
"importdata": "1.0.0",
|
||||
"membership": "2.1.0",
|
||||
"workflow": "0.6.0", # Phase 4: End Node Template Engine
|
||||
"app_dashboard": "1.0.0", # Dashboard-Lab Layout API (/api/app)
|
||||
"app_dashboard": "1.1.0", # Dashboard-Lab + GET /widgets/catalog (Widget-System Iteration 1)
|
||||
}
|
||||
|
||||
CHANGELOG = [
|
||||
|
|
|
|||
55
backend/widget_catalog.py
Normal file
55
backend/widget_catalog.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
Öffentlicher Widget-Katalog (Dashboard-Lab / später Produkt-Dashboard).
|
||||
|
||||
Single Source für: erlaubte IDs, Standard-Reihenfolge, Anzeige-Metadaten für API/GUI.
|
||||
Frontend-Komponenten registrieren dieselben IDs lokal (siehe widgetSystem/registerPilotLabWidgets).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, TypedDict
|
||||
|
||||
|
||||
class WidgetCatalogEntry(TypedDict):
|
||||
id: str
|
||||
title: str
|
||||
description: str
|
||||
|
||||
|
||||
# Reihenfolge in der Liste = Standard-Layout (alle default_enabled: True im Default-Layout)
|
||||
WIDGET_CATALOG: list[WidgetCatalogEntry] = [
|
||||
{
|
||||
"id": "welcome",
|
||||
"title": "Willkommen",
|
||||
"description": "Begrüßung und Kurzkontext",
|
||||
},
|
||||
{
|
||||
"id": "quick_capture",
|
||||
"title": "Schnelleingabe",
|
||||
"description": "Gewicht und Vitalwerte erfassen",
|
||||
},
|
||||
{
|
||||
"id": "kpi_board",
|
||||
"title": "KPI-Kacheln",
|
||||
"description": "Referenzwerte, KF%, Kalorien",
|
||||
},
|
||||
{
|
||||
"id": "body_overview",
|
||||
"title": "Körper (Chart)",
|
||||
"description": "Gewicht & Kennzahlen",
|
||||
},
|
||||
{
|
||||
"id": "activity_overview",
|
||||
"title": "Aktivität",
|
||||
"description": "Training & Konsistenz",
|
||||
},
|
||||
]
|
||||
|
||||
ALLOWED_WIDGET_IDS: frozenset[str] = frozenset(e["id"] for e in WIDGET_CATALOG)
|
||||
|
||||
|
||||
def catalog_response() -> dict[str, Any]:
|
||||
"""Payload für GET /api/app/widgets/catalog."""
|
||||
return {
|
||||
"catalog_version": 1,
|
||||
"widgets": list(WIDGET_CATALOG),
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/**
|
||||
* Dashboard-Lab: Widget-Metadaten (IDs müssen mit backend/dashboard_layout_schema.py übereinstimmen).
|
||||
*/
|
||||
export const DASHBOARD_LAB_WIDGET_META = [
|
||||
{ id: 'welcome', label: 'Willkommen' },
|
||||
{ id: 'quick_capture', label: 'Schnelleingabe' },
|
||||
{ id: 'kpi_board', label: 'KPI-Kacheln' },
|
||||
{ id: 'body_overview', label: 'Körper (Chart)' },
|
||||
{ id: 'activity_overview', label: 'Aktivität' },
|
||||
]
|
||||
|
|
@ -2,45 +2,34 @@ import { useCallback, useEffect, useState } from 'react'
|
|||
import { ChevronDown, ChevronUp, LayoutGrid } from 'lucide-react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { api, formatFastApiDetail } from '../utils/api'
|
||||
import { DASHBOARD_LAB_WIDGET_META } from '../app/areas/dashboardLab/dashboardLabWidgets'
|
||||
import PilotWelcome from '../components/pilot/PilotWelcome'
|
||||
import PilotQuickCapture from '../components/pilot/PilotQuickCapture'
|
||||
import PilotKpiBoard from '../components/pilot/PilotKpiBoard'
|
||||
import PilotBodySection from '../components/pilot/PilotBodySection'
|
||||
import PilotActivitySection from '../components/pilot/PilotActivitySection'
|
||||
import { WidgetRenderer } from '../widgetSystem/dashboardWidgetRegistry'
|
||||
import { ensurePilotLabWidgetsRegistered } from '../widgetSystem/registerPilotLabWidgets'
|
||||
import { moveWidget, toggleWidget } from '../widgetSystem/layoutEditor'
|
||||
|
||||
const metaById = Object.fromEntries(DASHBOARD_LAB_WIDGET_META.map((m) => [m.id, m]))
|
||||
|
||||
function moveWidget(layout, index, delta) {
|
||||
const next = [...layout.widgets]
|
||||
const j = index + delta
|
||||
if (j < 0 || j >= next.length) return layout
|
||||
;[next[index], next[j]] = [next[j], next[index]]
|
||||
return { ...layout, widgets: next }
|
||||
}
|
||||
|
||||
function toggleWidget(layout, index) {
|
||||
const next = layout.widgets.map((w, i) =>
|
||||
i === index ? { ...w, enabled: !w.enabled } : w
|
||||
)
|
||||
const anyOn = next.some((w) => w.enabled)
|
||||
if (!anyOn) return layout
|
||||
return { ...layout, widgets: next }
|
||||
function catalogMetaById(catalog) {
|
||||
if (!catalog?.widgets?.length) return {}
|
||||
return Object.fromEntries(catalog.widgets.map((w) => [w.id, w]))
|
||||
}
|
||||
|
||||
export default function DashboardLabPage() {
|
||||
ensurePilotLabWidgetsRegistered()
|
||||
|
||||
const [refreshTick, setRefreshTick] = useState(0)
|
||||
const bump = () => setRefreshTick((t) => t + 1)
|
||||
const requestRefresh = () => setRefreshTick((t) => t + 1)
|
||||
const [catalog, setCatalog] = useState(null)
|
||||
const [bundle, setBundle] = useState(null)
|
||||
const [layout, setLayout] = useState(null)
|
||||
const [err, setErr] = useState(null)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [msg, setMsg] = useState(null)
|
||||
|
||||
const metaById = catalogMetaById(catalog)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setErr(null)
|
||||
try {
|
||||
const b = await api.getAppDashboardLayout()
|
||||
const [cat, b] = await Promise.all([api.getAppWidgetsCatalog(), api.getAppDashboardLayout()])
|
||||
setCatalog(cat)
|
||||
setBundle(b)
|
||||
setLayout(b.layout)
|
||||
} catch (e) {
|
||||
|
|
@ -92,23 +81,6 @@ export default function DashboardLabPage() {
|
|||
}
|
||||
}
|
||||
|
||||
const renderWidget = (id) => {
|
||||
switch (id) {
|
||||
case 'welcome':
|
||||
return <PilotWelcome key="welcome" />
|
||||
case 'quick_capture':
|
||||
return <PilotQuickCapture key="quick_capture" onSaved={bump} />
|
||||
case 'kpi_board':
|
||||
return <PilotKpiBoard key="kpi_board" refreshTick={refreshTick} />
|
||||
case 'body_overview':
|
||||
return <PilotBodySection key="body_overview" refreshTick={refreshTick} />
|
||||
case 'activity_overview':
|
||||
return <PilotActivitySection key="activity_overview" refreshTick={refreshTick} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (err && !layout) {
|
||||
return (
|
||||
<div style={{ padding: 24, maxWidth: 640, margin: '0 auto' }}>
|
||||
|
|
@ -128,8 +100,6 @@ export default function DashboardLabPage() {
|
|||
)
|
||||
}
|
||||
|
||||
const enabledOrder = layout.widgets.filter((w) => w.enabled)
|
||||
|
||||
return (
|
||||
<div style={{ paddingBottom: 96, textAlign: 'left', maxWidth: 920, margin: '0 auto' }}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
|
|
@ -145,10 +115,10 @@ export default function DashboardLabPage() {
|
|||
App-Bereich: Dashboard-Lab
|
||||
</h1>
|
||||
<p style={{ fontSize: 13, color: 'var(--text2)', lineHeight: 1.6, marginTop: 8 }}>
|
||||
Geschützte Route (Login erforderlich). Widget-Reihenfolge und Sichtbarkeit werden pro Profil in der
|
||||
Datenbank gespeichert — getrennt vom Produktiv-Dashboard. Vergleich:{' '}
|
||||
Widget-System (Iteration 1): Katalog vom Server, Registry im Frontend, Renderer für alle
|
||||
Pilot-Module. Layout wird pro Profil persistiert — getrennt vom Produktiv-Dashboard. Vergleich:{' '}
|
||||
<Link to="/pilot/viz" style={{ color: 'var(--accent)' }}>
|
||||
Pilot-Übersicht (festes Layout)
|
||||
Pilot-Übersicht (festes Standard-Layout)
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
|
|
@ -171,15 +141,11 @@ export default function DashboardLabPage() {
|
|||
Status: {bundle.custom ? 'individuell gespeichert' : 'Standard (nicht in DB)'}
|
||||
</p>
|
||||
)}
|
||||
{err && (
|
||||
<p style={{ fontSize: 12, color: '#D85A30', marginBottom: 8 }}>{err}</p>
|
||||
)}
|
||||
{msg && (
|
||||
<p style={{ fontSize: 12, color: 'var(--accent)', marginBottom: 8 }}>{msg}</p>
|
||||
)}
|
||||
{err && <p style={{ fontSize: 12, color: '#D85A30', marginBottom: 8 }}>{err}</p>}
|
||||
{msg && <p style={{ fontSize: 12, color: 'var(--accent)', marginBottom: 8 }}>{msg}</p>}
|
||||
<ul style={{ listStyle: 'none', padding: 0, margin: '0 0 12px' }}>
|
||||
{layout.widgets.map((w, i) => {
|
||||
const label = metaById[w.id]?.label || w.id
|
||||
const label = metaById[w.id]?.title || w.id
|
||||
return (
|
||||
<li
|
||||
key={w.id}
|
||||
|
|
@ -237,7 +203,7 @@ export default function DashboardLabPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{enabledOrder.map((w) => renderWidget(w.id))}
|
||||
<WidgetRenderer layout={layout} refreshTick={refreshTick} requestRefresh={requestRefresh} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
import { useState } from 'react'
|
||||
import { FlaskConical } from 'lucide-react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import PilotWelcome from '../components/pilot/PilotWelcome'
|
||||
import PilotQuickCapture from '../components/pilot/PilotQuickCapture'
|
||||
import PilotKpiBoard from '../components/pilot/PilotKpiBoard'
|
||||
import PilotBodySection from '../components/pilot/PilotBodySection'
|
||||
import PilotActivitySection from '../components/pilot/PilotActivitySection'
|
||||
import { WidgetRenderer } from '../widgetSystem/dashboardWidgetRegistry'
|
||||
import { ensurePilotLabWidgetsRegistered } from '../widgetSystem/registerPilotLabWidgets'
|
||||
import { DEFAULT_LAB_LAYOUT } from '../widgetSystem/defaultLabLayout'
|
||||
|
||||
/**
|
||||
* Pilot-Übersicht nach Product-Spec:
|
||||
* Willkommen → Schnelleingabe (Gewicht + Vitalwerte) → KPIs (Referenzen + KF% + Ø kcal, max. 9)
|
||||
* → Bereich Körper (Gewicht-Chart 30 T, Ø7/Ø14, Bewertung wie Verlauf)
|
||||
* → Bereich Aktivität (Trainingstyp 30 T, Konsistenz).
|
||||
* Pilot-Übersicht nach Product-Spec (festes Standard-Layout).
|
||||
* Nutzt dasselbe Widget-Rendering wie /app/dashboard-lab.
|
||||
*/
|
||||
export default function PilotVizPage() {
|
||||
ensurePilotLabWidgetsRegistered()
|
||||
|
||||
const [refreshTick, setRefreshTick] = useState(0)
|
||||
const bump = () => setRefreshTick((t) => t + 1)
|
||||
const requestRefresh = () => setRefreshTick((t) => t + 1)
|
||||
|
||||
return (
|
||||
<div style={{ paddingBottom: 96, textAlign: 'left', maxWidth: 920, margin: '0 auto' }}>
|
||||
|
|
@ -37,11 +35,11 @@ export default function PilotVizPage() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<PilotWelcome />
|
||||
<PilotQuickCapture onSaved={bump} />
|
||||
<PilotKpiBoard refreshTick={refreshTick} />
|
||||
<PilotBodySection refreshTick={refreshTick} />
|
||||
<PilotActivitySection refreshTick={refreshTick} />
|
||||
<WidgetRenderer
|
||||
layout={DEFAULT_LAB_LAYOUT}
|
||||
refreshTick={refreshTick}
|
||||
requestRefresh={requestRefresh}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ export const api = {
|
|||
getProfile: () => req('/profile'),
|
||||
updateActiveProfile:(d)=> req('/profile', jput(d)),
|
||||
|
||||
// App-Bereich: Dashboard-Lab (Layout JSON, Issue #65)
|
||||
// App-Bereich: Dashboard-Lab (Layout JSON, Issue #65) + Widget-Katalog
|
||||
getAppWidgetsCatalog: () => req('/app/widgets/catalog'),
|
||||
getAppDashboardLayout: () => req('/app/dashboard-layout'),
|
||||
putAppDashboardLayout: (layout) => req('/app/dashboard-layout', jput(layout)),
|
||||
resetAppDashboardLayout: () => req('/app/dashboard-layout/reset', { method: 'POST' }),
|
||||
|
|
|
|||
60
frontend/src/widgetSystem/dashboardWidgetRegistry.jsx
Normal file
60
frontend/src/widgetSystem/dashboardWidgetRegistry.jsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/** @typedef {{ refreshTick: number, requestRefresh: () => void }} WidgetRenderContext */
|
||||
|
||||
const registry = new Map()
|
||||
|
||||
/**
|
||||
* @param {{ id: string, Component: import('react').ComponentType<any>, mapProps?: (ctx: WidgetRenderContext) => Record<string, unknown> }} spec
|
||||
*/
|
||||
export function registerDashboardWidget(spec) {
|
||||
if (!spec?.id || !spec?.Component) {
|
||||
console.warn('registerDashboardWidget: id und Component erforderlich', spec)
|
||||
return
|
||||
}
|
||||
registry.set(spec.id, spec)
|
||||
}
|
||||
|
||||
export function getRegisteredWidgetIds() {
|
||||
return [...registry.keys()]
|
||||
}
|
||||
|
||||
export function clearDashboardWidgetRegistry() {
|
||||
registry.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Nur für Tests: Registry neu füllen.
|
||||
*/
|
||||
export function __resetDashboardWidgetRegistryForTests() {
|
||||
registry.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {WidgetRenderContext} ctx
|
||||
*/
|
||||
export function renderRegisteredWidget(id, ctx) {
|
||||
const spec = registry.get(id)
|
||||
if (!spec) {
|
||||
return (
|
||||
<div key={id} className="card" style={{ borderColor: 'var(--danger, #D85A30)', marginBottom: 16 }}>
|
||||
<strong>Unbekanntes Widget</strong>
|
||||
<div style={{ fontSize: 13, color: 'var(--text2)' }}>{id}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const { Component } = spec
|
||||
const props = spec.mapProps ? spec.mapProps(ctx) : {}
|
||||
return <Component key={id} {...props} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert alle aktivierten Widgets in Layout-Reihenfolge.
|
||||
* @param {{ version: number, widgets: Array<{ id: string, enabled: boolean }> }} layout
|
||||
* @param {WidgetRenderContext} ctx
|
||||
*/
|
||||
export function WidgetRenderer({ layout, refreshTick, requestRefresh }) {
|
||||
if (!layout?.widgets?.length) return null
|
||||
const ctx = { refreshTick, requestRefresh }
|
||||
const enabled = layout.widgets.filter((w) => w.enabled)
|
||||
return <>{enabled.map((w) => renderRegisteredWidget(w.id, ctx))}</>
|
||||
}
|
||||
15
frontend/src/widgetSystem/defaultLabLayout.js
Normal file
15
frontend/src/widgetSystem/defaultLabLayout.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Standard-Layout v1 (nur Pilot-Fallback ohne API).
|
||||
* Reihenfolge: gleich backend/widget_catalog.WIDGET_CATALOG bei Änderung dort mitpflegen
|
||||
* (oder später nur noch aus GET /api/app/dashboard-layout default_layout beziehen).
|
||||
*/
|
||||
export const DEFAULT_LAB_LAYOUT = {
|
||||
version: 1,
|
||||
widgets: [
|
||||
{ id: 'welcome', enabled: true },
|
||||
{ id: 'quick_capture', enabled: true },
|
||||
{ id: 'kpi_board', enabled: true },
|
||||
{ id: 'body_overview', enabled: true },
|
||||
{ id: 'activity_overview', enabled: true },
|
||||
],
|
||||
}
|
||||
16
frontend/src/widgetSystem/layoutEditor.js
Normal file
16
frontend/src/widgetSystem/layoutEditor.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export function moveWidget(layout, index, delta) {
|
||||
const next = [...layout.widgets]
|
||||
const j = index + delta
|
||||
if (j < 0 || j >= next.length) return layout
|
||||
const t = next[index]
|
||||
next[index] = next[j]
|
||||
next[j] = t
|
||||
return { ...layout, widgets: next }
|
||||
}
|
||||
|
||||
export function toggleWidget(layout, index) {
|
||||
const next = layout.widgets.map((w, i) => (i === index ? { ...w, enabled: !w.enabled } : w))
|
||||
const anyOn = next.some((w) => w.enabled)
|
||||
if (!anyOn) return layout
|
||||
return { ...layout, widgets: next }
|
||||
}
|
||||
47
frontend/src/widgetSystem/registerPilotLabWidgets.js
Normal file
47
frontend/src/widgetSystem/registerPilotLabWidgets.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Pilot/Lab-Widgets registrieren. IDs müssen zu backend/widget_catalog.WIDGET_CATALOG passen.
|
||||
*/
|
||||
import PilotWelcome from '../components/pilot/PilotWelcome'
|
||||
import PilotQuickCapture from '../components/pilot/PilotQuickCapture'
|
||||
import PilotKpiBoard from '../components/pilot/PilotKpiBoard'
|
||||
import PilotBodySection from '../components/pilot/PilotBodySection'
|
||||
import PilotActivitySection from '../components/pilot/PilotActivitySection'
|
||||
import { registerDashboardWidget } from './dashboardWidgetRegistry'
|
||||
|
||||
let _registered = false
|
||||
|
||||
export function ensurePilotLabWidgetsRegistered() {
|
||||
if (_registered) return
|
||||
_registered = true
|
||||
|
||||
registerDashboardWidget({
|
||||
id: 'welcome',
|
||||
Component: PilotWelcome,
|
||||
mapProps: () => ({}),
|
||||
})
|
||||
registerDashboardWidget({
|
||||
id: 'quick_capture',
|
||||
Component: PilotQuickCapture,
|
||||
mapProps: (ctx) => ({ onSaved: ctx.requestRefresh }),
|
||||
})
|
||||
registerDashboardWidget({
|
||||
id: 'kpi_board',
|
||||
Component: PilotKpiBoard,
|
||||
mapProps: (ctx) => ({ refreshTick: ctx.refreshTick }),
|
||||
})
|
||||
registerDashboardWidget({
|
||||
id: 'body_overview',
|
||||
Component: PilotBodySection,
|
||||
mapProps: (ctx) => ({ refreshTick: ctx.refreshTick }),
|
||||
})
|
||||
registerDashboardWidget({
|
||||
id: 'activity_overview',
|
||||
Component: PilotActivitySection,
|
||||
mapProps: (ctx) => ({ refreshTick: ctx.refreshTick }),
|
||||
})
|
||||
}
|
||||
|
||||
/** @internal Nur für Tests */
|
||||
export function __resetPilotLabRegistrationForTests() {
|
||||
_registered = false
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user