chore: comprehensive commit of all debugging, infrastructure, and extension fixes
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 6s
Monorepo Pipeline / 🧪 Test (push) Successful in 56s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m22s
Monorepo Pipeline / 🏗️ Build (push) Successful in 3m51s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 6s
Monorepo Pipeline / 🧪 Test (push) Successful in 56s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m22s
Monorepo Pipeline / 🏗️ Build (push) Successful in 3m51s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
Summary of changes: - Corrected Directus extensions to use 'vue-router' for 'useRouter' instead of '@directus/extensions-sdk' (Fixed runtime crash). - Standardized extension folder structure and moved built extensions to the root 'directus/extensions' directory. - Updated 'scripts/sync-extensions.sh' and 'scripts/validate-extensions.sh' for better extension management. - Added 'scripts/validate-sdk-imports.sh' as a safeguard against future invalid SDK imports. - Integrated import validation into the '.husky/pre-push' hook. - Standardized Docker restart policies and network configurations in 'cms-infra/docker-compose.yml'. - Updated tracked 'data.db' with the correct 'module_bar' settings to ensure extension visibility. - Cleaned up legacy files and consolidated extension package source code. This commit captures the full state of the repository after resolving the 'missing extensions' issue.
This commit is contained in:
14
packages/company-manager/src/index.ts
Normal file
14
packages/company-manager/src/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineModule } from '@directus/extensions-sdk';
|
||||
import ModuleComponent from './module.vue';
|
||||
|
||||
export default defineModule({
|
||||
id: 'company-manager',
|
||||
name: 'Company Manager',
|
||||
icon: 'business',
|
||||
routes: [
|
||||
{
|
||||
path: '',
|
||||
component: ModuleComponent,
|
||||
},
|
||||
],
|
||||
});
|
||||
231
packages/company-manager/src/module.vue
Normal file
231
packages/company-manager/src/module.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<private-view title="Company Manager">
|
||||
<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 Firma anlegen" />
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-list-item
|
||||
v-for="company in companies"
|
||||
:key="company.id"
|
||||
:active="selectedCompany?.id === company.id"
|
||||
class="company-item"
|
||||
clickable
|
||||
@click="selectCompany(company)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon name="business" />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-text-overflow :text="company.name" />
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<v-notice v-if="feedback" :type="feedback.type" @close="feedback = null" dismissible>
|
||||
{{ feedback.message }}
|
||||
</v-notice>
|
||||
|
||||
<div v-if="!selectedCompany" class="empty-state">
|
||||
<v-info title="Firma auswählen" icon="business" center>
|
||||
Wähle eine Firma in der Navigation aus oder
|
||||
<v-button x-small @click="openCreateDrawer">erstelle eine neue Firma</v-button>.
|
||||
</v-info>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<header class="header">
|
||||
<div class="header-left">
|
||||
<h1 class="title">{{ selectedCompany.name }}</h1>
|
||||
<p class="subtitle">{{ selectedCompany.industry || 'Keine Branche angegeben' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<v-button secondary rounded icon v-tooltip="'Firma bearbeiten'" @click="openEditDrawer">
|
||||
<v-icon name="edit" />
|
||||
</v-button>
|
||||
<v-button danger rounded icon v-tooltip="'Firma löschen'" @click="deleteCompany">
|
||||
<v-icon name="delete" />
|
||||
</v-button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<div class="details-grid">
|
||||
<div class="detail-item">
|
||||
<span class="label">Website</span>
|
||||
<p class="value">
|
||||
<a v-if="selectedCompany.website" :href="selectedCompany.website" target="_blank">{{ selectedCompany.website }}</a>
|
||||
<span v-else>---</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="label">Adresse</span>
|
||||
<p class="value">{{ selectedCompany.address || '---' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create/Edit Drawer -->
|
||||
<v-drawer
|
||||
v-model="drawerActive"
|
||||
:title="isEditing ? 'Firma bearbeiten' : 'Neue Firma anlegen'"
|
||||
icon="business"
|
||||
@cancel="drawerActive = false"
|
||||
>
|
||||
<template #default>
|
||||
<div class="drawer-content">
|
||||
<div class="form-section">
|
||||
<div class="field">
|
||||
<span class="label">Firmenname</span>
|
||||
<v-input v-model="form.name" placeholder="z.B. Schmidt GmbH" autofocus />
|
||||
</div>
|
||||
<div class="field">
|
||||
<span class="label">Branche</span>
|
||||
<v-input v-model="form.industry" placeholder="z.B. IT-Services" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<span class="label">Website</span>
|
||||
<v-input v-model="form.website" placeholder="https://..." />
|
||||
</div>
|
||||
<div class="field">
|
||||
<span class="label">Adresse</span>
|
||||
<v-textarea v-model="form.address" placeholder="Straße, PLZ Ort" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-actions">
|
||||
<v-button primary block :loading="saving" @click="saveCompany">
|
||||
Firma speichern
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-drawer>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useApi } from '@directus/extensions-sdk';
|
||||
|
||||
const api = useApi();
|
||||
const companies = ref([]);
|
||||
const selectedCompany = ref(null);
|
||||
const feedback = ref(null);
|
||||
const saving = ref(false);
|
||||
const drawerActive = ref(false);
|
||||
const isEditing = ref(false);
|
||||
|
||||
const form = ref({
|
||||
id: null,
|
||||
name: '',
|
||||
industry: '',
|
||||
website: '',
|
||||
address: ''
|
||||
});
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const resp = await api.get('/items/companies', {
|
||||
params: { sort: 'name' }
|
||||
});
|
||||
companies.value = resp.data.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch companies:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function selectCompany(company: any) {
|
||||
selectedCompany.value = company;
|
||||
}
|
||||
|
||||
function openCreateDrawer() {
|
||||
isEditing.value = false;
|
||||
form.value = {
|
||||
id: null,
|
||||
name: '',
|
||||
industry: '',
|
||||
website: '',
|
||||
address: ''
|
||||
};
|
||||
drawerActive.value = true;
|
||||
}
|
||||
|
||||
function openEditDrawer() {
|
||||
isEditing.value = true;
|
||||
form.value = { ...selectedCompany.value };
|
||||
drawerActive.value = true;
|
||||
}
|
||||
|
||||
async function saveCompany() {
|
||||
if (!form.value.name) {
|
||||
feedback.value = { type: 'danger', message: 'Firmenname ist erforderlich.' };
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
if (isEditing.value) {
|
||||
await api.patch(`/items/companies/${form.value.id}`, form.value);
|
||||
feedback.value = { type: 'success', message: 'Firma aktualisiert!' };
|
||||
} else {
|
||||
await api.post('/items/companies', form.value);
|
||||
feedback.value = { type: 'success', message: 'Firma angelegt!' };
|
||||
}
|
||||
drawerActive.value = false;
|
||||
await fetchData();
|
||||
if (isEditing.value) {
|
||||
selectedCompany.value = companies.value.find(c => c.id === form.value.id);
|
||||
}
|
||||
} catch (error) {
|
||||
feedback.value = { type: 'danger', message: error.message };
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCompany() {
|
||||
if (!confirm('Soll diese Firma wirklich gelöscht werden?')) return;
|
||||
|
||||
try {
|
||||
await api.delete(`/items/companies/${selectedCompany.value.id}`);
|
||||
feedback.value = { type: 'success', message: 'Firma gelöscht.' };
|
||||
selectedCompany.value = null;
|
||||
await fetchData();
|
||||
} catch (error) {
|
||||
feedback.value = { type: 'danger', message: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchData);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content-wrapper { padding: 32px; height: 100%; }
|
||||
.header { margin-bottom: 24px; display: flex; justify-content: space-between; align-items: flex-end; }
|
||||
.title { font-size: 24px; font-weight: 800; margin-bottom: 4px; }
|
||||
.subtitle { color: var(--theme--foreground-subdued); font-size: 14px; }
|
||||
.header-right { display: flex; gap: 12px; }
|
||||
.empty-state { height: 100%; display: flex; align-items: center; justify-content: center; }
|
||||
.details-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 32px; margin-top: 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>
|
||||
Reference in New Issue
Block a user