- 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.
131 lines
3.9 KiB
JavaScript
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>
|
|
)
|
|
}
|