Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧪 Test (push) Successful in 2m2s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m14s
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 / 🏗️ Build (push) Has been cancelled
311 lines
7.4 KiB
Vue
311 lines
7.4 KiB
Vue
<template>
|
|
<MintelManagerLayout
|
|
title="People Manager"
|
|
:item-title="`${selectedPerson?.first_name} ${selectedPerson?.last_name}` || 'Person wählen'"
|
|
:is-empty="!selectedPerson"
|
|
empty-title="Person auswählen"
|
|
empty-icon="person"
|
|
:notice="feedback"
|
|
@close-notice="feedback = null"
|
|
>
|
|
<template #navigation>
|
|
<v-list nav>
|
|
<v-list-item @click="openCreateDrawer" clickable>
|
|
<v-list-item-icon>
|
|
<v-icon name="add" color="var(--theme--primary)" />
|
|
</v-list-item-icon>
|
|
<v-list-item-content>
|
|
<v-text-overflow text="Neue Person anlegen" />
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-divider />
|
|
|
|
<v-list-item
|
|
v-for="person in people"
|
|
:key="person.id"
|
|
:active="selectedPerson?.id === person.id"
|
|
class="nav-item"
|
|
clickable
|
|
@click="selectPerson(person)"
|
|
>
|
|
<v-list-item-icon>
|
|
<v-icon name="person" />
|
|
</v-list-item-icon>
|
|
<v-list-item-content>
|
|
<v-text-overflow :text="`${person.first_name} ${person.last_name}`" />
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
</v-list>
|
|
</template>
|
|
|
|
<template #subtitle>
|
|
<template v-if="selectedPerson">
|
|
{{ getCompanyName(selectedPerson) }}
|
|
</template>
|
|
</template>
|
|
|
|
<template #actions>
|
|
<v-button secondary rounded icon v-tooltip.bottom="'Person bearbeiten'" @click="openEditDrawer">
|
|
<v-icon name="edit" />
|
|
</v-button>
|
|
<v-button danger rounded icon v-tooltip.bottom="'Person löschen'" @click="deletePerson">
|
|
<v-icon name="delete" />
|
|
</v-button>
|
|
</template>
|
|
|
|
<template #empty-state>
|
|
Wähle eine Person in der Navigation aus oder
|
|
<v-button x-small @click="openCreateDrawer">erstelle eine neue Person</v-button>.
|
|
</template>
|
|
|
|
<div v-if="selectedPerson" class="details-grid">
|
|
<div class="detail-item">
|
|
<span class="label">Vorname</span>
|
|
<p class="value">{{ selectedPerson.first_name }}</p>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="label">Nachname</span>
|
|
<p class="value">{{ selectedPerson.last_name }}</p>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="label">E-Mail</span>
|
|
<p class="value">{{ selectedPerson.email || '---' }}</p>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="label">Organisation</span>
|
|
<p class="value">{{ getCompanyName(selectedPerson) }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create/Edit Drawer -->
|
|
<v-drawer
|
|
v-model="drawerActive"
|
|
:title="isEditing ? 'Person bearbeiten' : 'Neue Person anlegen'"
|
|
icon="person"
|
|
@cancel="drawerActive = false"
|
|
>
|
|
<template #default>
|
|
<div class="drawer-content">
|
|
<div class="form-section">
|
|
<div class="field">
|
|
<span class="label">Vorname</span>
|
|
<v-input v-model="form.first_name" placeholder="Vorname" autofocus />
|
|
</div>
|
|
<div class="field">
|
|
<span class="label">Nachname</span>
|
|
<v-input v-model="form.last_name" placeholder="Nachname" />
|
|
</div>
|
|
<div class="field">
|
|
<span class="label">E-Mail</span>
|
|
<v-input v-model="form.email" placeholder="E-Mail Adresse" type="email" />
|
|
</div>
|
|
<div class="field">
|
|
<span class="label">Zentrale Firma</span>
|
|
<MintelSelect
|
|
v-model="form.company"
|
|
:items="companyOptions"
|
|
placeholder="Bestehende Firma auswählen..."
|
|
allow-add
|
|
@add="openQuickAdd('company')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="drawer-actions">
|
|
<v-button primary block :loading="saving" @click="savePerson">
|
|
Person speichern
|
|
</v-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</v-drawer>
|
|
</MintelManagerLayout>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, computed, nextTick } from 'vue';
|
|
import { useApi } from '@directus/extensions-sdk';
|
|
import { MintelManagerLayout, MintelSelect } from '@mintel/directus-extension-toolkit';
|
|
|
|
const api = useApi();
|
|
const people = ref([]);
|
|
const companies = ref([]);
|
|
const selectedPerson = ref(null);
|
|
const feedback = ref(null);
|
|
const saving = ref(false);
|
|
const drawerActive = ref(false);
|
|
const isEditing = ref(false);
|
|
|
|
const form = ref({
|
|
id: null,
|
|
first_name: '',
|
|
last_name: '',
|
|
email: '',
|
|
company: null
|
|
});
|
|
|
|
const companyOptions = computed(() =>
|
|
companies.value.map(c => ({
|
|
text: c.name,
|
|
value: c.id
|
|
}))
|
|
);
|
|
|
|
function getCompanyName(person: any) {
|
|
if (!person) return '---';
|
|
if (person.company) {
|
|
return typeof person.company === 'object' ? person.company.name : (companies.value.find(c => c.id === person.company)?.name || 'Unbekannte Firma');
|
|
}
|
|
return '---';
|
|
}
|
|
|
|
async function fetchData() {
|
|
try {
|
|
const [peopleResp, companiesResp] = await Promise.all([
|
|
api.get('/items/people', {
|
|
params: {
|
|
sort: 'last_name',
|
|
fields: '*.*'
|
|
}
|
|
}),
|
|
api.get('/items/companies', {
|
|
params: { sort: 'name' }
|
|
})
|
|
]);
|
|
people.value = peopleResp.data.data;
|
|
companies.value = companiesResp.data.data;
|
|
} catch (error) {
|
|
console.error('Failed to fetch data:', error);
|
|
}
|
|
}
|
|
|
|
function selectPerson(person) {
|
|
selectedPerson.value = person;
|
|
}
|
|
|
|
function openCreateDrawer() {
|
|
isEditing.value = false;
|
|
form.value = {
|
|
id: null,
|
|
first_name: '',
|
|
last_name: '',
|
|
email: '',
|
|
company: null
|
|
};
|
|
drawerActive.value = true;
|
|
}
|
|
|
|
function openEditDrawer() {
|
|
isEditing.value = true;
|
|
const person = selectedPerson.value;
|
|
form.value = {
|
|
id: person.id,
|
|
first_name: person.first_name,
|
|
last_name: person.last_name,
|
|
email: person.email,
|
|
company: person.company?.id || person.company
|
|
};
|
|
drawerActive.value = true;
|
|
}
|
|
|
|
async function savePerson() {
|
|
if (!form.value.first_name || !form.value.last_name) {
|
|
feedback.value = { type: 'danger', message: 'Vor- und Nachname sind erforderlich.' };
|
|
return;
|
|
}
|
|
|
|
saving.value = true;
|
|
try {
|
|
let updatedItem;
|
|
if (isEditing.value) {
|
|
const res = await api.patch(`/items/people/${form.value.id}`, form.value);
|
|
updatedItem = res.data.data;
|
|
feedback.value = { type: 'success', message: 'Person aktualisiert!' };
|
|
} else {
|
|
const res = await api.post('/items/people', form.value);
|
|
updatedItem = res.data.data;
|
|
feedback.value = { type: 'success', message: 'Person angelegt!' };
|
|
}
|
|
drawerActive.value = false;
|
|
await fetchData();
|
|
if (updatedItem) {
|
|
selectedPerson.value = people.value.find(p => p.id === updatedItem.id) || updatedItem;
|
|
}
|
|
} catch (error) {
|
|
feedback.value = { type: 'danger', message: error.message };
|
|
} finally {
|
|
saving.value = false;
|
|
}
|
|
}
|
|
|
|
async function deletePerson() {
|
|
if (!confirm('Soll diese Person wirklich gelöscht werden?')) return;
|
|
|
|
try {
|
|
await api.delete(`/items/people/${selectedPerson.value.id}`);
|
|
feedback.value = { type: 'success', message: 'Person gelöscht.' };
|
|
selectedPerson.value = null;
|
|
await fetchData();
|
|
} catch (error) {
|
|
feedback.value = { type: 'danger', message: error.message };
|
|
}
|
|
}
|
|
|
|
function openQuickAdd(type: string) {
|
|
feedback.value = { type: 'info', message: `Firma im Company Manager anlegen.` };
|
|
}
|
|
|
|
onMounted(fetchData);
|
|
</script>
|
|
|
|
<style scoped>
|
|
.details-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 32px;
|
|
}
|
|
|
|
.detail-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.label {
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
color: var(--theme--foreground-subdued);
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.value {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.drawer-content {
|
|
padding: 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 32px;
|
|
}
|
|
|
|
.form-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.drawer-actions {
|
|
margin-top: 24px;
|
|
}
|
|
</style>
|