Add logout button, fix metro label positioning

- Add top bar with breadcrumb (left) and user name + logout button (right)
- Move station labels below dots (centered) to prevent line overlap
- Move line labels further above stations
- Increase vertical spacing between metro lines (160px) for label clearance
- Align station x-coordinates for cleaner map layout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-04-01 15:30:14 +02:00
parent 0b71dbca81
commit 15848b5e96
3 changed files with 85 additions and 34 deletions

View File

@@ -25,18 +25,9 @@ const emit = defineEmits(['navigate'])
<style scoped> <style scoped>
.breadcrumb { .breadcrumb {
position: fixed;
top: 12px;
left: 16px;
z-index: 50;
font-family: 'VT323', monospace; font-family: 'VT323', monospace;
font-size: 16px; font-size: 16px;
color: #8892b0; color: #8892b0;
background: rgba(26, 26, 46, 0.85);
padding: 6px 14px;
border-radius: 4px;
border: 1px solid rgba(0, 210, 255, 0.15);
backdrop-filter: blur(8px);
} }
.breadcrumb-item.active { .breadcrumb-item.active {

View File

@@ -129,11 +129,11 @@ const renderMap = () => {
.attr('stroke-linecap', 'round') .attr('stroke-linecap', 'round')
.attr('opacity', 0.7) .attr('opacity', 0.7)
// Line label // Line label — positioned well above the first station
if (lineNodes.length > 0) { if (lineNodes.length > 0) {
g.append('text') g.append('text')
.attr('x', lineNodes[0].x - 10) .attr('x', lineNodes[0].x - 10)
.attr('y', lineNodes[0].y - 25) .attr('y', lineNodes[0].y - 35)
.attr('fill', color) .attr('fill', color)
.attr('font-family', "'VT323', monospace") .attr('font-family', "'VT323', monospace")
.attr('font-size', '16px') .attr('font-size', '16px')
@@ -217,20 +217,22 @@ const renderMap = () => {
}) })
.attr('stroke-width', 2) .attr('stroke-width', 2)
// Station labels // Station labels — positioned below the station dot to avoid line overlap
nodeGroups.append('text') nodeGroups.append('text')
.attr('x', 18) .attr('x', 0)
.attr('y', 5) .attr('y', 28)
.attr('text-anchor', 'middle')
.attr('fill', '#e8e8e8') .attr('fill', '#e8e8e8')
.attr('font-family', "'VT323', monospace") .attr('font-family', "'VT323', monospace")
.attr('font-size', '14px') .attr('font-size', '14px')
.text(d => d.name) .text(d => d.name)
// Status badge // Status badge — below the label
nodeGroups.filter(d => d.badge) nodeGroups.filter(d => d.badge)
.append('text') .append('text')
.attr('x', 18) .attr('x', 0)
.attr('y', 20) .attr('y', 42)
.attr('text-anchor', 'middle')
.attr('fill', '#8892b0') .attr('fill', '#8892b0')
.attr('font-family', "'IBM Plex Mono', monospace") .attr('font-family', "'IBM Plex Mono', monospace")
.attr('font-size', '10px') .attr('font-size', '10px')

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { usePage } from '@inertiajs/vue3' import { usePage, router } from '@inertiajs/vue3'
import MetroCanvas from '@/Components/MetroMap/MetroCanvas.vue' import MetroCanvas from '@/Components/MetroMap/MetroCanvas.vue'
import Breadcrumb from '@/Components/MetroMap/Breadcrumb.vue' import Breadcrumb from '@/Components/MetroMap/Breadcrumb.vue'
import NodePreview from '@/Components/MetroMap/NodePreview.vue' import NodePreview from '@/Components/MetroMap/NodePreview.vue'
@@ -24,24 +24,24 @@ const demoLines = ref([
]) ])
const demoNodes = ref([ const demoNodes = ref([
// Water Quality line // Water Quality line — y spacing of 160px between lines for label clearance
{ id: 'wq1', name: 'Smart Sensors', lineId: 'water-quality', x: -200, y: -150, order: 1, status: 'actief', description: 'Slimme sensoren voor waterkwaliteitsmonitoring', owner: 'Jan Visser', children: 5, badge: 'Experiment' }, { id: 'wq1', name: 'Smart Sensors', lineId: 'water-quality', x: -200, y: -240, order: 1, status: 'actief', description: 'Slimme sensoren voor waterkwaliteitsmonitoring', owner: 'Jan Visser', children: 5, badge: 'Experiment' },
{ id: 'wq2', name: 'Biomonitoring', lineId: 'water-quality', x: 0, y: -150, order: 2, status: 'concept', description: 'Biologische monitoring methoden', owner: 'Sara Jansen', children: 3, badge: 'Concept' }, { id: 'wq2', name: 'Biomonitoring', lineId: 'water-quality', x: 50, y: -240, order: 2, status: 'concept', description: 'Biologische monitoring methoden', owner: 'Sara Jansen', children: 3, badge: 'Concept' },
{ id: 'wq3', name: 'Voorspelmodel', lineId: 'water-quality', x: 200, y: -150, order: 3, status: 'signaal', description: 'Predictief model waterkwaliteit', badge: 'Signaal' }, { id: 'wq3', name: 'Voorspelmodel', lineId: 'water-quality', x: 300, y: -240, order: 3, status: 'signaal', description: 'Predictief model waterkwaliteit', badge: 'Signaal' },
// Smart Infrastructure line // Smart Infrastructure line
{ id: 'si1', name: 'Edge Computing', lineId: 'smart-infra', x: -200, y: -20, order: 1, status: 'actief', description: 'Edge-layer evolutie voor OT-omgevingen', owner: 'Rene de Ren', children: 8, badge: 'Pilot' }, { id: 'si1', name: 'Edge Computing', lineId: 'smart-infra', x: -200, y: -80, order: 1, status: 'actief', description: 'Edge-layer evolutie voor OT-omgevingen', owner: 'Rene de Ren', children: 8, badge: 'Pilot' },
{ id: 'si2', name: 'Digital Twin', lineId: 'smart-infra', x: 50, y: -20, order: 2, status: 'verkenning', description: 'Digitale tweeling van zuiveringsinstallaties', children: 2, badge: 'Verkenning' }, { id: 'si2', name: 'Digital Twin', lineId: 'smart-infra', x: 50, y: -80, order: 2, status: 'verkenning', description: 'Digitale tweeling van zuiveringsinstallaties', children: 2, badge: 'Verkenning' },
{ id: 'si3', name: 'Predictive Maint.', lineId: 'smart-infra', x: 250, y: -20, order: 3, status: 'concept', description: 'Voorspellend onderhoud op basis van sensordata', badge: 'Concept' }, { id: 'si3', name: 'Predictive Maint.', lineId: 'smart-infra', x: 300, y: -80, order: 3, status: 'concept', description: 'Voorspellend onderhoud op basis van sensordata', badge: 'Concept' },
// Data-driven line // Data-driven line
{ id: 'dd1', name: 'Data Platform', lineId: 'data-driven', x: -150, y: 110, order: 1, status: 'afgerond', description: 'Centraal dataplatform voor operationele data', owner: 'Lisa de Boer', badge: 'Afgerond' }, { id: 'dd1', name: 'Data Platform', lineId: 'data-driven', x: -200, y: 80, order: 1, status: 'afgerond', description: 'Centraal dataplatform voor operationele data', owner: 'Lisa de Boer', badge: 'Afgerond' },
{ id: 'dd2', name: 'ML Pipeline', lineId: 'data-driven', x: 80, y: 110, order: 2, status: 'actief', description: 'Machine learning pipeline voor anomalie-detectie', owner: 'Tom Bakker', children: 4, badge: 'Experiment' }, { id: 'dd2', name: 'ML Pipeline', lineId: 'data-driven', x: 50, y: 80, order: 2, status: 'actief', description: 'Machine learning pipeline voor anomalie-detectie', owner: 'Tom Bakker', children: 4, badge: 'Experiment' },
{ id: 'dd3', name: 'Dashboard Suite', lineId: 'data-driven', x: 280, y: 110, order: 3, status: 'overdracht_bouwen', description: 'Operationele dashboards voor beheerders', badge: 'Overdracht' }, { id: 'dd3', name: 'Dashboard Suite', lineId: 'data-driven', x: 300, y: 80, order: 3, status: 'overdracht_bouwen', description: 'Operationele dashboards voor beheerders', badge: 'Overdracht' },
// Sustainability line // Sustainability line
{ id: 'su1', name: 'Energietransitie', lineId: 'sustainability', x: -100, y: 240, order: 1, status: 'pilot', description: 'Energieneutraal zuiveren', owner: 'Mark de Vries', children: 6, badge: 'Pilot' }, { id: 'su1', name: 'Energietransitie', lineId: 'sustainability', x: -200, y: 240, order: 1, status: 'pilot', description: 'Energieneutraal zuiveren', owner: 'Mark de Vries', children: 6, badge: 'Pilot' },
{ id: 'su2', name: 'Circulair Water', lineId: 'sustainability', x: 150, y: 240, order: 2, status: 'verkenning', description: 'Circulaire waterbehandeling', badge: 'Verkenning' }, { id: 'su2', name: 'Circulair Water', lineId: 'sustainability', x: 50, y: 240, order: 2, status: 'verkenning', description: 'Circulaire waterbehandeling', badge: 'Verkenning' },
]) ])
const demoConnections = ref([ const demoConnections = ref([
@@ -86,14 +86,27 @@ const handleCliCommand = (command) => {
console.log('CLI command:', command) console.log('CLI command:', command)
// Will be connected to AI service // Will be connected to AI service
} }
const logout = () => {
router.post('/logout')
}
const user = computed(() => page.props.auth?.user)
</script> </script>
<template> <template>
<div class="metro-map-page"> <div class="metro-map-page">
<Breadcrumb <!-- Top bar: breadcrumb left, user/logout right -->
:path="breadcrumbPath" <div class="top-bar">
@navigate="handleBreadcrumbNavigate" <Breadcrumb
/> :path="breadcrumbPath"
@navigate="handleBreadcrumbNavigate"
/>
<div class="user-controls">
<span v-if="user" class="user-name">{{ user.name }}</span>
<button @click="logout" class="logout-btn">[LOGOUT]</button>
</div>
</div>
<MetroCanvas <MetroCanvas
:nodes="demoNodes" :nodes="demoNodes"
@@ -124,4 +137,49 @@ const handleCliCommand = (command) => {
position: relative; position: relative;
background: #1a1a2e; background: #1a1a2e;
} }
.top-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 50;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background: rgba(26, 26, 46, 0.85);
border-bottom: 1px solid rgba(0, 210, 255, 0.15);
backdrop-filter: blur(8px);
}
.user-controls {
display: flex;
align-items: center;
gap: 12px;
}
.user-name {
font-family: 'VT323', monospace;
font-size: 16px;
color: #8892b0;
}
.logout-btn {
font-family: 'VT323', monospace;
font-size: 14px;
color: #e94560;
background: none;
border: 1px solid rgba(233, 69, 96, 0.3);
padding: 4px 10px;
border-radius: 3px;
cursor: pointer;
transition: all 0.2s;
}
.logout-btn:hover {
background: rgba(233, 69, 96, 0.15);
border-color: #e94560;
box-shadow: 0 0 10px rgba(233, 69, 96, 0.2);
}
</style> </style>