feat(next-feedback): add onActiveChange prop for controlled activation
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Successful in 1m1s
Monorepo Pipeline / 🚀 Release (push) Has been cancelled
Monorepo Pipeline / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been cancelled
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled

This commit is contained in:
2026-02-14 02:03:13 +01:00
parent f48d89c368
commit 9b1f3fb7e8
43 changed files with 121616 additions and 37174 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
],
"directus:extension": {
"type": "module",
"path": "dist/index.js",
"path": "index.js",
"source": "src/index.ts",
"host": "app",
"name": "unified dashboard"

View File

@@ -1,14 +0,0 @@
import { defineModule } from '@directus/extensions-sdk';
import ModuleComponent from './module.vue';
export default defineModule({
id: 'unified-dashboard',
name: 'Overview',
icon: 'dashboard',
routes: [
{
path: '',
component: ModuleComponent,
},
],
});

View File

@@ -1,141 +0,0 @@
<template>
<private-view title="Overview">
<div class="dashboard">
<header class="dashboard-header">
<h1 class="title">Infrastructure Stack</h1>
<p class="subtitle">Zentrale Schnittstelle für Firmen, Personen und Leads.</p>
</header>
<div class="stats-grid">
<div class="stat-card" @click="navigateTo('/company-manager')">
<div class="stat-icon"><v-icon name="business" large /></div>
<div class="stat-content">
<span class="stat-label">Firmen</span>
<span class="stat-value">{{ stats.companies }}</span>
</div>
<v-icon name="chevron_right" class="arrow" />
</div>
<div class="stat-card" @click="navigateTo('/people-manager')">
<div class="stat-icon"><v-icon name="person" large /></div>
<div class="stat-content">
<span class="stat-label">Personen</span>
<span class="stat-value">{{ stats.people }}</span>
</div>
<v-icon name="chevron_right" class="arrow" />
</div>
<div class="stat-card" @click="navigateTo('/acquisition-manager')">
<div class="stat-icon"><v-icon name="auto_awesome" large /></div>
<div class="stat-content">
<span class="stat-label">Leads</span>
<span class="stat-value">{{ stats.leads }}</span>
</div>
<v-icon name="chevron_right" class="arrow" />
</div>
</div>
<div class="recent-activity">
<h2 class="section-title">Schnellzugriff</h2>
<div class="action-grid">
<v-button secondary block @click="navigateTo('/people-manager?create=true')">
<v-icon name="person_add" left />
Neue Person anlegen
</v-button>
<v-button secondary block @click="navigateTo('/acquisition-manager?create=true')">
<v-icon name="add_link" left />
Neuen Lead registrieren
</v-button>
</div>
</div>
</div>
</private-view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useApi } from '@directus/extensions-sdk';
import { useRouter } from 'vue-router';
const api = useApi();
const router = useRouter();
const stats = ref({
companies: 0,
people: 0,
leads: 0
});
async function fetchStats() {
try {
const [comp, peop, lead] = await Promise.all([
api.get('/items/companies?aggregate[count]=*'),
api.get('/items/people?aggregate[count]=*'),
api.get('/items/leads?aggregate[count]=*')
]);
stats.value = {
companies: comp.data.data[0].count,
people: peop.data.data[0].count,
leads: lead.data.data[0].count
};
} catch (error) {
console.error('Failed to fetch stats:', error);
}
}
function navigateTo(path: string) {
router.push(path);
}
onMounted(fetchStats);
</script>
<style scoped>
.dashboard { padding: 40px; }
.dashboard-header { margin-bottom: 48px; }
.title { font-size: 32px; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 8px; }
.subtitle { color: var(--theme--foreground-subdued); font-size: 16px; }
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-bottom: 48px; }
.stat-card {
background: var(--theme--background-normal);
border: 1px solid var(--theme--border);
padding: 24px;
border-radius: 12px;
display: flex;
align-items: center;
gap: 20px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
}
.stat-card:hover {
border-color: var(--theme--primary);
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
.stat-icon {
width: 56px;
height: 56px;
background: var(--theme--background-subdued);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: var(--theme--primary);
}
.stat-content { display: flex; flex-direction: column; }
.stat-label { font-size: 12px; font-weight: 700; text-transform: uppercase; color: var(--theme--foreground-subdued); letter-spacing: 0.5px; }
.stat-value { font-size: 28px; font-weight: 800; color: var(--theme--foreground); }
.arrow { position: absolute; right: 24px; opacity: 0.2; }
.stat-card:hover .arrow { opacity: 1; color: var(--theme--primary); }
.recent-activity { max-width: 600px; }
.section-title { font-size: 18px; font-weight: 700; margin-bottom: 24px; }
.action-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
</style>