New Release #4
@@ -56,132 +56,337 @@ const submit = async () => {
|
||||
|
||||
<template>
|
||||
<AppLayout title='Persönliche Daten'>
|
||||
<shadowed-box style="width: 95%; margin: 20px auto; padding: 20px; overflow-x: hidden;">
|
||||
<shadowed-box class="personal-data-box">
|
||||
|
||||
<div class="max-w-2xl mx-auto p-6">
|
||||
<form @submit.prevent="submit">
|
||||
<table class="form-table" style="width: 90%; margin: 10px;">
|
||||
<form @submit.prevent="submit">
|
||||
<!-- Sektion: Stammdaten -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Stammdaten</legend>
|
||||
|
||||
<!-- Nicht veränderbare Felder -->
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Vorname:</td>
|
||||
<td><span class="text-gray-700">{{ personalData.firstname }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Nachname:</td>
|
||||
<td><span class="text-gray-700">{{ personalData.lastname }}</span></td>
|
||||
</tr>
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--readonly">
|
||||
<label>Vorname</label>
|
||||
<div class="pd-readonly">{{ personalData.firstname }}</div>
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--readonly">
|
||||
<label>Nachname</label>
|
||||
<div class="pd-readonly">{{ personalData.lastname }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Veränderbare Felder -->
|
||||
<tr>
|
||||
<td>Pfadiname:</td>
|
||||
<td><input type="text" v-model="form.nickname" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E-Mail:</td>
|
||||
<td><input type="email" v-model="form.email" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Telefon:</td>
|
||||
<td><input type="text" v-model="form.phone" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Straße / Hausnummer:</td>
|
||||
<td><input type="text" v-model="form.address1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Adresszusatz:</td>
|
||||
<td><input type="text" v-model="form.address2" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PLZ:</td>
|
||||
<td><input type="text" v-model="form.postcode" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ort:</td>
|
||||
<td><input type="text" v-model="form.city" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 200px; padding: 5px;">Geburtsdatum:</td>
|
||||
<td><input type="date" v-model="form.birthday" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Medikamente:</td>
|
||||
<td><input type="text" v-model="form.medications" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allergien:</td>
|
||||
<td><input type="text" v-model="form.allergies" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unverträglichkeiten:</td>
|
||||
<td><input type="text" v-model="form.intolerances" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Letzte Tetanus-Impfung:</td>
|
||||
<td><input type="date" v-model="form.tetanusVaccination" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ernährungsgewohnheiten:</td>
|
||||
<td>
|
||||
<select v-model="form.eatingHabits">
|
||||
<option value="EATING_HABIT_VEGAN">Vegan</option>
|
||||
<option value="EATING_HABIT_VEGETARIAN">Vegetarisch</option>
|
||||
<option value="EATING_HABIT_OMNIVOR">Omnivor</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Badeerlaubnis:</td>
|
||||
<td>
|
||||
<select v-model="form.swimmingPermission">
|
||||
<option value="SWIMMING_PERMISSION_ALLOWED">Erteilt, kann schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_LIMITED">Erteilt, kann nicht schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_DENIED">Nicht erteilt</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Erste-Hilfe-Erlaubnis:</td>
|
||||
<td>
|
||||
<select v-model="form.firstAidPermission">
|
||||
<option value="FIRST_AID_PERMISSION_ALLOWED">Erweiterte Erste Hilfe erlaubt</option>
|
||||
<option value="FIRST_AID_PERMISSION_DENIED">Erweiterte Erste Hilfe verweigert</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kontoinhaber*in:</td>
|
||||
<td><input type="text" v-model="form.bankAccountOwner" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IBAN:</td>
|
||||
<td><IbanInput v-model="form.bankAccountIban" /></td>
|
||||
</tr>
|
||||
<div class="pd-field">
|
||||
<label for="nickname">Pfadiname</label>
|
||||
<input id="nickname" type="text" v-model="form.nickname" />
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" class="btn-row" style="padding-top: 20px;">
|
||||
<button type="submit" class="button" :disabled="saving">
|
||||
{{ saving ? 'Wird gespeichert…' : 'Speichern' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<div class="pd-field">
|
||||
<label for="birthday">Geburtsdatum</label>
|
||||
<input id="birthday" type="date" v-model="form.birthday" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Kontakt -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Kontakt</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field">
|
||||
<label for="email">E-Mail</label>
|
||||
<input id="email" type="email" v-model="form.email" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="phone">Telefon</label>
|
||||
<input id="phone" type="text" v-model="form.phone" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="address1">Straße / Hausnummer</label>
|
||||
<input id="address1" type="text" v-model="form.address1" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="address2">Adresszusatz</label>
|
||||
<input id="address2" type="text" v-model="form.address2" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--narrow">
|
||||
<label for="postcode">PLZ</label>
|
||||
<input id="postcode" type="text" v-model="form.postcode" maxlength="5" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--wide">
|
||||
<label for="city">Ort</label>
|
||||
<input id="city" type="text" v-model="form.city" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Gesundheit -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Gesundheit</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="medications">Medikamente</label>
|
||||
<input id="medications" type="text" v-model="form.medications" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="allergies">Allergien</label>
|
||||
<input id="allergies" type="text" v-model="form.allergies" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="intolerances">Unverträglichkeiten</label>
|
||||
<input id="intolerances" type="text" v-model="form.intolerances" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="tetanus">Letzte Tetanus-Impfung</label>
|
||||
<input id="tetanus" type="date" v-model="form.tetanusVaccination" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field">
|
||||
<label for="eating">Ernährungsgewohnheiten</label>
|
||||
<select id="eating" v-model="form.eatingHabits">
|
||||
<option value="EATING_HABIT_VEGAN">Vegan</option>
|
||||
<option value="EATING_HABIT_VEGETARIAN">Vegetarisch</option>
|
||||
<option value="EATING_HABIT_OMNIVOR">Omnivor</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Erlaubnisse -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Erlaubnisse</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="swimming">Badeerlaubnis</label>
|
||||
<select id="swimming" v-model="form.swimmingPermission">
|
||||
<option value="SWIMMING_PERMISSION_ALLOWED">Erteilt, kann schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_LIMITED">Erteilt, kann nicht schwimmen</option>
|
||||
<option value="SWIMMING_PERMISSION_DENIED">Nicht erteilt</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="firstaid">Erste-Hilfe-Erlaubnis</label>
|
||||
<select id="firstaid" v-model="form.firstAidPermission">
|
||||
<option value="FIRST_AID_PERMISSION_ALLOWED">Erweiterte Erste Hilfe erlaubt</option>
|
||||
<option value="FIRST_AID_PERMISSION_DENIED">Erweiterte Erste Hilfe verweigert</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Sektion: Bankverbindung -->
|
||||
<fieldset class="pd-fieldset">
|
||||
<legend>Bankverbindung</legend>
|
||||
|
||||
<div class="pd-grid">
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="owner">Kontoinhaber*in</label>
|
||||
<input id="owner" type="text" v-model="form.bankAccountOwner" />
|
||||
</div>
|
||||
|
||||
<div class="pd-field pd-field--full">
|
||||
<label for="iban">IBAN</label>
|
||||
<IbanInput id="iban" v-model="form.bankAccountIban" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pd-actions">
|
||||
<button type="submit" class="button pd-submit" :disabled="saving">
|
||||
{{ saving ? 'Wird gespeichert…' : 'Speichern' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</shadowed-box>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
textarea {
|
||||
.personal-data-box {
|
||||
width: 95%;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.pd-fieldset {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
padding: 16px 20px 20px;
|
||||
margin-bottom: 18px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 8px #efefef;
|
||||
}
|
||||
|
||||
.pd-fieldset legend {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
padding: 4px 12px;
|
||||
background-color: #f8fafc;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
color: #1d4899;
|
||||
}
|
||||
|
||||
/* Grid: 2-spaltig auf Desktop */
|
||||
.pd-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px 20px;
|
||||
}
|
||||
|
||||
.pd-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pd-field--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.pd-field--narrow {
|
||||
grid-column: span 1;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.pd-field--wide {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.pd-field label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.pd-field input,
|
||||
.pd-field select {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.pd-field input:focus,
|
||||
.pd-field select:focus {
|
||||
outline: none;
|
||||
border-color: #1d4899;
|
||||
}
|
||||
|
||||
.pd-readonly {
|
||||
padding: 8px 10px;
|
||||
border: 1px dashed #e5e7eb;
|
||||
border-radius: 6px;
|
||||
background-color: #f9fafb;
|
||||
color: #6b7280;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.pd-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.pd-submit {
|
||||
padding: 10px 28px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: 1px solid #809dd5;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.pd-submit:hover:not(:disabled) {
|
||||
background-color: #1d4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pd-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ─── Tablet (640–1023px) ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.personal-data-box {
|
||||
width: 100%;
|
||||
margin: 10px auto;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.pd-fieldset {
|
||||
padding: 12px 14px 16px;
|
||||
}
|
||||
|
||||
.pd-grid {
|
||||
gap: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone (< 640px) ─── */
|
||||
@media (max-width: 639px) {
|
||||
.personal-data-box {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.pd-fieldset {
|
||||
padding: 10px 12px 14px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.pd-fieldset legend {
|
||||
font-size: 0.9rem;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
/* Grid: 1-spaltig auf Smartphone */
|
||||
.pd-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Auch "narrow" und "wide" werden volle Breite */
|
||||
.pd-field--narrow,
|
||||
.pd-field--wide,
|
||||
.pd-field--full {
|
||||
grid-column: 1 / -1;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.pd-field label {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.pd-field input,
|
||||
.pd-field select {
|
||||
padding: 10px;
|
||||
font-size: 1rem; /* iOS Zoom-Prevention bei >=16px */
|
||||
}
|
||||
|
||||
.pd-actions {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.pd-submit {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,78 +8,67 @@ const props = defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 95%; margin: 20px auto;">
|
||||
<div class="available-events-wrapper">
|
||||
|
||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
||||
<div v-if="props.events.length === 0" class="available-events-empty">
|
||||
Aktuell sind keine Veranstaltungen verfügbar.
|
||||
</div>
|
||||
|
||||
<shadowed-box
|
||||
v-for="event in props.events"
|
||||
:key="event.id"
|
||||
style="padding: 24px; margin-bottom: 20px;"
|
||||
class="available-event-card"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px;">
|
||||
<div class="available-event-header">
|
||||
<div>
|
||||
<h2 style="margin: 0 0 4px 0; font-size: 1.25rem;">{{ event.name }}</h2>
|
||||
<span style="color: #6b7280; font-size: 0.9rem;">{{ event.postalCode }} {{ event.location }}</span>
|
||||
<h2 class="available-event-title">{{ event.name }}</h2>
|
||||
<span class="available-event-location">{{ event.postalCode }} {{ event.location }}</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="event.registrationAllowed"
|
||||
style="background: #d1fae5; color: #065f46; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
||||
class="available-event-badge available-event-badge--open"
|
||||
>
|
||||
Anmeldung offen
|
||||
</span>
|
||||
Anmeldung offen
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
style="background: #fee2e2; color: #991b1b; padding: 4px 12px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap;"
|
||||
class="available-event-badge available-event-badge--closed"
|
||||
>
|
||||
Anmeldung geschlossen
|
||||
</span>
|
||||
Anmeldung geschlossen
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr style="margin: 16px 0; border: none; border-top: 1px solid #e5e7eb;" />
|
||||
<hr class="available-event-divider" />
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<table class="available-event-table">
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Zeitraum</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.eventBegin }} – {{ event.eventEnd }} ({{ event.duration }} Tage)</td>
|
||||
<th>Zeitraum</th>
|
||||
<td>{{ event.eventBegin }} – {{ event.eventEnd }} ({{ event.duration }} Tage)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; width: 220px; color: #374151; font-weight: 600;">Veranstaltungsort</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.postalCode }} {{ event.location }}</td>
|
||||
<th>Veranstaltungsort</th>
|
||||
<td>{{ event.postalCode }} {{ event.location }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Frühbuchen bis</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.earlyBirdEnd.formatted }}</td>
|
||||
<th>Frühbuchen bis</th>
|
||||
<td>{{ event.earlyBirdEnd.formatted }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Anmeldeschluss</th>
|
||||
<td style="padding: 6px 0; color: #111827;">{{ event.registrationFinalEnd.formatted }}</td>
|
||||
<th>Anmeldeschluss</th>
|
||||
<td>{{ event.registrationFinalEnd.formatted }}</td>
|
||||
</tr>
|
||||
<tr v-if="event.email">
|
||||
<th style="text-align: left; padding: 6px 12px 6px 0; color: #374151; font-weight: 600;">Kontakt</th>
|
||||
<td style="padding: 6px 0;">
|
||||
<a :href="'mailto:' + event.email" style="color: #2563eb;">{{ event.email }}</a>
|
||||
<th>Kontakt</th>
|
||||
<td>
|
||||
<a :href="'mailto:' + event.email" class="available-event-mail">{{ event.email }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
|
||||
<div class="available-event-actions">
|
||||
<a
|
||||
:href="'/event/' + event.identifier + '/signup'"
|
||||
style="
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
opacity: 1;
|
||||
transition: background-color 0.2s;
|
||||
"
|
||||
class="available-event-button"
|
||||
:style="{ opacity: event.registrationAllowed ? '1' : '0.5', pointerEvents: event.registrationAllowed ? 'auto' : 'none' }"
|
||||
>
|
||||
Zur Anmeldung →
|
||||
@@ -87,14 +76,160 @@ const props = defineProps({
|
||||
</div>
|
||||
</shadowed-box>
|
||||
|
||||
</div><div style="width: 95%; margin: 20px auto;">
|
||||
|
||||
<div v-if="props.events.length === 0" style="text-align: center; color: #6b7280; padding: 40px 0;">
|
||||
Aktuell sind keine Veranstaltungen verfügbar.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.available-events-wrapper {
|
||||
width: 95%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.available-events-empty {
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.available-event-card {
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.available-event-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.available-event-title {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.available-event-location {
|
||||
color: #6b7280;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.available-event-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.available-event-badge--open { background: #d1fae5; color: #065f46; }
|
||||
.available-event-badge--closed { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
.available-event-divider {
|
||||
margin: 16px 0;
|
||||
border: none;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.available-event-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.available-event-table th {
|
||||
text-align: left;
|
||||
padding: 6px 12px 6px 0;
|
||||
width: 220px;
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.available-event-table td {
|
||||
padding: 6px 0;
|
||||
color: #111827;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.available-event-mail {
|
||||
color: #2563eb;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.available-event-actions {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.available-event-button {
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
/* ─── Tablet ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.available-events-wrapper {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.available-event-table th {
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
.available-event-card {
|
||||
padding: 16px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.available-event-title {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
/* Tabelle vertikal stapeln: Label über Wert */
|
||||
.available-event-table,
|
||||
.available-event-table tbody,
|
||||
.available-event-table tr,
|
||||
.available-event-table th,
|
||||
.available-event-table td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.available-event-table tr {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.available-event-table th {
|
||||
padding: 4px 0 2px 0;
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.available-event-table td {
|
||||
padding: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.available-event-actions {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.available-event-button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -79,12 +79,37 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="smartphone-actions">
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/first-aid-list'">
|
||||
<input type="button" value="Erste-Hilfe-Liste (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/csv/participant-list'">
|
||||
<input type="button" value="Teili-Liste (CSV)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/kitchen-list'">
|
||||
<input type="button" value="Küchenübersicht (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/amount-list'">
|
||||
<input type="button" value="Beitragsliste (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/drinking-list'">
|
||||
<input type="button" value="Getränkeliste (PDF)" />
|
||||
</a><br/>
|
||||
|
||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/photo-permission-list'">
|
||||
<input type="button" value="Foto-Erlaubnis (PDF)" />
|
||||
</a><br/>
|
||||
</div>
|
||||
|
||||
|
||||
<ParticipationFees v-if="displayData === 'participationFees'" :event="dynamicProps.event" @close="showMain" />
|
||||
<CommonSettings v-else-if="displayData === 'commonSettings'" :event="dynamicProps.event" @close="showMain" />
|
||||
<EventManagement v-else-if="displayData === 'eventManagement'" :event="dynamicProps.event" @close="showMain" />
|
||||
|
||||
|
||||
|
||||
<div class="event-flexbox" v-else>
|
||||
<div class="event-flexbox-row top">
|
||||
<div class="left"><ParticipationSummary v-if="dynamicProps.event" :event="dynamicProps.event" /></div>
|
||||
@@ -118,6 +143,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
@@ -241,4 +268,22 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.smartphone-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
.smartphone-actions {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.event-flexbox {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -301,7 +301,7 @@ function mailToGroup(groupKey) {
|
||||
<h2>{{ event?.name ?? "Veranstaltung" }}</h2>
|
||||
<div :key="groupKey">
|
||||
<div>
|
||||
<table style="width: 95%; margin: 20px auto; border-collapse: collapse;" v-for="[groupKey, participants] in getGroupEntries">
|
||||
<table class="participants-table" v-for="[groupKey, participants] in getGroupEntries">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" style="background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold">
|
||||
@@ -309,10 +309,10 @@ function mailToGroup(groupKey) {
|
||||
</th>
|
||||
</tr>
|
||||
<tr style="background: linear-gradient(to bottom, #fff, #f6f7f7);">
|
||||
<th>Name</th>
|
||||
<th>Beitrag</th>
|
||||
<th>E-Mail-Adresse</th>
|
||||
<th>Telefon</th>
|
||||
<th class="pl-name">Name</th>
|
||||
<th class="pl-amount">Beitrag</th>
|
||||
<th class="pl-email">E-Mail-Adresse</th>
|
||||
<th class="pl-phone">Telefon</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -323,7 +323,7 @@ function mailToGroup(groupKey) {
|
||||
>
|
||||
<tr :class="getRowClass(participant)" :id="'participant-' + participant.identifier + '-common'">
|
||||
<td :id="'participant-' + participant.identifier +'-name'"
|
||||
style="width: 300px;"
|
||||
class="pl-name"
|
||||
:class="participant.efz_status === 'checked_invalid' ? 'efz-invalid' :
|
||||
participant.efz_status === 'not_checked' ? 'efz-not-checked' : ''">
|
||||
<div :id="'participant-' + participant.identifier +'-fullname'" v-html="participant.fullname" /><br />
|
||||
@@ -335,7 +335,7 @@ function mailToGroup(groupKey) {
|
||||
<span :id="'participant-' + participant.identifier +'-coc-action'" v-if="participant.efz_status !== 'checked_valid' && participant.efz_status !== 'not_required'" class="link" style="color: #3cb62e; font-size: 11pt;" @click="markCocExisting(participant)">Vorhanden?</span>
|
||||
</td>
|
||||
|
||||
<td :id="'participant-' + participant.identifier +'-payment'" :class="participant.amount_left_value != 0 && !participant.unregistered ? 'not-paid' : ''" style="width: 275px; '">
|
||||
<td class="pl-amount" :id="'participant-' + participant.identifier +'-payment'" :class="participant.amount_left_value != 0 && !participant.unregistered ? 'not-paid' : ''">
|
||||
Gezahlt: <label :id="'participant-' + participant.identifier + '-paid'">{{ participant?.amountPaid.readable }}</label> /<br />
|
||||
Gesamt: <label :id="'participant-' + participant.identifier + '-expected'">{{ participant?.amountExpected.readable }}</label>
|
||||
<br /><br />
|
||||
@@ -345,12 +345,12 @@ function mailToGroup(groupKey) {
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<td class="pl-email">
|
||||
<label :id="'participant-' + participant.identifier +'-email_1'" class="block-label">{{ participant?.email_1 ?? "-" }}</label>
|
||||
<label :id="'participant-' + participant.identifier +'-email_2'" class="block-label">{{ participant.email_2 }}</label>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<td class="pl-phone">
|
||||
<label :id="'participant-' + participant.identifier +'-phone_1'" class="block-label">{{ participant?.phone_1 }}</label>
|
||||
<label :id="'participant-' + participant.identifier +'-phone_2'" class="block-label">{{ participant?.phone_2 }}</label>
|
||||
</td>
|
||||
@@ -359,7 +359,7 @@ function mailToGroup(groupKey) {
|
||||
</tr>
|
||||
|
||||
<tr class="participant-meta-row" :id="'participant-' + participant.identifier + '-meta'">
|
||||
<td colspan="5" style="height: 15px !important; font-size: 9pt; background-color: #ffffff; border-top-style: none;">
|
||||
<td colspan="5" class="pl-meta">
|
||||
<label :id="'participant-' + participant.identifier +'-localgroup'">
|
||||
{{ participant?.localgroup ?? "-" }}
|
||||
</label> |
|
||||
@@ -385,8 +385,8 @@ function mailToGroup(groupKey) {
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<tr>
|
||||
<td colspan="3" style="font-weight: normal;">
|
||||
<tr class="pl-summary-row">
|
||||
<td colspan="3" class="pl-age-summary">
|
||||
0 - 5 Jahre: <strong>{{ getAgeCounts(participants)['0-5'] ?? 0 }}</strong> |
|
||||
6-11 Jahre: <strong>{{ getAgeCounts(participants)['6-11'] ?? 0 }}</strong> |
|
||||
12-15 Jahre: <strong>{{ getAgeCounts(participants)['12-15'] ?? 0 }}</strong> |
|
||||
@@ -394,7 +394,7 @@ function mailToGroup(groupKey) {
|
||||
18 - 27 Jahre: <strong>{{ getAgeCounts(participants)['18-27'] ?? 0 }}</strong> |
|
||||
27 Jahre und älter: <strong>{{ getAgeCounts(participants)['27+'] ?? 0 }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<td class="pl-summary-action">
|
||||
<input type="button" class="button" @click="mailToGroup(groupKey)" value="E-Mail an Gruppe senden" />
|
||||
</td>
|
||||
</tr>
|
||||
@@ -458,81 +458,193 @@ function mailToGroup(groupKey) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
margin-bottom: 60px !important;
|
||||
.participants-table {
|
||||
width: 95%;
|
||||
margin: 20px auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 60px !important;
|
||||
}
|
||||
|
||||
tr {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr td {
|
||||
height: 80px;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
font-size: 11pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
tr th {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr th:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Spaltenbreiten auf Desktop */
|
||||
.pl-name { width: 300px; }
|
||||
.pl-amount { width: 275px; }
|
||||
.pl-email { width: auto; }
|
||||
.pl-phone { width: auto; }
|
||||
|
||||
.pl-meta {
|
||||
height: 15px !important;
|
||||
font-size: 9pt;
|
||||
background-color: #ffffff;
|
||||
border-top-style: none;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
border-style: solid;
|
||||
border-width: 0 1px;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
border-style: solid;
|
||||
border-width: 5px 1px 0 1px;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
border-width: 1px 1px 0 1px;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-width: 0 1px 1px 1px;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: block;
|
||||
font-size: 10pt;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.not-paid {
|
||||
color: #ff0000; background-color: #ffe6e6;
|
||||
}
|
||||
|
||||
.efz-invalid {
|
||||
color: #ff0000; background-color: #ffe6e6;
|
||||
}
|
||||
|
||||
.efz-not-checked {
|
||||
color: #8D914BFF; background-color: #F4E99EFF;
|
||||
}
|
||||
|
||||
.block-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ─── Tablet ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.participants-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tr {
|
||||
.pl-name { width: 40%; }
|
||||
.pl-amount { width: 25%; }
|
||||
.pl-email,
|
||||
.pl-phone { width: 17.5%; }
|
||||
|
||||
tr td {
|
||||
padding: 8px;
|
||||
font-size: 10pt;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
.participants-table {
|
||||
width: 100%;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Auf Smartphone werden Beitrag und E-Mail ausgeblendet,
|
||||
Name + Telefon bleiben sichtbar */
|
||||
.pl-amount,
|
||||
.pl-email {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.pl-name {
|
||||
width: 65% !important;
|
||||
display: table-cell;
|
||||
padding: 10px 8px !important;
|
||||
height: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr td {
|
||||
height: 80px;
|
||||
.pl-phone {
|
||||
width: 35% !important;
|
||||
display: table-cell;
|
||||
padding: 10px 6px !important;
|
||||
height: auto;
|
||||
vertical-align: top;
|
||||
font-size: 0.8rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.pl-phone .block-label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* Header-Zeile entsprechend anpassen */
|
||||
thead tr:nth-child(2) th.pl-name { width: 65% !important; }
|
||||
thead tr:nth-child(2) th.pl-phone { width: 35% !important; }
|
||||
|
||||
/* Meta-Zeile (Adresse, Aktionen) bleibt sichtbar, aber kompakter */
|
||||
.pl-meta {
|
||||
padding: 8px !important;
|
||||
font-size: 0.78rem !important;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Summary-Zeile (Altersverteilung) auf Mobile: stapeln */
|
||||
.pl-summary-row {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pl-summary-row td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.pl-age-summary {
|
||||
font-size: 0.78rem !important;
|
||||
line-height: 1.8;
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.pl-summary-action input.button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
font-size: 11pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
tr th {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr th:after {
|
||||
content: "";
|
||||
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
border-style: solid;
|
||||
border-width: 0 1px;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: #ffffff;
|
||||
border-style: solid;
|
||||
border-width: 5px 1px 0 1px;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
tr:first-child {
|
||||
border-width: 1px 1px 0 1px;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
border-width: 0 1px 1px 1px;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
background: linear-gradient(to bottom, #fff, #f6f7f7); font-weight: bold;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: block;
|
||||
font-size: 10pt;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.not-paid {
|
||||
color: #ff0000; background-color: #ffe6e6;
|
||||
}
|
||||
|
||||
.efz-invalid {
|
||||
color: #ff0000; background-color: #ffe6e6;
|
||||
}
|
||||
|
||||
.efz-not-checked {
|
||||
color: #8D914BFF; background-color: #F4E99EFF;
|
||||
}
|
||||
|
||||
.block-label {
|
||||
display: block;
|
||||
/* tr-Border auf Mobile angleichen */
|
||||
tr td {
|
||||
height: auto;
|
||||
padding: 10px 8px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
event: Object
|
||||
})
|
||||
const props = defineProps({
|
||||
event: Object
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -68,8 +68,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
<tr>
|
||||
<th colspan="2" style="border-width: 1px; border-bottom-style: solid">Gesamt</th>
|
||||
<td style="font-weight: bold; border-width: 1px; border-bottom-style: solid">
|
||||
@@ -117,7 +115,6 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -131,22 +128,22 @@
|
||||
|
||||
.participant-flexbox-row {
|
||||
display: flex;
|
||||
gap: 10px; /* Abstand zwischen den Spalten */
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.participant-flexbox-row.top .left {
|
||||
flex: 0 0 50%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.participant-flexbox.top .right {
|
||||
flex: 0 0 50%;
|
||||
.participant-flexbox-row.top .left,
|
||||
.participant-flexbox-row.top .right {
|
||||
flex: 1 1 280px;
|
||||
padding: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.participant-income-table,
|
||||
.event-payment-table {
|
||||
width: 475px;
|
||||
width: 100%;
|
||||
max-width: 475px;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
.participant-income-table th {
|
||||
@@ -162,4 +159,5 @@
|
||||
.event-payment-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -50,43 +50,37 @@ const steps = [
|
||||
|
||||
<template v-else>
|
||||
<!-- Fortschrittsleiste (ab Step 2) -->
|
||||
<div v-if="currentStep > 1" style="margin-bottom: 28px;">
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; align-items: center;">
|
||||
<div v-if="currentStep > 1" class="signup-progress">
|
||||
<div class="signup-progress-pills">
|
||||
<template v-for="(s, index) in steps.filter(s => s.step > 1)" :key="s.step">
|
||||
<!-- Trennlinie zwischen Pills -->
|
||||
<div v-if="index > 0" style="flex-shrink: 0; width: 16px; height: 2px; background: #e5e7eb; border-radius: 1px;"></div>
|
||||
<div v-if="index > 0" class="signup-progress-separator"></div>
|
||||
<div
|
||||
:style="{
|
||||
padding: '5px 14px',
|
||||
borderRadius: '999px',
|
||||
fontSize: '0.78rem',
|
||||
fontWeight: '600',
|
||||
whiteSpace: 'nowrap',
|
||||
border: '2px solid',
|
||||
borderColor: currentStep === s.step ? '#2563eb' : currentStep > s.step ? '#bbf7d0' : '#e5e7eb',
|
||||
background: currentStep === s.step ? '#2563eb' : currentStep > s.step ? '#f0fdf4' : '#f9fafb',
|
||||
color: currentStep === s.step ? 'white' : currentStep > s.step ? '#15803d' : '#9ca3af',
|
||||
cursor: currentStep > s.step ? 'pointer' : 'default',
|
||||
class="signup-pill"
|
||||
:class="{
|
||||
'signup-pill--active': currentStep === s.step,
|
||||
'signup-pill--done': currentStep > s.step,
|
||||
'signup-pill--upcoming': currentStep < s.step,
|
||||
}"
|
||||
@click="currentStep > s.step ? goToStep(s.step) : null"
|
||||
>
|
||||
<span v-if="currentStep > s.step" style="margin-right: 4px;">✓</span>
|
||||
<span v-if="currentStep > s.step" class="signup-pill__check">✓</span>
|
||||
{{ s.label }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Fortschrittsbalken -->
|
||||
<div style="margin-top: 10px; height: 3px; background: #e5e7eb; border-radius: 2px; overflow: hidden;">
|
||||
<div class="signup-progress-bar">
|
||||
<div
|
||||
:style="{
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg, #2563eb, #3b82f6)',
|
||||
borderRadius: '2px',
|
||||
width: ((currentStep - 2) / (steps.length - 2) * 100) + '%',
|
||||
transition: 'width 0.3s ease',
|
||||
}"
|
||||
class="signup-progress-bar__fill"
|
||||
:style="{ width: ((currentStep - 2) / (steps.length - 2) * 100) + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Step-Anzeige -->
|
||||
<div class="signup-progress-mobile">
|
||||
Schritt {{ currentStep - 1 }} von {{ steps.length - 1 }}:
|
||||
<strong>{{ steps.find(s => s.step === currentStep)?.label }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Steps -->
|
||||
@@ -115,11 +109,87 @@ const steps = [
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* ─── Progress (Step-Pills) ─── */
|
||||
.signup-progress {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.signup-progress-pills {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.signup-progress-separator {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.signup-pill {
|
||||
padding: 5px 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
border: 2px solid;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.signup-pill__check { margin-right: 4px; }
|
||||
|
||||
.signup-pill--active {
|
||||
border-color: #2563eb;
|
||||
background: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.signup-pill--done {
|
||||
border-color: #bbf7d0;
|
||||
background: #f0fdf4;
|
||||
color: #15803d;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.signup-pill--upcoming {
|
||||
border-color: #e5e7eb;
|
||||
background: #f9fafb;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.signup-progress-bar {
|
||||
margin-top: 10px;
|
||||
height: 3px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.signup-progress-bar__fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #2563eb, #3b82f6);
|
||||
border-radius: 2px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.signup-progress-mobile {
|
||||
display: none;
|
||||
margin-top: 8px;
|
||||
font-size: 0.9rem;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* ─── Form-Table ─── */
|
||||
.form-table { width: 100%; border-collapse: collapse; }
|
||||
.form-table td { padding: 8px 12px 8px 0; vertical-align: top; }
|
||||
.form-table td:first-child { width: 220px; color: #374151; font-weight: 500; }
|
||||
.form-table input[type="text"],
|
||||
.form-table input[type="date"],
|
||||
.form-table input[type="email"],
|
||||
.form-table input[type="number"],
|
||||
.form-table select,
|
||||
.form-table textarea {
|
||||
width: 100%;
|
||||
@@ -129,7 +199,8 @@ const steps = [
|
||||
font-size: 0.95rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.btn-row { display: flex; gap: 10px; padding-top: 16px; }
|
||||
|
||||
.btn-row { display: flex; gap: 10px; padding-top: 16px; flex-wrap: wrap; }
|
||||
.btn-primary {
|
||||
padding: 8px 20px;
|
||||
background: #2563eb;
|
||||
@@ -149,4 +220,58 @@ const steps = [
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ─── Tablet ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.form-table td:first-child {
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
/* Pills auf Mobile: kompakter, Trennstriche ausblenden */
|
||||
.signup-progress-pills {
|
||||
display: none;
|
||||
}
|
||||
.signup-progress-mobile {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Form-Table: Label oberhalb des Feldes */
|
||||
.form-table,
|
||||
.form-table tbody,
|
||||
.form-table tr {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-table td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.form-table td:first-child {
|
||||
width: 100% !important;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.form-table td[colspan] {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
flex-direction: column-reverse;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@ const emit = defineEmits(['next'])
|
||||
<h3 style="margin: 0 0 6px 0; color: #111827;">Wer nimmt teil?</h3>
|
||||
<p style="margin: 0 0 24px 0; color: #6b7280; font-size: 0.95rem;">Bitte wähle deine Altersgruppe aus.</p>
|
||||
|
||||
<div style="display: flex; gap: 20px; flex-wrap: wrap;">
|
||||
<div class="age-card-row">
|
||||
|
||||
<!-- Kind / Jugendliche:r -->
|
||||
<div class="age-card" @click="emit('next', 2)">
|
||||
@@ -40,9 +40,15 @@ const emit = defineEmits(['next'])
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.age-card-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.age-card {
|
||||
flex: 1;
|
||||
min-width: 220px;
|
||||
flex: 1 1 280px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -62,13 +68,14 @@ const emit = defineEmits(['next'])
|
||||
|
||||
.age-card__badge {
|
||||
position: relative;
|
||||
width: 350px;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
aspect-ratio: 350 / 200;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.age-card__img {
|
||||
width: 350px;
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.age-card__badge-fallback {
|
||||
@@ -100,4 +107,43 @@ const emit = defineEmits(['next'])
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
.age-card-row {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.age-card {
|
||||
flex: 1 1 100%;
|
||||
padding: 18px 14px;
|
||||
flex-direction: row;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.age-card__badge {
|
||||
flex: 0 0 90px;
|
||||
width: 90px;
|
||||
max-width: 90px;
|
||||
aspect-ratio: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.age-card__body {
|
||||
flex: 1;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.age-card__title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.age-card__desc {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -116,8 +116,10 @@ const next = () => {
|
||||
<tr>
|
||||
<td>PLZ, Ort:</td>
|
||||
<td>
|
||||
<input maxlength="5" type="text" v-model="props.formData.plz" style="width: 100px; margin-right: 8px;" />
|
||||
<input type="text" v-model="props.formData.ort" style="width: calc(100% - 110px);" />
|
||||
<div class="plz-ort-row">
|
||||
<input maxlength="5" type="text" v-model="props.formData.plz" class="plz-input" placeholder="PLZ" />
|
||||
<input type="text" v-model="props.formData.ort" class="ort-input" placeholder="Ort" />
|
||||
</div>
|
||||
<ErrorText :message="errors.plz" />
|
||||
<ErrorText :message="errors.ort" />
|
||||
</td>
|
||||
@@ -132,3 +134,28 @@ const next = () => {
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plz-ort-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plz-input {
|
||||
flex: 0 0 100px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.ort-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.plz-input {
|
||||
flex: 0 0 80px;
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,78 +26,78 @@ function close() {
|
||||
<AppLayout title="Für Veranstaltung anmelden">
|
||||
<FullScreenModal :show="showSignup" @close="close">
|
||||
|
||||
<shadowed-box style="width: 95%; margin: 30px auto; padding: 0; overflow: hidden; border-radius: 12px;">
|
||||
<shadowed-box class="signup-box">
|
||||
|
||||
<!-- Header -->
|
||||
<div v-if="props.event.registrationAllowed" style="background: linear-gradient(135deg, #1e40af, #3b82f6); padding: 28px 32px; color: white;">
|
||||
<div style="display: flex; align-items: center; gap: 14px; flex-wrap: wrap;">
|
||||
<h2 style="margin: 0; font-size: 1.5rem; font-weight: 700;">{{ props.event.name }}</h2>
|
||||
<span style="background: rgba(255,255,255,0.2); color: white; padding: 4px 14px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap; border: 1px solid rgba(255,255,255,0.4);">
|
||||
<div v-if="props.event.registrationAllowed" class="signup-header signup-header--open">
|
||||
<div class="signup-header-row">
|
||||
<h2 class="signup-event-title">{{ props.event.name }}</h2>
|
||||
<span class="signup-badge signup-badge--open">
|
||||
✓ Anmeldung offen
|
||||
</span>
|
||||
</div>
|
||||
<p style="margin: 8px 0 0 0; opacity: 0.85; font-size: 0.95rem;">
|
||||
<p class="signup-event-location">
|
||||
📍 {{ props.event.postalCode }} {{ props.event.location }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else style="background: linear-gradient(135deg, #991b1b, #dc2626); padding: 28px 32px; color: white;">
|
||||
<div style="display: flex; align-items: center; gap: 14px; flex-wrap: wrap;">
|
||||
<h2 style="margin: 0; font-size: 1.5rem; font-weight: 700;">{{ props.event.name }}</h2>
|
||||
<span style="background: rgba(255,0,0,0.2); color: #fecaca; padding: 4px 14px; border-radius: 999px; font-size: 0.8rem; font-weight: 600; white-space: nowrap; border: 1px solid rgba(255,100,100,0.4);">
|
||||
<div v-else class="signup-header signup-header--closed">
|
||||
<div class="signup-header-row">
|
||||
<h2 class="signup-event-title">{{ props.event.name }}</h2>
|
||||
<span class="signup-badge signup-badge--closed">
|
||||
✗ Anmeldung geschlossen
|
||||
</span>
|
||||
</div>
|
||||
<p style="margin: 8px 0 0 0; opacity: 0.85; font-size: 0.95rem;">
|
||||
<p class="signup-event-location">
|
||||
📍 {{ props.event.postalCode }} {{ props.event.location }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div style="padding: 28px 32px;">
|
||||
<div class="signup-body">
|
||||
|
||||
<!-- Info-Grid -->
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;">
|
||||
<div class="signup-info-grid">
|
||||
|
||||
<!-- Zeile 1 links: Zeitraum -->
|
||||
<div style="background: #f8fafc; border-radius: 8px; padding: 16px;">
|
||||
<div style="font-size: 0.75rem; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px;">Veranstaltungszeitraum</div>
|
||||
<div style="font-size: 1rem; color: #111827; font-weight: 500;">{{ props.event.eventBegin }} – {{ props.event.eventEnd }}</div>
|
||||
<div style="font-size: 0.85rem; color: #6b7280;">{{ props.event.duration }} Tage</div>
|
||||
<!-- Zeitraum -->
|
||||
<div class="info-card">
|
||||
<div class="info-card__label">Veranstaltungszeitraum</div>
|
||||
<div class="info-card__value">{{ props.event.eventBegin }} – {{ props.event.eventEnd }}</div>
|
||||
<div class="info-card__sub">{{ props.event.duration }} Tage</div>
|
||||
</div>
|
||||
|
||||
<!-- Zeile 1 rechts: Erstattungsrichtlinien -->
|
||||
<div style="background: #f0f9ff; border-radius: 8px; padding: 16px; border-left: 3px solid #0ea5e9;">
|
||||
<div style="font-size: 0.75rem; color: #0369a1; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px;">Erstattungsrichtlinien</div>
|
||||
<div style="color: #0c4a6e; font-size: 0.875rem; line-height: 1.8;">
|
||||
<!-- Erstattungsrichtlinien -->
|
||||
<div class="info-card info-card--accent">
|
||||
<div class="info-card__label info-card__label--accent">Erstattungsrichtlinien</div>
|
||||
<div class="info-card__list">
|
||||
<div>100 % bis {{ props.event.earlyBirdEnd.formatted }}</div>
|
||||
<div>{{ props.event.refundAfterEarlyBirdEnd }} % bis {{ props.event.registrationFinalEnd.formatted }}</div>
|
||||
<div>Danach nur bei Nachrückplätzen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zeile 2 links: Anmeldeschluss -->
|
||||
<div style="background: #f8fafc; border-radius: 8px; padding: 16px;">
|
||||
<div style="font-size: 0.75rem; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px;">Anmeldeschluss</div>
|
||||
<div style="font-size: 1rem; color: #111827; font-weight: 500; margin-bottom: 20px;">{{ props.event.registrationFinalEnd.formatted }}</div>
|
||||
<!-- Anmeldeschluss -->
|
||||
<div class="info-card">
|
||||
<div class="info-card__label">Anmeldeschluss</div>
|
||||
<div class="info-card__value" style="margin-bottom: 16px;">{{ props.event.registrationFinalEnd.formatted }}</div>
|
||||
|
||||
<div style="font-size: 0.75rem; color: #366a34; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px;">Frühbucher bis</div>
|
||||
<div style="font-size: 1rem; color: #366a34; font-weight: 500;">{{ props.event.earlyBirdEnd.formatted }}</div>
|
||||
<div class="info-card__label info-card__label--green">Frühbucher bis</div>
|
||||
<div class="info-card__value" style="color: #366a34;">{{ props.event.earlyBirdEnd.formatted }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Zeile 2 rechts: Kontakt -->
|
||||
<div style="background: #f8fafc; border-radius: 8px; padding: 16px;">
|
||||
<div style="font-size: 0.75rem; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px;">Kontakt</div>
|
||||
<p style="margin: 0 0 6px 0; color: #374151; font-size: 0.9rem; line-height: 1.6;">
|
||||
<!-- Kontakt -->
|
||||
<div class="info-card">
|
||||
<div class="info-card__label">Kontakt</div>
|
||||
<p class="info-card__text">
|
||||
Hast du Fragen zur Veranstaltung oder deiner Anmeldung? Kontaktiere uns per E-Mail:
|
||||
</p>
|
||||
<a :href="'mailto:' + props.event.email" style="font-size: 0.95rem; color: #2563eb; text-decoration: none; font-weight: 500;">{{ props.event.email }}</a>
|
||||
<a :href="'mailto:' + props.event.email" class="info-card__link">{{ props.event.email }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="signup-divider" />
|
||||
|
||||
<hr style="margin: 0 32px; border: none; border-top: 1px solid #e5e7eb;" />
|
||||
<div style="padding: 28px 32px;">
|
||||
<div class="signup-body">
|
||||
<SignupForm
|
||||
:event="props.event"
|
||||
:participantData="props.participantData ?? {}"
|
||||
@@ -115,5 +115,151 @@ function close() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.signup-box {
|
||||
width: 95%;
|
||||
margin: 30px auto;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.signup-header {
|
||||
padding: 28px 32px;
|
||||
color: white;
|
||||
}
|
||||
.signup-header--open { background: linear-gradient(135deg, #1e40af, #3b82f6); }
|
||||
.signup-header--closed { background: linear-gradient(135deg, #991b1b, #dc2626); }
|
||||
|
||||
.signup-header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.signup-event-title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.signup-badge {
|
||||
padding: 4px 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
border: 1px solid rgba(255,255,255,0.4);
|
||||
}
|
||||
.signup-badge--open { background: rgba(255,255,255,0.2); color: white; }
|
||||
.signup-badge--closed { background: rgba(255,0,0,0.2); color: #fecaca; }
|
||||
|
||||
.signup-event-location {
|
||||
margin: 8px 0 0 0;
|
||||
opacity: 0.85;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
.signup-body { padding: 28px 32px; }
|
||||
.signup-divider { margin: 0 32px; border: none; border-top: 1px solid #e5e7eb; }
|
||||
|
||||
/* Info-Grid */
|
||||
.signup-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
.info-card--accent {
|
||||
background: #f0f9ff;
|
||||
border-left: 3px solid #0ea5e9;
|
||||
}
|
||||
|
||||
.info-card__label {
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.info-card__label--accent { color: #0369a1; margin-bottom: 8px; }
|
||||
.info-card__label--green { color: #366a34; margin-top: 4px; }
|
||||
|
||||
.info-card__value {
|
||||
font-size: 1rem;
|
||||
color: #111827;
|
||||
font-weight: 500;
|
||||
}
|
||||
.info-card__sub {
|
||||
font-size: 0.85rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
.info-card__list {
|
||||
color: #0c4a6e;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.info-card__text {
|
||||
margin: 0 0 6px 0;
|
||||
color: #374151;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.info-card__link {
|
||||
font-size: 0.95rem;
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* ─── Tablet (640–1023px) ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.signup-box {
|
||||
width: 100%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
.signup-header,
|
||||
.signup-body { padding: 20px 18px; }
|
||||
.signup-divider { margin: 0 18px; }
|
||||
|
||||
.signup-info-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
.signup-event-title { font-size: 1.25rem; }
|
||||
}
|
||||
|
||||
/* ─── Smartphone (< 640px) ─── */
|
||||
@media (max-width: 639px) {
|
||||
.signup-box {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
.signup-header,
|
||||
.signup-body { padding: 16px 12px; }
|
||||
.signup-divider { margin: 0 12px; }
|
||||
|
||||
.signup-event-title { font-size: 1.1rem; }
|
||||
.signup-event-location { font-size: 0.85rem; }
|
||||
.signup-badge { font-size: 0.7rem; padding: 3px 10px; }
|
||||
|
||||
.signup-info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-card { padding: 12px; }
|
||||
.info-card__value { font-size: 0.95rem; }
|
||||
.info-card__list { font-size: 0.82rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -100,8 +100,8 @@ async function sendData() {
|
||||
type="text"
|
||||
@keyup="invoiceCheckContactName();"
|
||||
id="contact_name"
|
||||
name="contact_name" v-model="userName"
|
||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
class="refund-input"
|
||||
name="contact_name" v-model="userName" /><br /><br />
|
||||
|
||||
<label v-if="userName !== ''">
|
||||
<strong>E-Mail-Adresse (Für Rückfragen):</strong>
|
||||
@@ -109,9 +109,9 @@ async function sendData() {
|
||||
<input
|
||||
v-if="userName !== ''"
|
||||
type="email"
|
||||
class="refund-input"
|
||||
name="contact_email"
|
||||
v-model="userEmail"
|
||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
v-model="userEmail" /><br /><br />
|
||||
|
||||
<label v-if="userName !== ''">
|
||||
<strong>Telefonnummer (für Rückfragen):</strong>
|
||||
@@ -119,26 +119,26 @@ async function sendData() {
|
||||
<input
|
||||
v-if="userName !== ''"
|
||||
type="text"
|
||||
class="refund-input"
|
||||
id="contact_telephone"
|
||||
name="contact_telephone" v-model="userTelephone"
|
||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
name="contact_telephone" v-model="userTelephone" /><br /><br />
|
||||
|
||||
<label v-if="userName !== ''">
|
||||
<strong>Weitere Informationen zur Abrechnung (Max. 128 Zeichen):</strong>
|
||||
</label><br />
|
||||
<textarea
|
||||
id="notices"
|
||||
class="refund-input"
|
||||
name="notices" v-model="notices"
|
||||
maxlength="128"
|
||||
style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
maxlength="128" /><br /><br />
|
||||
|
||||
<span id="decision" v-if="userEmail !== '' && userTelephone !== '' && decision === ''">
|
||||
<label><br />
|
||||
<strong>Möchtest du den Betrag spenden?</strong>
|
||||
</label><br />
|
||||
|
||||
<input type="button" style="border-radius: 0; width: 100px;" @click="decision='donation'" value="Ja" />
|
||||
<input type="button" style="border-radius: 0; width: 100px;" @click="decision='payout'" value="Nein" />
|
||||
<input type="button" class="refund-choice" @click="decision='donation'" value="Ja" />
|
||||
<input type="button" class="refund-choice" @click="decision='payout'" value="Nein" />
|
||||
</span>
|
||||
|
||||
<span id="confirm_donation" v-if="decision === 'donation'">
|
||||
@@ -151,12 +151,12 @@ async function sendData() {
|
||||
<span id="confirm_payment" v-if="decision === 'payout'">
|
||||
<label>
|
||||
<strong>Konto-Inhaber*in:</strong></label><br />
|
||||
<input type="text" name="account_owner" id="account_owner" v-model="userAccountOwner" style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
<input type="text" name="account_owner" id="account_owner" class="refund-input" v-model="userAccountOwner" /><br /><br />
|
||||
|
||||
<label>
|
||||
<strong>IBAN:</strong>
|
||||
</label><br />
|
||||
<IbanInput id="account_iban" name="account_iban" v-model="userIban" style="font-size: 14pt; width: 550px;" /><br /><br />
|
||||
<IbanInput id="account_iban" name="account_iban" class="refund-input" v-model="userIban" /><br /><br />
|
||||
<ErrorText :message="receiptError" />
|
||||
<span v-if="userAccountOwner != '' && userIban && userIban.length === 27"><br />
|
||||
<input type="radio" name="confirmation_radio" value="payment" id="confirmation_radio_payment" v-model="confirmation">
|
||||
@@ -168,6 +168,26 @@ async function sendData() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* optional styling */
|
||||
</style>
|
||||
.refund-input {
|
||||
font-size: 14pt;
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.refund-choice {
|
||||
border-radius: 0;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.refund-input {
|
||||
font-size: 12pt;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.refund-choice {
|
||||
width: calc(50% - 6px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,7 +33,7 @@ class LoginController extends CommonController {
|
||||
'password.required' => 'Bitte gib dein Passwort ein.',
|
||||
]);
|
||||
|
||||
$user = $this->users->findByUsername($request->get('username'));
|
||||
$user = $this->users->findByUsername($request->input('username'));
|
||||
if ($user !== null && $user->password === null) {
|
||||
return redirect()->intended('/register/verifyEmail');
|
||||
}
|
||||
|
||||
@@ -26,32 +26,32 @@ const password = ref('')
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||
|
||||
function resetPassword() {
|
||||
window.location.href = '/reset-password';
|
||||
}
|
||||
window.location.href = '/reset-password';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout title='Anmelden' :user="props.user" :navbar="props.navbar" :tenant="props.tenant" :currentPath="props.currentPath">
|
||||
<form method="POST" action="/login">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<shadowed-box style="width: 50%; margin: 150px auto; padding: 20px;">
|
||||
<table>
|
||||
<shadowed-box class="login-box">
|
||||
<table class="login-table">
|
||||
<tr>
|
||||
<th>Anmeldename</th>
|
||||
<td>
|
||||
<input type="text" name="username" id="username"></input>
|
||||
<input type="text" name="username" id="username" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Passwort</th>
|
||||
<td><input type="password" name="password" id="password"></input></td>
|
||||
<td><input type="password" name="password" id="password" /></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<input type="submit" value="Anmelden" style="margin-top: 20px;" />
|
||||
<input type="button" @click="resetPassword" style="margin-top: 20px; margin-left: 20px;" value="Passwort vergessen" />
|
||||
<td colspan="2" class="login-buttons">
|
||||
<input type="submit" value="Anmelden" />
|
||||
<input type="button" @click="resetPassword" value="Passwort vergessen" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -61,8 +61,59 @@ function resetPassword() {
|
||||
</template>
|
||||
|
||||
<style>
|
||||
th {
|
||||
width: 100px;
|
||||
.login-table th {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
width: 50%;
|
||||
margin: 150px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-buttons input {
|
||||
margin-top: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Tablet */
|
||||
@media (max-width: 1023px) {
|
||||
.login-box {
|
||||
width: 70% !important;
|
||||
margin: 80px auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smartphone */
|
||||
@media (max-width: 639px) {
|
||||
.login-box {
|
||||
width: 95% !important;
|
||||
margin: 30px auto !important;
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.login-table,
|
||||
.login-table tbody,
|
||||
.login-table tr,
|
||||
.login-table th,
|
||||
.login-table td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-table th {
|
||||
padding-bottom: 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.login-table th::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.login-buttons input {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Scopes\CommonModel;
|
||||
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
|
||||
|
||||
class CronTask extends CommonModel
|
||||
{
|
||||
@@ -11,6 +12,6 @@ class CronTask extends CommonModel
|
||||
protected $dates = ['last_run'];
|
||||
|
||||
protected $casts = [
|
||||
'last_run' => 'datetime',
|
||||
'last_run' => AsArrayObject::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ class CronTaskHandleProvider extends CommonController
|
||||
$now = Carbon::now();
|
||||
|
||||
$tenants = Tenant::where('has_active_instance', true)->get();
|
||||
|
||||
foreach ($tenants as $tenant) {
|
||||
app()->instance('tenant', $tenant);
|
||||
$this->runTenantTasks($tenant, $now);
|
||||
@@ -31,7 +30,6 @@ class CronTaskHandleProvider extends CommonController
|
||||
|
||||
private function runTenantTasks(Tenant $tenant, Carbon $now) {
|
||||
$tasks = CronTask::all();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
|
||||
// --- Every-Time Tasks ---
|
||||
@@ -41,11 +39,12 @@ class CronTaskHandleProvider extends CommonController
|
||||
|
||||
// --- Daily Tasks ---
|
||||
if ($task->execution_type === CronTaskType::CRON_TASK_TYPE_DAILY) {
|
||||
$tenantLastRun = $task->last_run?->get($tenant->slug);
|
||||
|
||||
$scheduledTime = \DateTime::createFromFormat('Y-m-d H:i:s', date('Y-m-d ') . $task->schedule_time);
|
||||
$now = Carbon::now();
|
||||
|
||||
|
||||
$alreadyRunToday = $task->last_run?->isToday() ?? false;
|
||||
$alreadyRunToday = $tenantLastRun == null ? true : $tenantLastRun->isToday() ?? false;
|
||||
|
||||
if (!$alreadyRunToday && $now >= $scheduledTime) {
|
||||
$this->runTask($task);
|
||||
@@ -59,14 +58,18 @@ class CronTaskHandleProvider extends CommonController
|
||||
$logger = $this->taskLogger($task->name, app('tenant'));
|
||||
app()->instance('taskLogger', $logger);
|
||||
|
||||
|
||||
$taskClass = "\\App\\Tasks\\" . $task->name;
|
||||
if (class_exists($taskClass)) {
|
||||
$instance = new $taskClass();
|
||||
$instance->handle();
|
||||
$lastRun = $task->last_run;
|
||||
if ($lastRun === null) {
|
||||
$lastRun = [];
|
||||
}
|
||||
|
||||
$lastRun[app('tenant')->slug] = now();
|
||||
// Update last_run
|
||||
$task->last_run = now();
|
||||
$task->last_run = $lastRun;
|
||||
$task->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ class CloseEvent implements CronTask {
|
||||
$eventRepository = new EventRepository();
|
||||
/** @var Event $event */
|
||||
foreach ($eventRepository->getAvailable(false) as $event) {
|
||||
echo $event->id . $event->tenant .'<br>';
|
||||
if ($event->registration_final_end <= $now ) {
|
||||
$event->registration_allowed = false;
|
||||
$event->save();
|
||||
|
||||
@@ -126,6 +126,14 @@ onUnmounted(() => {
|
||||
right: 15px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
z-index: 10;
|
||||
background: rgba(255,255,255,0.9);
|
||||
border-radius: 50%;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
@@ -146,4 +154,41 @@ onUnmounted(() => {
|
||||
transform: scale(0.98);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ─── Tablet ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.full-screen-modal-content {
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.full-screen-modal-body {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
.full-screen-modal-content {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.full-screen-modal-body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.full-screen-modal-close {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
font-size: 26px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+113
-84
@@ -1,42 +1,42 @@
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="show"
|
||||
class="modal-overlay"
|
||||
@click.self="close"
|
||||
>
|
||||
<transition name="scale">
|
||||
<div
|
||||
v-if="show"
|
||||
ref="modalRef"
|
||||
class="modal-content"
|
||||
tabindex="-1"
|
||||
:style="{ width: width }"
|
||||
>
|
||||
<teleport to="body">
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="show"
|
||||
class="modal-overlay"
|
||||
@click.self="close"
|
||||
>
|
||||
<transition name="scale">
|
||||
<div
|
||||
v-if="show"
|
||||
ref="modalRef"
|
||||
class="modal-content"
|
||||
tabindex="-1"
|
||||
:style="{ width: width }"
|
||||
>
|
||||
|
||||
<div class="modal-header">
|
||||
<div class="title"><label>{{ title }}</label>
|
||||
<span @click="close" class="dashicons dashicons-dismiss modal-close"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-header">
|
||||
<div class="title"><label>{{ title }}</label>
|
||||
<span @click="close" class="dashicons dashicons-dismiss modal-close"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" style="max-height: 600px; overflow: auto;">
|
||||
<slot />
|
||||
<div class="modal-body">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</teleport>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
title: { type: String, default: 'Modal' },
|
||||
show: Boolean,
|
||||
title: { type: String, default: 'Modal' },
|
||||
width: { type: String, default: '1200px' },
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
@@ -44,98 +44,127 @@ const emit = defineEmits(['close'])
|
||||
const modalRef = ref(null)
|
||||
|
||||
function close() {
|
||||
emit('close')
|
||||
emit('close')
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. ESC-Key schließt Modal
|
||||
*/
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === 'Escape') {
|
||||
close()
|
||||
}
|
||||
|
||||
// 2. Focus-Trap
|
||||
if (e.key === 'Tab' && modalRef.value) {
|
||||
const focusable = modalRef.value.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
)
|
||||
if (focusable.length === 0) return
|
||||
|
||||
const first = focusable[0]
|
||||
const last = focusable[focusable.length - 1]
|
||||
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault()
|
||||
last.focus()
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault()
|
||||
first.focus()
|
||||
if (e.key === 'Escape') {
|
||||
close()
|
||||
}
|
||||
|
||||
if (e.key === 'Tab' && modalRef.value) {
|
||||
const focusable = modalRef.value.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
)
|
||||
if (focusable.length === 0) return
|
||||
|
||||
const first = focusable[0]
|
||||
const last = focusable[focusable.length - 1]
|
||||
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault()
|
||||
last.focus()
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault()
|
||||
first.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. Body-Scroll sperren wenn Modal offen
|
||||
*/
|
||||
watch(
|
||||
() => props.show,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
await nextTick()
|
||||
modalRef.value?.focus() // Setzt Focus ins Modal
|
||||
} else {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
if (newVal) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
await nextTick()
|
||||
modalRef.value?.focus()
|
||||
} else {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
document.body.style.overflow = '' // fallback cleanup
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
document.body.style.overflow = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
|
||||
max-width: 1200px;
|
||||
width: 90%;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
outline: none;
|
||||
max-width: 1200px;
|
||||
width: 90%;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
.scale-enter-active,
|
||||
.scale-leave-active {
|
||||
transition: transform 0.25s ease, opacity 0.25s ease;
|
||||
transition: transform 0.25s ease, opacity 0.25s ease;
|
||||
}
|
||||
.scale-enter-from,
|
||||
.scale-leave-to {
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ─── Tablet ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.modal-content {
|
||||
width: 95% !important;
|
||||
max-width: 95vw;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 70vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
.modal-overlay {
|
||||
align-items: flex-end;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 75vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
import { ref, shallowRef, onMounted } from "vue"
|
||||
|
||||
const props = defineProps({
|
||||
tabs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
// [{ title: "Titel", component: Component, endpoint: "/wp-json/..." }]
|
||||
},
|
||||
subTabIndex: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
tabs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
subTabIndex: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const activeTab = ref(null) // aktuell ausgewählter Tab
|
||||
const tabData = ref({}) // Daten für jeden Tab
|
||||
const tabComponent = shallowRef(null) // Komponente für aktuellen Tab
|
||||
const activeTab = ref(null)
|
||||
const tabData = ref({})
|
||||
const tabComponent = shallowRef(null)
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
@@ -34,13 +33,13 @@ async function selectTab(index) {
|
||||
?.getAttribute('content')
|
||||
|
||||
const response = await fetch(tab.endpoint, {
|
||||
method: 'GET', // oder POST, PUT, DELETE …
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin', // wichtig für Session/Auth
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error("Fehler: " + response.status)
|
||||
@@ -56,65 +55,102 @@ async function selectTab(index) {
|
||||
}
|
||||
}
|
||||
|
||||
// ersten Tab automatisch laden
|
||||
onMounted(() => {
|
||||
if (props.tabs.length > 0) {
|
||||
selectTab(props.subTabIndex)
|
||||
}
|
||||
if (props.tabs.length > 0) {
|
||||
selectTab(props.subTabIndex)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tabs">
|
||||
<!-- Tab Header -->
|
||||
<div class="tab-header">
|
||||
<button
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="['tab-button', { active: activeTab === index }]"
|
||||
@click="selectTab(index)"
|
||||
>
|
||||
{{ tab.title }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<div class="tab-header">
|
||||
<button
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="['tab-button', { active: activeTab === index }]"
|
||||
@click="selectTab(index)"
|
||||
>
|
||||
{{ tab.title }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content" id="tab-content">
|
||||
<div v-if="loading">⏳ Lädt...</div>
|
||||
<div v-else-if="error">❌ {{ error }}</div>
|
||||
<component
|
||||
v-else-if="tabComponent"
|
||||
:is="tabComponent"
|
||||
:data="tabData[activeTab]"
|
||||
:deep_jump_id="tabs[activeTab].deep_jump_id"
|
||||
:deep_jump_id_sub="tabs[activeTab].deep_jump_id_sub"
|
||||
/>
|
||||
<div class="tab-content" id="tab-content">
|
||||
<div v-if="loading">⏳ Lädt...</div>
|
||||
<div v-else-if="error">❌ {{ error }}</div>
|
||||
<component
|
||||
v-else-if="tabComponent"
|
||||
:is="tabComponent"
|
||||
:data="tabData[activeTab]"
|
||||
:deep_jump_id="tabs[activeTab].deep_jump_id"
|
||||
:deep_jump_id_sub="tabs[activeTab].deep_jump_id_sub"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tabs {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.tab-header {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ddd;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
.tab-button {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: #f8f8f8;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: #f8f8f8;
|
||||
white-space: nowrap;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.tab-button.active {
|
||||
font-weight: bold;
|
||||
background: white;
|
||||
border-bottom: 2px solid #0073aa;
|
||||
font-weight: bold;
|
||||
background: white;
|
||||
border-bottom: 2px solid #0073aa;
|
||||
}
|
||||
.tab-content {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* ─── Tablet ─── */
|
||||
@media (max-width: 1023px) {
|
||||
.tab-button {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Smartphone ─── */
|
||||
@media (max-width: 639px) {
|
||||
.tab-header {
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 0.65rem 1rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
border-bottom: none;
|
||||
border-left: 4px solid #0073aa;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -46,4 +46,12 @@ import UpcomingEvents from "../../../Domains/Dashboard/Views/Partials/Widgets/Up
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.widget-container {
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
+129
-15
@@ -16,7 +16,6 @@
|
||||
html {
|
||||
background-color: #FAFAFB !important;
|
||||
font-family: Aleo;
|
||||
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@@ -39,35 +38,32 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 80px; /* Höhe anpassen */
|
||||
height: 80px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: calc(100% - 40px);
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header .anonymous-actions {
|
||||
position: relative;
|
||||
right: calc(-100% + 330px);
|
||||
width: 350px;
|
||||
right: calc(-100% + 300px);
|
||||
width: 450px;
|
||||
overflow: hidden;
|
||||
border-radius: 50px 0 0 50px;
|
||||
background-color: #FAFAFB !important;
|
||||
text-align: right;
|
||||
padding: 10px;
|
||||
top: -75px;
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
.header .user-info {
|
||||
position: relative;
|
||||
right: calc(-100% + 275px);
|
||||
width: 295px;
|
||||
right: calc(-100% + 190px);
|
||||
width: 195px;
|
||||
overflow: hidden;
|
||||
border-radius: 50px 0 0 50px;
|
||||
background-color: #FAFAFB !important;
|
||||
text-align: right;
|
||||
padding: 10px;
|
||||
top: -75px;
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
.header .left-side {
|
||||
@@ -78,7 +74,6 @@ h1, h2, h3, h4, h5, h6 {
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
margin-top: -5px;
|
||||
|
||||
}
|
||||
|
||||
.flexbox {
|
||||
@@ -88,8 +83,6 @@ h1, h2, h3, h4, h5, h6 {
|
||||
padding: 0;
|
||||
gap: 1px;
|
||||
margin: 0;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
@@ -182,3 +175,124 @@ th:after {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
RESPONSIVE – TABLET (640px – 1023px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 1023px) {
|
||||
.app-layout {
|
||||
margin: 0 !important;
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
height: 60px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header .anonymous-actions,
|
||||
.header .user-info {
|
||||
position: static !important;
|
||||
right: auto !important;
|
||||
top: auto !important;
|
||||
width: auto !important;
|
||||
border-radius: 0 !important;
|
||||
background-color: transparent !important;
|
||||
padding: 0 10px !important;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.header .left-side {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
padding: 0 15px;
|
||||
margin-top: 0 !important;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.header .left-side h1 {
|
||||
font-size: 1.1rem;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#show_username {
|
||||
position: static !important;
|
||||
top: auto !important;
|
||||
font-size: 0.78rem;
|
||||
text-align: left;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 15px !important;
|
||||
}
|
||||
|
||||
.footer table {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.footer table td:first-child {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
RESPONSIVE – SMARTPHONE (< 640px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 639px) {
|
||||
.header {
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.header .left-side h1 {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
#show_username {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.header .anonymous-actions a,
|
||||
.header .user-info a {
|
||||
padding: 8px 8px !important;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 12px 8px !important;
|
||||
}
|
||||
|
||||
.footer table,
|
||||
.footer table tbody,
|
||||
.footer table tr,
|
||||
.footer table td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
text-align: center;
|
||||
line-height: 1.8em;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: 9pt;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,3 +38,96 @@
|
||||
font-weight: bolder;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
RESPONSIVE – TABLET (640px – 1023px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 1023px) {
|
||||
.costunit-list table {
|
||||
width: 100% !important;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.costunit-list table tr td input[type="button"] {
|
||||
width: 100%;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.costunit-list table tr th {
|
||||
width: 120px !important;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
RESPONSIVE – SMARTPHONE (< 640px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 639px) {
|
||||
.costunit-list table {
|
||||
width: 100% !important;
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.costunit-list table thead,
|
||||
.costunit-list table tbody,
|
||||
.costunit-list table tfoot,
|
||||
.costunit-list table tr,
|
||||
.costunit-list table td,
|
||||
.costunit-list table th {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.costunit-list table td[rowspan] {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.costunit-list table tr {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.costunit-list table tr th {
|
||||
flex: 0 0 140px;
|
||||
font-size: 0.82rem !important;
|
||||
width: 140px !important;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.costunit-list table tr td {
|
||||
flex: 1;
|
||||
font-size: 0.82rem;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.costunit-list table thead tr td {
|
||||
font-size: 1rem !important;
|
||||
padding: 6px 0 !important;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.costunit-list table tr td:last-child {
|
||||
flex: 0 0 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.costunit-list table tr td input[type="button"] {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
font-size: 0.85rem;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.costunit-list .link {
|
||||
display: inline-block;
|
||||
margin: 4px 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
+87
-2
@@ -40,7 +40,6 @@ fieldset legend {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
||||
.invoice-list-table {
|
||||
width: 90%;
|
||||
margin: 20px auto;
|
||||
@@ -59,7 +58,93 @@ fieldset legend {
|
||||
border-right: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
|
||||
.invoice-list-table tr:first-child td {
|
||||
background: linear-gradient(to bottom, #fff, #f6f7f7);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
RESPONSIVE – TABLET (640px – 1023px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 1023px) {
|
||||
.invoice-main-flexbox {
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.invoice-type-layer {
|
||||
flex: 1 1 calc(50% - 15px);
|
||||
min-width: 180px;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 10px 10px;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
.invoice-list-table {
|
||||
width: 100%;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
RESPONSIVE – SMARTPHONE (< 640px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 639px) {
|
||||
.invoice-main-flexbox {
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.invoice-type-layer {
|
||||
flex: 1 1 100%;
|
||||
min-height: 100px;
|
||||
font-size: 0.9rem;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 8px 4px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
fieldset legend {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.invoice-list-table {
|
||||
width: 100%;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.invoice-list-table tr {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-radius: 6px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.invoice-list-table td {
|
||||
display: block;
|
||||
width: 100% !important;
|
||||
border: none !important;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.invoice-list-table tr:first-child td {
|
||||
background: linear-gradient(to bottom, #fff, #f6f7f7);
|
||||
border-bottom: 1px solid #ddd !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.invoice-list-table input[type="button"],
|
||||
.invoice-list-table .button {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
+352
-112
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import {reactive, onMounted} from 'vue';
|
||||
import {reactive, onMounted, ref} from 'vue';
|
||||
import Icon from "../../../app/Views/Components/Icon.vue";
|
||||
import GlobalWidgets from "../../../app/Views/Partials/GlobalWidgets/GlobalWidgets.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
@@ -13,6 +13,7 @@ const globalProps = reactive({
|
||||
common: [],
|
||||
costunits: [],
|
||||
events: [],
|
||||
eventControl: [],
|
||||
},
|
||||
tenant: '',
|
||||
user: null,
|
||||
@@ -22,6 +23,16 @@ const globalProps = reactive({
|
||||
message: ''
|
||||
});
|
||||
|
||||
const sidebarOpen = ref(false);
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebarOpen.value = !sidebarOpen.value;
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
sidebarOpen.value = false;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/core/retrieve-global-data');
|
||||
const data = await response.json();
|
||||
@@ -52,130 +63,109 @@ const props = defineProps({
|
||||
flash: { type: Object, default: () => ({}) }
|
||||
});
|
||||
|
||||
console.log(globalProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-layout">
|
||||
<!-- Sidebar -->
|
||||
<!-- Main content -->
|
||||
<!-- Mobile Overlay -->
|
||||
<div class="sidebar-overlay" :class="{ active: sidebarOpen }" @click="closeSidebar"></div>
|
||||
|
||||
<div class="main">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<button class="hamburger-btn" @click="toggleSidebar" aria-label="Menü öffnen">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
|
||||
<div class="left-side">
|
||||
<h1>{{ props.title }}</h1>
|
||||
<label id="show_username" v-if="globalProps.user !== null">Willkommen, {{ globalProps.user.nicename }}</label>
|
||||
</div>
|
||||
|
||||
<div class="header-actions" v-if="globalProps.user !== null">
|
||||
<div class="user-info">
|
||||
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
|
||||
<Icon name="envelope" />
|
||||
</a>
|
||||
<a href="/profile" class="header-link-anonymous" title="Mein Profil">
|
||||
<Icon name="user" />
|
||||
</a>
|
||||
<a href="/logout" class="header-link-anonymous-logout" title="Abmelden">
|
||||
<Icon name="lock" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="anonymous-header-actions-mark" v-else>
|
||||
<div class="anonymous-actions">
|
||||
<a href="/register" class="header-link-anonymous">Registrieren</a>
|
||||
<a href="/login" class="header-link-anonymous">Anmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Flexbox: Sidebar + Content -->
|
||||
<div class="flexbox">
|
||||
<div class="sidebar">
|
||||
<div class="sidebar" :class="{ 'sidebar-open': sidebarOpen }">
|
||||
<div class="logo">
|
||||
<img src="../../../public/images/logo.png" alt="Logo" />
|
||||
|
||||
</div>
|
||||
<nav class="nav">
|
||||
<ul class="nav-links" v-if="globalProps.navbar.personal.length > 0">
|
||||
<li v-for="navlink in globalProps.navbar.personal">
|
||||
<a
|
||||
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url"
|
||||
>{{navlink.display}}</a>
|
||||
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav-links" v-if="globalProps.navbar.common.length > 0">
|
||||
<li v-for="navlink in globalProps.navbar.common">
|
||||
<a
|
||||
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url"
|
||||
>{{navlink.display}}</a>
|
||||
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav-links" v-if="globalProps.navbar.costunits.length > 0">
|
||||
<li v-for="navlink in globalProps.navbar.costunits">
|
||||
<a
|
||||
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url"
|
||||
>{{navlink.display}}</a>
|
||||
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav-links" v-if="globalProps.navbar.events.length > 0">
|
||||
<li v-for="navlink in globalProps.navbar.events">
|
||||
<a
|
||||
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url"
|
||||
>{{navlink.display}}</a>
|
||||
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav-links" v-if="globalProps.navbar.eventControl.length > 0">
|
||||
<ul class="nav-links" v-if="globalProps.navbar.eventControl && globalProps.navbar.eventControl.length > 0">
|
||||
<li v-for="navlink in globalProps.navbar.eventControl">
|
||||
<a
|
||||
:class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url"
|
||||
>{{navlink.display}}</a>
|
||||
<a :class="{ navlink_active: navlink.url.endsWith(currentPath) }"
|
||||
:href="navlink.url" @click="closeSidebar">{{ navlink.display }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="header">
|
||||
<div class="left-side">
|
||||
<h1>{{ props.title }}</h1>
|
||||
<label id="show_username" v-if="globalProps.user !== null">Willkommen, {{ globalProps.user.nicename }}</label>
|
||||
</div>
|
||||
<div class="user-info" v-if="globalProps.user !== null">
|
||||
<a href="/messages" class="header-link-anonymous" title="Meine Nachrichten">
|
||||
<Icon name="envelope" />
|
||||
</a>
|
||||
|
||||
<a href="/profile" class="header-link-anonymous" title="Mein Profil">
|
||||
<Icon name="user" />
|
||||
</a>
|
||||
|
||||
<a href="/logout" class="header-link-anonymous-logout" title="Abmelden">
|
||||
<Icon name="lock" />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="anonymous-actions" v-else>
|
||||
<a href="/register" class="header-link-anonymous">Registrieren</a>
|
||||
<a href="/login" class="header-link-anonymous"> Anmelden </a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
<global-widgets :user="globalProps.user" :tenant="globalProps.tenant" v-if="globalProps.user !== null" />
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Version {{ globalProps.version }}</td>
|
||||
<td>
|
||||
mareike - Mdodernes Anmeldesystem und richtig einfache Kostenerfassung
|
||||
</td>
|
||||
<td>
|
||||
Impressum
|
||||
</td>
|
||||
<td>
|
||||
Datenschutzerklärung
|
||||
</td>
|
||||
<td>
|
||||
© 2022 - 2026
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="footer-inner">
|
||||
<span>Version {{ globalProps.version }}</span>
|
||||
<span class="footer-hide-mobile">mareike – Modernes Anmeldesystem und richtig einfache Kostenerfassung</span>
|
||||
<span>Impressum</span>
|
||||
<span>Datenschutzerklärung</span>
|
||||
<span>© 2022 – 2026</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Toaster -->
|
||||
<transition name="fade">
|
||||
<div v-if="flash.message" class="toaster">
|
||||
{{ flash.message }}
|
||||
@@ -185,6 +175,55 @@ console.log(globalProps)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ─── Header ─── */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
padding: 0;
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
flex: 1;
|
||||
padding: 0 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.left-side h1 {
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#show_username {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.anonymous-header-actions-mark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.header-link-anonymous,
|
||||
.header-link-anonymous-logout {
|
||||
@@ -192,13 +231,8 @@ console.log(globalProps)
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
background-color: #ffffff;
|
||||
padding: 10px 30px;
|
||||
margin: 0 -20px 0 30px;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.header-link-anonymous-logout {
|
||||
padding-right: 35px !important;
|
||||
padding: 10px 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-link-anonymous:hover {
|
||||
@@ -211,45 +245,110 @@ console.log(globalProps)
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
border-bottom: 1px solid #ddd;
|
||||
width: 285px;
|
||||
/* ─── Hamburger ─── */
|
||||
.hamburger-btn {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
width: 50px;
|
||||
height: 80px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.nav-links li a {
|
||||
color: #b6b6b6;
|
||||
background-color: #fff;
|
||||
padding: 20px 25px;
|
||||
.hamburger-btn span {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
width: calc(100% - 50px);
|
||||
font-weight: bold;
|
||||
margin-bottom: 0px;
|
||||
width: 24px;
|
||||
height: 3px;
|
||||
background-color: #333;
|
||||
border-radius: 2px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
/* ─── Layout ─── */
|
||||
.app-layout {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background: #f0f2f5;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
margin: 20px;
|
||||
box-shadow: 20px 20px 15px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.flexbox {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
background-color: #FAFAFB;
|
||||
overflow: hidden;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
/* ─── Sidebar ─── */
|
||||
.sidebar {
|
||||
flex-basis: 275px;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ffffff;
|
||||
overflow-y: auto;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.4);
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 135px;
|
||||
height: 70px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* ─── Nav ─── */
|
||||
.nav {
|
||||
flex: 1;
|
||||
margin-left: -10px;
|
||||
width: 275px;
|
||||
|
||||
}
|
||||
|
||||
.nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.nav li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
padding: 8px 12px;
|
||||
.nav-links li a {
|
||||
color: #b6b6b6;
|
||||
background-color: #fff;
|
||||
padding: 16px 25px;
|
||||
display: block;
|
||||
transition: background 0.2s;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav a:hover {
|
||||
@@ -259,6 +358,147 @@ console.log(globalProps)
|
||||
|
||||
.navlink_active {
|
||||
background-color: #fae39c !important;
|
||||
color: #1d4899 !important; /* Dunklere Schrift beim Hover */
|
||||
color: #1d4899 !important;
|
||||
}
|
||||
|
||||
/* ─── Content ─── */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px 20px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ─── Footer ─── */
|
||||
.footer {
|
||||
background: #666666;
|
||||
border-top: 1px solid #ddd;
|
||||
color: #ffffff;
|
||||
padding: 10px 15px;
|
||||
font-size: 11pt;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 20px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
TABLET (640px – 1023px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 1023px) {
|
||||
.app-layout {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.hamburger-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
transform: translateX(-100%);
|
||||
width: 260px;
|
||||
flex-basis: 260px;
|
||||
}
|
||||
|
||||
.sidebar.sidebar-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.sidebar-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header-link-anonymous,
|
||||
.header-link-anonymous-logout {
|
||||
padding: 10px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.left-side h1 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
SMARTPHONE (< 640px)
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 639px) {
|
||||
.header {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.hamburger-btn {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.left-side h1 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#show_username {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.anonymous-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
font-size: 0.8rem;
|
||||
width: 250px !important;
|
||||
}
|
||||
|
||||
.anonymous-header-actions-mark {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-link-anonymous,
|
||||
.header-link-anonymous-logout {
|
||||
padding: 6px 8px;
|
||||
font-size: 0.75rem;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.footer-hide-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
justify-content: center;
|
||||
font-size: 9pt;
|
||||
gap: 6px 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
flex-basis: 240px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user