Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 26s
Monorepo Pipeline / 🚀 Release (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 / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🧹 Lint (push) Has been cancelled
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled
The Directus 11.x SDK does not export useRouter. Importing it caused a SyntaxError that crashed the entire extensions bundle, preventing ALL modules from appearing in the Data Studio sidebar. Changes: - Replace useRouter import from @directus/extensions-sdk → vue-router - Add scripts/validate-sdk-imports.sh to catch invalid SDK imports - Integrate SDK import validation into pre-push hook - Add EXTENSIONS_AUTO_RELOAD to docker-compose.yml - Remove debug NODE_ENV=development
142 lines
4.1 KiB
Vue
142 lines
4.1 KiB
Vue
<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>
|