Update
This commit is contained in:
+105
-38
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CircleAlert, LogOut, UserRound } from 'lucide-react'
|
||||
import { CircleAlert, LogOut, ShieldCheck, UserRound } from 'lucide-react'
|
||||
import '@fontsource/manrope/latin-500.css'
|
||||
import '@fontsource/manrope/latin-600.css'
|
||||
import '@fontsource/manrope/latin-700.css'
|
||||
@@ -10,6 +10,7 @@ import './styles/app.css'
|
||||
import './styles/premium.css'
|
||||
import './styles/atlas-workbench.css'
|
||||
import './styles/atlas-premium-v2.css'
|
||||
import './styles/professionalization.css'
|
||||
import { LandingPage } from './components/auth/LandingPage'
|
||||
import { ChangeDetectionPanel } from './components/analysis/ChangeDetectionPanel'
|
||||
import { GeoAssistantPanel } from './components/assistant/GeoAssistantPanel'
|
||||
@@ -86,20 +87,39 @@ const workspaceNavGroups: WorkspaceNavigationGroup[] = [
|
||||
{ label: 'Beheer', keys: ['overview', 'system'] },
|
||||
]
|
||||
|
||||
const guestWorkspaceKeys = new Set<WorkspaceKey>(['map', 'analysis'])
|
||||
const guestWorkspaceGroups: WorkspaceNavigationGroup[] = [
|
||||
{ label: 'Demowerkruimte', keys: ['map', 'analysis'] },
|
||||
]
|
||||
|
||||
interface WorkbenchAppProps {
|
||||
username: string | null
|
||||
accessMode: 'open' | 'operator' | 'guest'
|
||||
loggingOut: boolean
|
||||
onLogout: () => void
|
||||
}
|
||||
|
||||
function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JSX.Element {
|
||||
function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchAppProps): JSX.Element {
|
||||
const isGuest = accessMode === 'guest'
|
||||
const [activeWorkspace, setActiveWorkspace] = useState<WorkspaceKey>('map')
|
||||
const [inspectorOpen, setInspectorOpen] = useState(false)
|
||||
const [guestDemoReady, setGuestDemoReady] = useState(!isGuest)
|
||||
const guestDemoStartedRef = useRef(false)
|
||||
const visibleWorkspaceItems = useMemo(
|
||||
() => isGuest ? workspaceNavItems.filter((item) => guestWorkspaceKeys.has(item.key)) : workspaceNavItems,
|
||||
[isGuest],
|
||||
)
|
||||
const visibleWorkspaceGroups = isGuest ? guestWorkspaceGroups : workspaceNavGroups
|
||||
const [mapContentMode, setMapContentMode] = useState<'dataset' | 'analysis'>('dataset')
|
||||
const [mapContextSourceLabel, setMapContextSourceLabel] = useState<string | null>(null)
|
||||
const [mapContextLayerLabel, setMapContextLayerLabel] = useState<string | null>(null)
|
||||
const workbenchMainRef = useRef<HTMLElement | null>(null)
|
||||
const previousWorkspaceRef = useRef<WorkspaceKey>(activeWorkspace)
|
||||
useEffect(() => {
|
||||
if (isGuest && !guestWorkspaceKeys.has(activeWorkspace)) {
|
||||
setActiveWorkspace('map')
|
||||
}
|
||||
}, [activeWorkspace, isGuest])
|
||||
useEffect(() => {
|
||||
workbenchMainRef.current?.scrollTo({ top: 0, left: 0 })
|
||||
if (previousWorkspaceRef.current !== activeWorkspace) {
|
||||
@@ -566,6 +586,7 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
demoWorkflowMessage,
|
||||
loadDemoWorkflow,
|
||||
} = useDemoWorkflow({
|
||||
restrictedMode: isGuest,
|
||||
loadProjects,
|
||||
loadProjectData,
|
||||
loadDatasetDetails,
|
||||
@@ -586,7 +607,15 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
setErrorMessage,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!isGuest || guestDemoStartedRef.current) return
|
||||
guestDemoStartedRef.current = true
|
||||
setGuestDemoReady(false)
|
||||
void loadDemoWorkflow().finally(() => setGuestDemoReady(true))
|
||||
}, [isGuest, loadDemoWorkflow])
|
||||
|
||||
useWorkbenchBootstrap({
|
||||
restrictedMode: isGuest,
|
||||
selectedProjectId,
|
||||
selectedDetectionRunId,
|
||||
detectionClassFilter,
|
||||
@@ -613,7 +642,7 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
})
|
||||
|
||||
const selectedArea = areas.find((area) => area.id === selectedMapAreaId) ?? null
|
||||
const activeWorkspaceItem = workspaceNavItems.find((item) => item.key === activeWorkspace) ?? workspaceNavItems[0]
|
||||
const activeWorkspaceItem = visibleWorkspaceItems.find((item) => item.key === activeWorkspace) ?? visibleWorkspaceItems[0]
|
||||
const mapLayerSourceLabel = useMemo(() => {
|
||||
if (analysisMapLayerActive && changeDetectionResult?.geojson) {
|
||||
return 'Veranderingsanalyse'
|
||||
@@ -687,6 +716,10 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
setActiveWorkspace('exports')
|
||||
}
|
||||
const openWorkflowGuidanceStep = (target: WorkspaceKey) => {
|
||||
if (isGuest && !guestWorkspaceKeys.has(target)) {
|
||||
setActiveWorkspace(target === 'exports' ? 'analysis' : 'map')
|
||||
return
|
||||
}
|
||||
if (target === 'map' && availableMapDatasets.length > 0 && !mapFeatureCollection) {
|
||||
openDatasetInMap(availableMapDatasets[0])
|
||||
return
|
||||
@@ -781,7 +814,7 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
: 'Geen actieve laag'
|
||||
|
||||
return (
|
||||
<div className="app-shell workbench-shell">
|
||||
<div className={isGuest ? "app-shell workbench-shell workbench-shell-guest" : "app-shell workbench-shell"}>
|
||||
<a
|
||||
className="skip-link"
|
||||
href="#workspace-main"
|
||||
@@ -805,8 +838,8 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
<div className="workbench-layout">
|
||||
<WorkbenchNavigation
|
||||
activeWorkspace={activeWorkspace}
|
||||
groups={workspaceNavGroups}
|
||||
items={workspaceNavItems}
|
||||
groups={visibleWorkspaceGroups}
|
||||
items={visibleWorkspaceItems}
|
||||
onSelect={setActiveWorkspace}
|
||||
/>
|
||||
|
||||
@@ -838,18 +871,39 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
<span aria-hidden="true" />
|
||||
<strong>{workspaceDataLoading ? 'Laden' : errorMessage ? 'Aandacht' : 'Gereed'}</strong>
|
||||
</div>
|
||||
{username ? (
|
||||
<div className="context-account" aria-label="Aangemelde gebruiker">
|
||||
<UserRound aria-hidden="true" />
|
||||
<span title={username}>{username}</span>
|
||||
{username || isGuest ? (
|
||||
<div
|
||||
className={isGuest ? 'context-account context-account-guest' : 'context-account'}
|
||||
aria-label={isGuest ? 'Tijdelijke gastensessie' : 'Aangemelde gebruiker'}
|
||||
>
|
||||
{isGuest ? <ShieldCheck aria-hidden="true" /> : <UserRound aria-hidden="true" />}
|
||||
<span title={username ?? undefined}>{isGuest ? 'Gastmodus' : username}</span>
|
||||
<button type="button" onClick={onLogout} disabled={loggingOut}>
|
||||
<LogOut aria-hidden="true" />
|
||||
<span>{loggingOut ? 'Uitloggen…' : 'Uitloggen'}</span>
|
||||
<span>{loggingOut ? 'Afsluiten…' : isGuest ? 'Demo afsluiten' : 'Uitloggen'}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
{isGuest ? (
|
||||
<div className="guest-mode-banner" role="status">
|
||||
<ShieldCheck aria-hidden="true" />
|
||||
<div>
|
||||
<strong>Tijdelijke demowerkruimte</strong>
|
||||
<span>U verkent vooraf geladen voorbeelddata. Wijzigingen, nieuwe analyses en operatorfuncties zijn uitgeschakeld.</span>
|
||||
</div>
|
||||
<span className="guest-mode-badge">Alleen-lezen</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isGuest && (!guestDemoReady || loadingDemoWorkflow) ? (
|
||||
<div className="guest-demo-loading" role="status" aria-live="polite">
|
||||
<span aria-hidden="true" />
|
||||
<strong>Demodata en kwaliteitsbewijs worden voorbereid…</strong>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="workbench-content">
|
||||
|
||||
<main
|
||||
@@ -867,7 +921,7 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
<WorkspaceSignal workspace={activeWorkspace} />
|
||||
<div className="workspace-heading-actions">
|
||||
<p>{activeWorkspaceItem.description}</p>
|
||||
{activeWorkspace !== 'overview' ? (
|
||||
{!isGuest && activeWorkspace !== 'overview' ? (
|
||||
<button
|
||||
type="button"
|
||||
className={inspectorOpen ? 'inspector-toggle inspector-toggle-active' : 'inspector-toggle'}
|
||||
@@ -950,6 +1004,7 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
|
||||
<div className="workspace-persistent-map" hidden={activeWorkspace !== 'map'}>
|
||||
<MapWorkspace
|
||||
readOnly={isGuest}
|
||||
selectedProjectId={selectedProjectId}
|
||||
projects={projects}
|
||||
areas={areas}
|
||||
@@ -1045,8 +1100,8 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
onRefreshProjectData={() => (
|
||||
selectedProjectId ? loadProjectData(selectedProjectId) : Promise.resolve(null)
|
||||
)}
|
||||
onOpenAssistant={() => setActiveWorkspace('assistant')}
|
||||
onOpenExports={() => setActiveWorkspace('exports')}
|
||||
onOpenAssistant={() => setActiveWorkspace(isGuest ? 'analysis' : 'assistant')}
|
||||
onOpenExports={() => setActiveWorkspace(isGuest ? 'analysis' : 'exports')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1063,29 +1118,39 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
evidenceLoading={qualityEvidenceLoading}
|
||||
evidenceError={qualityEvidenceError}
|
||||
onOpenMapWorkspace={() => setActiveWorkspace('map')}
|
||||
onOpenAnalysisWorkspace={() => setActiveWorkspace('ai')}
|
||||
onOpenAnalysisWorkspace={() => setActiveWorkspace(isGuest ? 'map' : 'ai')}
|
||||
/>
|
||||
<details className="secondary-analysis-disclosure">
|
||||
<summary>
|
||||
<span>Historische vectorlagen vergelijken</span>
|
||||
<strong>Geavanceerd</strong>
|
||||
</summary>
|
||||
<ChangeDetectionPanel
|
||||
vectorDatasets={availableVectorDatasets}
|
||||
sourceDatasetId={changeSourceDatasetId}
|
||||
targetDatasetId={changeTargetDatasetId}
|
||||
iouThreshold={changeIouThreshold}
|
||||
includeUnchanged={changeIncludeUnchanged}
|
||||
running={runningChangeDetection}
|
||||
result={changeDetectionResult}
|
||||
error={changeDetectionError}
|
||||
onSourceDatasetChange={setChangeSourceDatasetId}
|
||||
onTargetDatasetChange={setChangeTargetDatasetId}
|
||||
onIouThresholdChange={setChangeIouThreshold}
|
||||
onIncludeUnchangedChange={setChangeIncludeUnchanged}
|
||||
onRun={runChangeDetection}
|
||||
/>
|
||||
</details>
|
||||
{!isGuest ? (
|
||||
<details className="secondary-analysis-disclosure">
|
||||
<summary>
|
||||
<span>Historische vectorlagen vergelijken</span>
|
||||
<strong>Geavanceerd</strong>
|
||||
</summary>
|
||||
<ChangeDetectionPanel
|
||||
vectorDatasets={availableVectorDatasets}
|
||||
sourceDatasetId={changeSourceDatasetId}
|
||||
targetDatasetId={changeTargetDatasetId}
|
||||
iouThreshold={changeIouThreshold}
|
||||
includeUnchanged={changeIncludeUnchanged}
|
||||
running={runningChangeDetection}
|
||||
result={changeDetectionResult}
|
||||
error={changeDetectionError}
|
||||
onSourceDatasetChange={setChangeSourceDatasetId}
|
||||
onTargetDatasetChange={setChangeTargetDatasetId}
|
||||
onIouThresholdChange={setChangeIouThreshold}
|
||||
onIncludeUnchangedChange={setChangeIncludeUnchanged}
|
||||
onRun={runChangeDetection}
|
||||
/>
|
||||
</details>
|
||||
) : (
|
||||
<div className="guest-readonly-card">
|
||||
<ShieldCheck aria-hidden="true" />
|
||||
<div>
|
||||
<strong>Controleerbaar voorbeeldresultaat</strong>
|
||||
<span>Deze kwaliteitsweergave gebruikt de vooraf berekende demo. Nieuwe vergelijkingen zijn alleen beschikbaar voor operators.</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1250,7 +1315,7 @@ function WorkbenchApp({ username, loggingOut, onLogout }: WorkbenchAppProps): JS
|
||||
) : null}
|
||||
</main>
|
||||
|
||||
{inspectorOpen ? (
|
||||
{inspectorOpen && !isGuest ? (
|
||||
<aside className="workbench-inspector" id="workbench-inspector" aria-label="Details van de huidige selectie">
|
||||
<WorkbenchInspector
|
||||
selectedProject={selectedProject}
|
||||
@@ -1357,6 +1422,7 @@ function App(): JSX.Element {
|
||||
return (
|
||||
<LandingPage
|
||||
serviceError={sessionError}
|
||||
guestAccessEnabled={session.guest_access_enabled}
|
||||
onAuthenticated={handleAuthenticated}
|
||||
/>
|
||||
)
|
||||
@@ -1364,7 +1430,8 @@ function App(): JSX.Element {
|
||||
|
||||
return (
|
||||
<WorkbenchApp
|
||||
username={session.authentication_required ? session.username : null}
|
||||
username={session.username}
|
||||
accessMode={session.role ?? (session.authentication_required ? 'operator' : 'open')}
|
||||
loggingOut={loggingOut}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user