feat: mobile-responsive dashboard v2
CI - Build & Test / Backend (.NET) (push) Successful in 26s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 16s
CI - Build & Test / Security Check (push) Successful in 3s

This commit is contained in:
2026-06-14 12:16:06 +02:00
parent 58675f0c69
commit 0f8939306d
8 changed files with 262 additions and 8 deletions
@@ -168,4 +168,24 @@ defineEmits<{
.blk:hover { .blk:hover {
background: rgba(251,113,133,.22); background: rgba(251,113,133,.22);
} }
@media (max-width: 767px) {
.alertbar {
flex-wrap: wrap;
gap: 8px;
padding: 10px;
}
.seg {
flex: 0 0 calc(50% - 4px);
}
.sep {
display: none;
}
.blk {
margin-left: 0;
}
}
</style> </style>
@@ -288,12 +288,12 @@ function handleReset() {
@click="handleReset" @click="handleReset"
> >
<span class="btn-icon" v-html="icons.flow || ''"></span> <span class="btn-icon" v-html="icons.flow || ''"></span>
Reset <span class="reset-label">Reset</span>
</button> </button>
<button class="add-btn" @click="emit('add')"> <button class="add-btn" @click="emit('add')" title="Agent hinzufügen">
<span class="btn-icon" v-html="icons.plus || ''"></span> <span class="btn-icon" v-html="icons.plus || ''"></span>
Agent hinzufügen <span class="add-label">Agent hinzufügen</span>
</button> </button>
</div> </div>
@@ -481,4 +481,28 @@ function handleReset() {
:deep(.node.dragging) { :deep(.node.dragging) {
cursor: grabbing; cursor: grabbing;
} }
@media (max-width: 767px) {
.add-label {
display: none;
}
.reset-label {
display: none;
}
.add-btn {
width: 34px;
padding: 0;
display: grid;
place-items: center;
}
.reset-btn {
width: 30px;
padding: 0;
display: grid;
place-items: center;
}
}
</style> </style>
@@ -251,6 +251,22 @@ watch(
@keyframes blink { 50% { opacity: 0; } } @keyframes blink { 50% { opacity: 0; } }
@media (max-width: 767px) {
.iris-panel {
width: 100%;
flex: 0 0 auto;
max-height: 45vh;
}
.chat-scroll {
max-height: 30vh;
}
.expand-btn {
display: none;
}
}
/* ── Input ───────────────────────────────────── */ /* ── Input ───────────────────────────────────── */
.chat-in { .chat-in {
padding: 12px; padding: 12px;
@@ -146,4 +146,20 @@ function statusLabel(s: TaskItem['status']): string {
padding: 12px; padding: 12px;
white-space: nowrap; white-space: nowrap;
} }
@media (max-width: 767px) {
.tstrip {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.tstrip::-webkit-scrollbar {
display: none;
}
.tcard {
flex: 0 0 200px;
}
}
</style> </style>
+58 -1
View File
@@ -6,6 +6,14 @@ import { useAgentStore } from '../../stores/agents'
import { useTaskStore } from '../../stores/tasks' import { useTaskStore } from '../../stores/tasks'
import { navigation, icons } from '../../composables/icons' import { navigation, icons } from '../../composables/icons'
import type { NavGroupDef } from '../../composables/icons' import type { NavGroupDef } from '../../composables/icons'
defineProps<{
mobileOpen?: boolean
}>()
defineEmits<{
close: []
}>()
import NavGroup from './NavGroup.vue' import NavGroup from './NavGroup.vue'
import { initials } from '../../utils/format' import { initials } from '../../utils/format'
@@ -63,7 +71,8 @@ const dynamicNavigation = computed<NavGroupDef[]>(() => {
</script> </script>
<template> <template>
<aside class="sidebar"> <aside :class="['sidebar', { open: mobileOpen }]">
<button class="sidebar-close" @click="$emit('close')" v-html="icons.chevron_left || ''"></button>
<!-- Brand --> <!-- Brand -->
<div class="side-top"> <div class="side-top">
<div class="brand-mark" v-html="icons.command || ''"></div> <div class="brand-mark" v-html="icons.command || ''"></div>
@@ -171,6 +180,54 @@ const dynamicNavigation = computed<NavGroupDef[]>(() => {
background: rgba(124,108,255,.06); background: rgba(124,108,255,.06);
} }
.sidebar-close {
display: none;
}
@media (max-width: 767px) {
.sidebar {
position: fixed;
left: 0;
top: 0;
z-index: 100;
height: 100vh;
width: 280px;
transform: translateX(-100%);
transition: transform 0.25s ease;
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-close {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 18px;
right: 12px;
width: 30px;
height: 30px;
border-radius: 8px;
border: none;
background: transparent;
color: var(--tx-2);
cursor: pointer;
z-index: 1;
}
.sidebar-close:hover {
background: rgba(124,108,255,.1);
color: var(--tx);
}
.sidebar-close :deep(svg) {
width: 18px;
height: 18px;
}
}
.avatar { .avatar {
width: 34px; width: 34px;
height: 34px; height: 34px;
+70 -2
View File
@@ -4,10 +4,17 @@ import { icons } from '../../composables/icons'
defineProps<{ defineProps<{
connected?: boolean connected?: boolean
}>() }>()
defineEmits<{
'toggle-sidebar': []
}>()
</script> </script>
<template> <template>
<header class="topbar"> <header class="topbar">
<!-- Hamburger (mobile only) -->
<button class="hamburger" @click="$emit('toggle-sidebar')" v-html="icons.list || ''"></button>
<!-- Search --> <!-- Search -->
<div class="search"> <div class="search">
<span class="search-icon" v-html="icons.search || ''"></span> <span class="search-icon" v-html="icons.search || ''"></span>
@@ -24,9 +31,9 @@ defineProps<{
</span> </span>
<!-- Ask Iris Button --> <!-- Ask Iris Button -->
<button class="btn btn-primary"> <button class="btn btn-primary ask-iris-btn">
<span class="btn-icon" v-html="icons.spark || ''"></span> <span class="btn-icon" v-html="icons.spark || ''"></span>
Ask Iris <span class="ask-label">Ask Iris</span>
</button> </button>
</header> </header>
</template> </template>
@@ -138,4 +145,65 @@ defineProps<{
height: 15px; height: 15px;
} }
.hamburger {
display: none;
}
@media (max-width: 767px) {
.topbar {
padding: 0 14px;
}
.search {
flex: 1;
max-width: none;
}
.hamburger {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 9px;
border: none;
background: transparent;
color: var(--tx-2);
cursor: pointer;
flex: 0 0 auto;
}
.hamburger:hover {
background: rgba(124,108,255,.1);
color: var(--tx);
}
.hamburger :deep(svg) {
width: 20px;
height: 20px;
}
.pill {
display: none;
}
.ask-iris-btn {
width: 32px;
height: 32px;
padding: 0;
display: grid;
place-items: center;
border-radius: 9px;
flex: 0 0 auto;
}
.ask-label {
display: none;
}
.ask-iris-btn .btn-icon {
display: flex;
}
}
</style> </style>
+43 -2
View File
@@ -3,7 +3,9 @@
* NexusLayout — V2 Dashboard Shell * NexusLayout — V2 Dashboard Shell
* Flex row, 100vh, overflow hidden. * Flex row, 100vh, overflow hidden.
* Sidebar (248px) + Main (flex:1, flex-column) * Sidebar (248px) + Main (flex:1, flex-column)
* Mobile: Sidebar als Overlay mit Hamburger-Toggle
*/ */
import { ref } from 'vue'
import { RouterView } from 'vue-router' import { RouterView } from 'vue-router'
import { useAgentStore } from '../stores/agents' import { useAgentStore } from '../stores/agents'
import GalaxyBackground from '../components/background/GalaxyBackground.vue' import GalaxyBackground from '../components/background/GalaxyBackground.vue'
@@ -11,14 +13,35 @@ import Sidebar from '../components/layout/Sidebar.vue'
import Topbar from '../components/layout/Topbar.vue' import Topbar from '../components/layout/Topbar.vue'
const agentStore = useAgentStore() const agentStore = useAgentStore()
/* ── Mobile Sidebar State ───────────────────────── */
const mobileMenuOpen = ref(false)
function closeMobileMenu() {
mobileMenuOpen.value = false
}
</script> </script>
<template> <template>
<div class="nexus-layout"> <div class="nexus-layout">
<GalaxyBackground /> <GalaxyBackground />
<Sidebar /> <Sidebar
:mobile-open="mobileMenuOpen"
@close="closeMobileMenu"
/>
<!-- Mobile Backdrop -->
<div
v-if="mobileMenuOpen"
class="mobile-backdrop"
@click="closeMobileMenu"
></div>
<main class="nexus-main"> <main class="nexus-main">
<Topbar :connected="agentStore.isConnected" /> <Topbar
:connected="agentStore.isConnected"
@toggle-sidebar="mobileMenuOpen = !mobileMenuOpen"
/>
<div class="nexus-content"> <div class="nexus-content">
<RouterView /> <RouterView />
</div> </div>
@@ -49,4 +72,22 @@ const agentStore = useAgentStore()
overflow: hidden; overflow: hidden;
min-height: 0; min-height: 0;
} }
.mobile-backdrop {
display: none;
}
@media (max-width: 767px) {
.nexus-main {
width: 100%;
}
.mobile-backdrop {
display: block;
position: fixed;
inset: 0;
z-index: 99;
background: rgba(0, 0, 0, 0.5);
}
}
</style> </style>
@@ -177,4 +177,16 @@ onUnmounted(() => {
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
} }
@media (max-width: 767px) {
.board-body {
flex-direction: column;
padding: 8px;
gap: 10px;
}
.stage {
flex: 1;
}
}
</style> </style>