mitai-jinkendo/frontend/src/pages/Dashboard.jsx
Lars e4e2f23d7f
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 17s
feat: Enhance dashboard layout and widget configuration
- Updated dashboard layout schema to introduce separate default layouts for product and lab dashboards.
- Added new functions for managing product and lab default layouts, improving user customization options.
- Updated app_dashboard version to 1.9.0 to reflect the introduction of product vs lab layout defaults and new API fields for dashboard configuration.
- Enhanced tests to validate new layout functionalities and ensure proper widget visibility based on user settings.
2026-04-08 07:41:16 +02:00

131 lines
3.9 KiB
JavaScript

import { useEffect, useMemo, useState } from 'react'
import { Link, useNavigate, useLocation } from 'react-router-dom'
import { LayoutDashboard } from 'lucide-react'
import { api } from '../utils/api'
import { useProfile } from '../context/ProfileContext'
import TrialBanner from '../components/TrialBanner'
import EmailVerificationBanner from '../components/EmailVerificationBanner'
import { ensurePilotLabWidgetsRegistered } from '../widgetSystem/registerPilotLabWidgets'
import { WidgetRenderer } from '../widgetSystem/dashboardWidgetRegistry'
function catalogMetaById(catalog) {
if (!catalog?.widgets?.length) return {}
return Object.fromEntries(catalog.widgets.map((w) => [w.id, w]))
}
export default function Dashboard() {
const nav = useNavigate()
const location = useLocation()
const { activeProfile } = useProfile()
const [adminDeniedHint, setAdminDeniedHint] = useState(false)
const [layoutBundle, setLayoutBundle] = useState(null)
const [catalog, setCatalog] = useState(null)
const [layoutLoading, setLayoutLoading] = useState(true)
const [refreshTick, setRefreshTick] = useState(0)
const requestRefresh = () => setRefreshTick((t) => t + 1)
useEffect(() => {
ensurePilotLabWidgetsRegistered()
}, [])
useEffect(() => {
let cancel = false
setLayoutLoading(true)
Promise.all([api.getAppDashboardLayout(), api.getAppWidgetsCatalog()])
.then(([b, c]) => {
if (cancel) return
setLayoutBundle(b)
setCatalog(c)
})
.catch(() => {
if (cancel) return
setLayoutBundle(null)
setCatalog(null)
})
.finally(() => {
if (!cancel) setLayoutLoading(false)
})
return () => {
cancel = true
}
}, [])
useEffect(() => {
if (!location.state?.adminDenied) return
setAdminDeniedHint(true)
nav('.', { replace: true, state: null })
const clear = window.setTimeout(() => setAdminDeniedHint(false), 8000)
return () => window.clearTimeout(clear)
}, [location.state, nav])
const metaById = useMemo(() => catalogMetaById(catalog), [catalog])
const layoutForPreview = useMemo(() => {
if (!layoutBundle?.layout) return null
const L = layoutBundle.layout
return {
...L,
widgets: L.widgets.map((w) => ({
...w,
enabled: w.enabled && metaById[w.id]?.allowed !== false,
})),
}
}, [layoutBundle, metaById])
return (
<div className="dashboard-page">
{adminDeniedHint && (
<div
role="alert"
style={{
marginBottom: 14,
padding: '12px 14px',
borderRadius: 10,
background: '#FCEBEB',
border: '1px solid #D85A3033',
fontSize: 13,
color: '#D85A30',
lineHeight: 1.5,
}}
>
<strong>Kein Admin-Zugriff.</strong> Dieser Bereich ist nur für Konten mit Administrator-Rolle. Du wurdest zur
Übersicht weitergeleitet.
</div>
)}
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 10 }}>
<Link
to="/settings/dashboard-layout"
className="btn btn-secondary"
style={{
fontSize: 12,
padding: '8px 12px',
textDecoration: 'none',
display: 'inline-flex',
alignItems: 'center',
gap: 8,
}}
>
<LayoutDashboard size={16} />
Übersicht anpassen
</Link>
</div>
{activeProfile && <EmailVerificationBanner profile={activeProfile} />}
{activeProfile && <TrialBanner profile={activeProfile} />}
{layoutLoading && (
<div className="empty-state">
<div className="spinner" />
</div>
)}
{!layoutLoading && layoutForPreview && (
<WidgetRenderer layout={layoutForPreview} refreshTick={refreshTick} requestRefresh={requestRefresh} />
)}
</div>
)
}