Replace demo data with 2026 R&D planning, fix zoom and dimension-aware creation

Seeder: Replace 12 demo projects with 6 real 2026 projects from Planning PPTX:
- BRIDGE (Pilot Klundert), CRISP (Compressor Aanbesteding), WISE (Monsternamekast),
  Gemaal 3.0, Afvlakkingsregeling, Structuur & Borging
- 4 strategic themes: Architectuur, Productiewaardig, Lab, Governance
- Real team members, commitments, documents, and dependencies

MetroCanvas: Fix zoom-out scaling
- Wider transition range (0.6→0.25 instead of 0.5→0.1) for smoother feel
- Animated zoom reset on dimension commit (400ms ease) instead of jarring snap
- Guard against re-entry during transitions with isCommitting flag
- Expose dimension metadata (parentEntityType/Id/Name) for parent components

FloatingActions: Dimension-aware creation
- Shows "Nieuw commitment/document" when inside a project dimension
- Shows "Nieuw project/thema" at root level
- Receives depth and parentEntityType props from MetroMap

MetroMap: Wire dimension tracking
- Tracks canvasDepth/canvasDimension from MetroCanvas dimension-change events
- Updates breadcrumb for both page-level and canvas-level navigation
- Passes dimension context to FloatingActions and CommitmentForm

MapDataService: Add parent metadata to buildProjectChildren output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-04-08 08:50:51 +02:00
parent 926872a082
commit 6711cd01a3
6 changed files with 531 additions and 415 deletions

View File

@@ -17,34 +17,62 @@ const props = defineProps({
speerpunten: { type: Array, default: () => [] },
})
// Canvas ref
const canvasRef = ref(null)
// Navigation state
const selectedNode = ref(null)
const showPreview = ref(false)
// Reactive breadcrumb based on mapData level and project
// Dimension tracking (synced from canvas zoom transitions)
const canvasDepth = ref(1)
const canvasDimension = ref(null)
// Reactive breadcrumb based on both page-level and canvas-level navigation
const breadcrumbPath = computed(() => {
const level = props.mapData.level ?? 1
const pageLevel = props.mapData.level ?? 1
const project = props.mapData.project ?? null
const path = [{ label: 'Strategie', level: 1, data: null }]
if (level === 2 && project) {
path.push({ label: project.name ?? project, level: 2, data: project })
if (pageLevel === 2 && project) {
// Page-level project view
path.push({ label: project.naam ?? project.name ?? 'Project', level: 2, data: project })
} else if (canvasDepth.value > 1 && canvasDimension.value) {
// Canvas zoom-in dimension
path.push({
label: canvasDimension.value.parentName ?? 'Detail',
level: 2,
data: canvasDimension.value,
})
}
return path
})
// Dimension-aware project ID: from page props OR from canvas zoom
const currentProjectId = computed(() => {
// Page-level project
if (props.mapData.project?.id) return props.mapData.project.id
// Canvas zoom-level project
if (canvasDimension.value?.parentEntityType === 'project') {
return canvasDimension.value.parentEntityId
}
return null
})
// Entity type in current dimension (for FAB awareness)
const currentParentEntityType = computed(() => {
if (props.mapData.level === 2) return 'project'
return canvasDimension.value?.parentEntityType ?? null
})
// Handlers
const handleNodeClick = (node) => {
selectedNode.value = node
showPreview.value = true
}
const handleNodeHover = (node) => {
// Could highlight related nodes/connections
}
const handleNodeLeave = () => {
// Reset highlights
}
const handleNodeHover = (node) => {}
const handleNodeLeave = () => {}
const handleZoomIn = (node) => {
showPreview.value = false
@@ -61,9 +89,17 @@ const handleBreadcrumbNavigate = (item, index) => {
}
}
const handleDimensionChange = (event) => {
canvasDepth.value = event.depth
canvasDimension.value = event.dimension ?? null
// Close preview when dimension changes
showPreview.value = false
selectedNode.value = null
}
const handleCliCommand = (command) => {
console.log('CLI command:', command)
// Will be connected to AI service
}
const logout = () => {
@@ -71,7 +107,6 @@ const logout = () => {
}
const user = computed(() => page.props.auth?.user)
const hasNodes = computed(() => props.mapData.nodes && props.mapData.nodes.length > 0)
// Modal state
@@ -88,8 +123,10 @@ const handleCreateCommitment = () => {
showCommitmentForm.value = true
}
// Get current project ID when at level 2
const currentProjectId = computed(() => props.mapData.project?.id ?? null)
const handleCreateDocument = () => {
// Future: document upload modal
console.log('Create document in project:', currentProjectId.value)
}
</script>
<template>
@@ -108,6 +145,7 @@ const currentProjectId = computed(() => props.mapData.project?.id ?? null)
<template v-if="hasNodes">
<MetroCanvas
ref="canvasRef"
:nodes="props.mapData.nodes"
:lines="props.mapData.lines"
:connections="props.mapData.connections"
@@ -115,6 +153,7 @@ const currentProjectId = computed(() => props.mapData.project?.id ?? null)
@node-click="handleNodeClick"
@node-hover="handleNodeHover"
@node-leave="handleNodeLeave"
@dimension-change="handleDimensionChange"
/>
<NodePreview
@@ -130,8 +169,13 @@ const currentProjectId = computed(() => props.mapData.project?.id ?? null)
</div>
<FloatingActions
:depth="canvasDepth"
:parent-entity-type="currentParentEntityType"
:parent-project-id="currentProjectId"
@create-project="handleCreateProject"
@create-theme="handleCreateProject"
@create-commitment="handleCreateCommitment"
@create-document="handleCreateDocument"
/>
<ProjectForm
@@ -212,7 +256,7 @@ const currentProjectId = computed(() => props.mapData.project?.id ?? null)
align-items: center;
justify-content: center;
height: 100%;
padding-top: 48px; /* account for top-bar */
padding-top: 48px;
}
.empty-message {