Admin function for tenant

This commit is contained in:
2026-06-21 18:00:20 +02:00
parent 12f05ceb09
commit fed54514c8
17 changed files with 827 additions and 0 deletions
@@ -0,0 +1,145 @@
<script setup>
import { ref } from 'vue';
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
import { toast } from "vue3-toastify";
const props = defineProps({
data: {
type: Object,
default: () => ({})
},
})
const { request } = useAjax()
const editing = ref(false)
const form = ref({
email: props.data.email ?? '',
email_finance: props.data.email_finance ?? '',
postcode: props.data.postcode ?? '',
city: props.data.city ?? '',
})
async function save() {
const response = await request('/api/v1/admin/tenant/contact', {
method: 'POST',
body: form.value,
})
if (response && response.status === 'success') {
toast.success(response.message)
editing.value = false
} else {
toast.error(response?.message ?? 'Fehler beim Speichern')
}
}
</script>
<template>
<div v-if="!editing">
<table class="data-table">
<tr><th>Email:</th><td>{{ form.email }}</td></tr>
<tr><th>Email Schatzmeister*in:</th><td>{{ form.email_finance }}</td></tr>
<tr><th>Postleitzahl:</th><td>{{ form.postcode }}</td></tr>
<tr><th>Ort:</th><td>{{ form.city }}</td></tr>
</table>
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
</div>
<div v-else>
<table class="data-table">
<tr>
<th>Email:</th>
<td><input type="email" v-model="form.email" class="form-input" /></td>
</tr>
<tr>
<th>Email Schatzmeister*in:</th>
<td><input type="email" v-model="form.email_finance" class="form-input" /></td>
</tr>
<tr>
<th>Postleitzahl:</th>
<td><input type="text" v-model="form.postcode" class="form-input" /></td>
</tr>
<tr>
<th>Ort:</th>
<td><input type="text" v-model="form.city" class="form-input" /></td>
</tr>
</table>
<div class="btn-group">
<button class="btn-save" @click="save">Speichern</button>
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
</div>
</div>
</template>
<style scoped>
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th {
text-align: left;
padding: 8px 12px;
width: 200px;
color: #374151;
border-bottom: 1px solid #e5e7eb;
}
.data-table td {
padding: 8px 12px;
border-bottom: 1px solid #e5e7eb;
}
.form-input {
width: 100%;
padding: 6px 10px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.95rem;
box-sizing: border-box;
}
.btn-edit, .btn-save, .btn-cancel {
margin-top: 15px;
padding: 8px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 0.9rem;
}
.btn-edit {
background-color: #1d4899;
color: #ffffff;
}
.btn-edit:hover {
background-color: #163a7a;
}
.btn-save {
background-color: #16a34a;
color: #ffffff;
}
.btn-save:hover {
background-color: #15803d;
}
.btn-cancel {
background-color: #e5e7eb;
color: #374151;
margin-left: 10px;
}
.btn-cancel:hover {
background-color: #d1d5db;
}
.btn-group {
display: flex;
gap: 10px;
}
</style>
@@ -0,0 +1,86 @@
<script setup>
import { ref } from 'vue';
import TextEditor from "../../../../Views/Components/TextEditor.vue";
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
import { toast } from "vue3-toastify";
import gdprTemplate from "../../../../../resources/templates/gdpr-template.html?raw";
const props = defineProps({
data: {
type: Object,
default: () => ({})
},
})
const { request } = useAjax()
const content = ref(props.data.gdpr_text ?? '')
function autoGenerate() {
if (content.value && content.value.trim() !== '') {
toast.error('Der Editor ist nicht leer. Bitte leere den Inhalt zuerst, um die Vorlage zu verwenden.')
return
}
const today = new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
content.value = gdprTemplate.replace('[Datum]', today)
}
async function save() {
const response = await request('/api/v1/admin/tenant/gdpr', {
method: 'POST',
body: { gdpr_text: content.value },
})
if (response && response.status === 'success') {
toast.success(response.message)
} else {
toast.error(response?.message ?? 'Fehler beim Speichern')
}
}
</script>
<template>
<div>
<TextEditor v-model="content" />
<div class="btn-group">
<button class="btn-save" @click="save">Speichern</button>
<button class="btn-generate" @click="autoGenerate">Auto-generieren</button>
</div>
</div>
</template>
<style scoped>
.btn-group {
display: flex;
gap: 10px;
margin-top: 15px;
}
.btn-save, .btn-generate {
padding: 8px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 0.9rem;
}
.btn-save {
background-color: #16a34a;
color: #ffffff;
}
.btn-save:hover {
background-color: #15803d;
}
.btn-generate {
background-color: #1d4899;
color: #ffffff;
}
.btn-generate:hover {
background-color: #163a7a;
}
</style>
@@ -0,0 +1,55 @@
<script setup>
import { ref } from 'vue';
import TextEditor from "../../../../Views/Components/TextEditor.vue";
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
import { toast } from "vue3-toastify";
const props = defineProps({
data: {
type: Object,
default: () => ({})
},
})
const { request } = useAjax()
const content = ref(props.data.impress_text ?? '')
async function save() {
const response = await request('/api/v1/admin/tenant/impress', {
method: 'POST',
body: { impress_text: content.value },
})
if (response && response.status === 'success') {
toast.success(response.message)
} else {
toast.error(response?.message ?? 'Fehler beim Speichern')
}
}
</script>
<template>
<div>
<TextEditor v-model="content" />
<button class="btn-save" @click="save">Speichern</button>
</div>
</template>
<style scoped>
.btn-save {
margin-top: 15px;
padding: 8px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 0.9rem;
background-color: #16a34a;
color: #ffffff;
}
.btn-save:hover {
background-color: #15803d;
}
</style>
@@ -0,0 +1,139 @@
<script setup>
import { ref } from 'vue';
import { useAjax } from "../../../../../resources/js/components/ajaxHandler.js";
import { toast } from "vue3-toastify";
const props = defineProps({
data: {
type: Object,
default: () => ({})
},
})
const { request } = useAjax()
const editing = ref(false)
const form = ref({
account_iban: props.data.account_iban ?? '',
account_bic: props.data.account_bic ?? '',
account_name: props.data.account_name ?? '',
})
async function save() {
const response = await request('/api/v1/admin/tenant/payment', {
method: 'POST',
body: form.value,
})
if (response && response.status === 'success') {
toast.success(response.message)
editing.value = false
} else {
toast.error(response?.message ?? 'Fehler beim Speichern')
}
}
</script>
<template>
<div v-if="!editing">
<table class="data-table">
<tr><th>IBAN:</th><td>{{ form.account_iban }}</td></tr>
<tr><th>BIC:</th><td>{{ form.account_bic }}</td></tr>
<tr><th>Name Kontoinhaber:</th><td>{{ form.account_name }}</td></tr>
</table>
<button class="btn-edit" @click="editing = true">Bearbeiten</button>
</div>
<div v-else>
<table class="data-table">
<tr>
<th>IBAN:</th>
<td><input type="text" v-model="form.account_iban" class="form-input" /></td>
</tr>
<tr>
<th>BIC:</th>
<td><input type="text" v-model="form.account_bic" class="form-input" /></td>
</tr>
<tr>
<th>Name Kontoinhaber:</th>
<td><input type="text" v-model="form.account_name" class="form-input" /></td>
</tr>
</table>
<div class="btn-group">
<button class="btn-save" @click="save">Speichern</button>
<button class="btn-cancel" @click="editing = false">Abbrechen</button>
</div>
</div>
</template>
<style scoped>
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th {
text-align: left;
padding: 8px 12px;
width: 200px;
color: #374151;
border-bottom: 1px solid #e5e7eb;
}
.data-table td {
padding: 8px 12px;
border-bottom: 1px solid #e5e7eb;
}
.form-input {
width: 100%;
padding: 6px 10px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.95rem;
box-sizing: border-box;
}
.btn-edit, .btn-save, .btn-cancel {
margin-top: 15px;
padding: 8px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 0.9rem;
}
.btn-edit {
background-color: #1d4899;
color: #ffffff;
}
.btn-edit:hover {
background-color: #163a7a;
}
.btn-save {
background-color: #16a34a;
color: #ffffff;
}
.btn-save:hover {
background-color: #15803d;
}
.btn-cancel {
background-color: #e5e7eb;
color: #374151;
margin-left: 10px;
}
.btn-cancel:hover {
background-color: #d1d5db;
}
.btn-group {
display: flex;
gap: 10px;
}
</style>
+109
View File
@@ -0,0 +1,109 @@
<script setup>
import AdminAppLayout from "../../../../resources/js/layouts/AdminAppLayout.vue";
import ShadowedBox from "../../../Views/Components/ShadowedBox.vue";
import TabbedPage from "../../../Views/Components/TabbedPage.vue";
import TenantContact from "./Partials/TenantContact.vue";
import TenantPayment from "./Partials/TenantPayment.vue";
import TenantImpress from "./Partials/TenantImpress.vue";
import TenantGdpr from "./Partials/TenantGdpr.vue";
const props = defineProps({
tenant: Object,
})
const tabs = [
{
title: 'Kontaktdaten',
component: TenantContact,
endpoint: '/api/v1/admin/tenant/contact',
},
{
title: 'Bezahldaten',
component: TenantPayment,
endpoint: '/api/v1/admin/tenant/payment',
},
{
title: 'Impressum',
component: TenantImpress,
endpoint: '/api/v1/admin/tenant/impress',
},
{
title: 'Datenschutzerklärung',
component: TenantGdpr,
endpoint: '/api/v1/admin/tenant/gdpr',
},
]
</script>
<template>
<AdminAppLayout :title="props.tenant.slug === 'lv' ? 'LV-Daten' : 'Stammesdaten'">
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
<table class="tenant-header">
<tr><th>Name</th><td>{{ props.tenant.name }}</td></tr>
<tr><th>Slug</th><td>{{ props.tenant.slug }}</td></tr>
<tr><th>mareike-URL:</th><td>{{ props.tenant.url }}</td></tr>
<tr><th>Status</th><td>
<span :class="props.tenant.is_active_local_group ? 'badge-active' : 'badge-inactive'">
{{ props.tenant.is_active_local_group ? 'Aktiv' : 'Inaktiv' }}
</span>
</td></tr>
</table>
<div style="margin-top: 30px;">
<tabbed-page :tabs="tabs" />
</div>
</shadowed-box>
</AdminAppLayout>
</template>
<style scoped>
.tenant-header {
width: 100%;
border-collapse: collapse;
border: 1px solid #d1d5db;
border-radius: 8px;
overflow: hidden;
}
.tenant-header th {
text-align: left;
padding: 10px 16px;
width: 200px;
color: #374151;
font-weight: bold;
background-color: #f9fafb;
border-bottom: 1px solid #d1d5db;
}
.tenant-header td {
padding: 10px 16px;
border-bottom: 1px solid #d1d5db;
}
.tenant-header tr:last-child th,
.tenant-header tr:last-child td {
border-bottom: none;
}
.badge-active {
display: inline-block;
padding: 3px 12px;
border-radius: 12px;
font-size: 0.85rem;
font-weight: bold;
color: #166534;
background-color: #dcfce7;
border: 1px solid #22c55e;
}
.badge-inactive {
display: inline-block;
padding: 3px 12px;
border-radius: 12px;
font-size: 0.85rem;
font-weight: bold;
color: #991b1b;
background-color: #fee2e2;
border: 1px solid #ef4444;
}
</style>