13d4c2f157
- IrisPanel: Task-Zahlen aus OperationsStore, Suggestions als Array, Chat via console.log - OperationsFeed: Filter-Pills filtern Feed-Items, TransitionGroup-Animationen - AgendaPanel: Checkboxen mit localStorage-Persistenz (nexus-agenda-done) - ActiveInitiatives: Cards hover-scale, Klick-Handler, Progress-Bars animiert - RecentlyFinished: Chips klickbar + a11y (role, tabindex, keyup) - DashboardView: fade-in Animation, Custom-Scrollbar - VERSION: 0.1.0 initial - Deploy-Workflow: Version-Bump-Semantik (major=x.0.0, minor=1.x.0, patch=1.0.x)
202 lines
5.2 KiB
Vue
202 lines
5.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { Clock } from '@lucide/vue'
|
|
|
|
type InitiativeStatus = 'healthy' | 'attention' | 'blocked' | 'paused' | 'completed'
|
|
|
|
interface Initiative {
|
|
title: string
|
|
progress: number
|
|
openTasks: number
|
|
blockers: number
|
|
status: InitiativeStatus
|
|
lastActivity: string
|
|
}
|
|
|
|
const initiatives = ref<Initiative[]>([
|
|
{ title: 'OpenClaw Companion', progress: 55, openTasks: 7, blockers: 2, status: 'healthy', lastActivity: 'vor 8 Minuten' },
|
|
{ title: '2D Idle Game', progress: 42, openTasks: 4, blockers: 0, status: 'healthy', lastActivity: 'vor 2 Stunden' },
|
|
{ title: 'Deutsch B2', progress: 73, openTasks: 3, blockers: 0, status: 'attention', lastActivity: 'vor 1 Stunde' },
|
|
{ title: 'Nexus Dashboard', progress: 60, openTasks: 3, blockers: 0, status: 'healthy', lastActivity: 'vor 5 Minuten' },
|
|
])
|
|
|
|
const statusMeta: Record<InitiativeStatus, { label: string; color: string; bg: string }> = {
|
|
healthy: { label: 'Healthy', color: '#22c55e', bg: 'rgba(34,197,94,0.1)' },
|
|
attention: { label: 'Needs Attention', color: '#eab308', bg: 'rgba(234,179,8,0.1)' },
|
|
blocked: { label: 'Blocked', color: '#ef4444', bg: 'rgba(239,68,68,0.1)' },
|
|
paused: { label: 'Paused', color: '#6b7280', bg: 'rgba(107,114,128,0.1)' },
|
|
completed: { label: 'Completed', color: '#3b82f6', bg: 'rgba(59,130,246,0.1)' },
|
|
}
|
|
|
|
function onInitiativeClick(title: string) {
|
|
console.log('[Dashboard] Open initiative:', title)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="initiatives-section">
|
|
<h2>Active Initiatives</h2>
|
|
<div class="initiatives-grid">
|
|
<div
|
|
v-for="(init, idx) in initiatives"
|
|
:key="idx"
|
|
:class="['initiative-card', 'status-' + init.status]"
|
|
@click="onInitiativeClick(init.title)"
|
|
role="button"
|
|
tabindex="0"
|
|
@keyup.enter="onInitiativeClick(init.title)"
|
|
>
|
|
<div class="init-head">
|
|
<h3>{{ init.title }}</h3>
|
|
<span
|
|
class="status-badge"
|
|
:style="{
|
|
color: statusMeta[init.status].color,
|
|
background: statusMeta[init.status].bg,
|
|
}"
|
|
>
|
|
{{ statusMeta[init.status].label }}
|
|
</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div
|
|
class="progress-fill"
|
|
:style="{ width: init.progress + '%' }"
|
|
></div>
|
|
</div>
|
|
<div class="progress-label">{{ init.progress }}%</div>
|
|
<div class="init-stats">
|
|
<span>{{ init.openTasks }} offene Aufgaben</span>
|
|
<span v-if="init.blockers">· {{ init.blockers }} Blocker</span>
|
|
</div>
|
|
<div class="init-meta">
|
|
<Clock :size="11" />
|
|
<span>Letzte Aktivität {{ init.lastActivity }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.initiatives-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
padding: 18px;
|
|
background: rgba(22, 27, 34, 0.8);
|
|
border: 1px solid rgba(139, 124, 246, 0.12);
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 24px rgba(0,0,0,0.3);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
transition: all 0.2s ease;
|
|
}
|
|
.initiatives-section:hover {
|
|
border-color: rgba(139, 124, 246, 0.18);
|
|
}
|
|
.initiatives-section h2 {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
color: #e8eaf0;
|
|
}
|
|
.initiatives-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
gap: 10px;
|
|
}
|
|
.initiative-card {
|
|
background: rgba(13, 17, 23, 0.5);
|
|
border: 1px solid rgba(139, 124, 246, 0.08);
|
|
border-radius: 14px;
|
|
padding: 14px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.initiative-card:hover {
|
|
transform: scale(1.02);
|
|
border-color: rgba(139, 124, 246, 0.2);
|
|
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
|
|
}
|
|
.initiative-card:focus-visible {
|
|
outline: 2px solid #a78bfa;
|
|
outline-offset: 2px;
|
|
}
|
|
.init-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
gap: 8px;
|
|
}
|
|
.init-head h3 {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
color: #e8eaf0;
|
|
}
|
|
.status-badge {
|
|
font-size: 8px;
|
|
font-weight: 700;
|
|
padding: 2px 7px;
|
|
border-radius: 12px;
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
.progress-bar {
|
|
height: 4px;
|
|
background: rgba(139, 124, 246, 0.1);
|
|
border-radius: 4px;
|
|
margin-bottom: 4px;
|
|
overflow: hidden;
|
|
}
|
|
.progress-fill {
|
|
height: 100%;
|
|
border-radius: 4px;
|
|
background: linear-gradient(90deg, #a78bfa, #8b5cf6);
|
|
transition: width 0.5s ease;
|
|
}
|
|
.initiative-card.status-attention .progress-fill {
|
|
background: linear-gradient(90deg, #eab308, #f59e0b);
|
|
}
|
|
.initiative-card.status-blocked .progress-fill {
|
|
background: linear-gradient(90deg, #ef4444, #dc2626);
|
|
}
|
|
.progress-label {
|
|
font-size: 10px;
|
|
color: #6b7385;
|
|
margin-bottom: 4px;
|
|
}
|
|
.init-stats {
|
|
font-size: 9px;
|
|
color: #6b7385;
|
|
margin-bottom: 4px;
|
|
display: flex;
|
|
gap: 4px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.init-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 9px;
|
|
color: #6b7385;
|
|
}
|
|
.init-meta svg {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.initiatives-grid {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
}
|
|
@media (max-width: 600px) {
|
|
.initiatives-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|