Icons for payment methods
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||
import {toast} from "vue3-toastify";
|
||||
|
||||
@@ -17,14 +16,10 @@ const emit = defineEmits(['close'])
|
||||
const dynmicProps = reactive({
|
||||
localGroups: [],
|
||||
eatingHabits:[],
|
||||
paymentMethods: []
|
||||
});
|
||||
|
||||
const contributingLocalGroups = ref([])
|
||||
const eatingHabits = ref([]);
|
||||
const paymentMethods = ref([]);
|
||||
// pro-Event-Config je Zahlungsmethode: { slug: { option: value } }
|
||||
const paymentMethodConfigurations = reactive({});
|
||||
|
||||
const errors = reactive({})
|
||||
|
||||
@@ -51,30 +46,9 @@ const emit = defineEmits(['close'])
|
||||
|
||||
contributingLocalGroups.value = props.event.contributingLocalGroups?.map(t => t.id) ?? []
|
||||
eatingHabits.value = props.event.eatingHabits?.map(t => t.id) ?? []
|
||||
paymentMethods.value = props.event.paymentMethods?.map(t => t.slug) ?? []
|
||||
|
||||
// Pro-Event-Config vorbelegen: bereits gespeicherte Pivot-Config des Events hat Vorrang,
|
||||
// sonst der Tenant-Standard (Snapshot-Quelle für neu zugewiesene Methoden).
|
||||
const eventConfigBySlug = {}
|
||||
for (const pm of props.event.paymentMethods ?? []) {
|
||||
eventConfigBySlug[pm.slug] = pm.configuration ?? {}
|
||||
}
|
||||
for (const method of dynmicProps.paymentMethods ?? []) {
|
||||
const source = eventConfigBySlug[method.slug] ?? method.configuration ?? {}
|
||||
const config = {}
|
||||
for (const option of method.optionsSchema ?? []) {
|
||||
config[option.name] = source[option.name] ?? ''
|
||||
}
|
||||
paymentMethodConfigurations[method.slug] = config
|
||||
}
|
||||
});
|
||||
|
||||
async function save() {
|
||||
if (paymentMethods.value.length === 0) {
|
||||
toast.error('Mindestens eine Zahlungsmöglichkeit muss ausgewählt sein.')
|
||||
return
|
||||
}
|
||||
|
||||
const response = await request('/api/v1/event/details/' + props.event.id + '/common-settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -94,8 +68,6 @@ const emit = defineEmits(['close'])
|
||||
supportPerson: formData.supportPerson,
|
||||
contributingLocalGroups: contributingLocalGroups.value,
|
||||
eatingHabits: eatingHabits.value,
|
||||
paymentMethods: paymentMethods.value,
|
||||
paymentMethodConfigurations: paymentMethodConfigurations,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -227,31 +199,6 @@ const emit = defineEmits(['close'])
|
||||
<label style="padding-left: 5px;" :for="'eatinghabit' + eatingHabit.id">{{eatingHabit.name}}</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th style="padding-top: 40px !important;">Zahlungsmöglichkeiten</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="paymentMethod in dynmicProps.paymentMethods">
|
||||
<td>
|
||||
<input type="checkbox" :id="'paymentmethod' + paymentMethod.id" :value="paymentMethod.slug" v-model="paymentMethods" />
|
||||
<label style="padding-left: 5px;" :for="'paymentmethod' + paymentMethod.id">{{paymentMethod.name}}</label>
|
||||
|
||||
<div
|
||||
v-if="paymentMethods.includes(paymentMethod.slug) && (paymentMethod.optionsSchema?.length ?? 0) > 0"
|
||||
class="payment-method-config"
|
||||
>
|
||||
<div v-for="option in paymentMethod.optionsSchema" :key="option.name" class="payment-method-config-row">
|
||||
<label>{{ option.label }}<span v-if="option.required" class="required-marker">*</span></label>
|
||||
<TextEditor v-if="option.type === 'richtext'"
|
||||
v-model="paymentMethodConfigurations[paymentMethod.slug][option.name]"/>
|
||||
<input v-else type="text"
|
||||
v-model="paymentMethodConfigurations[paymentMethod.slug][option.name]"
|
||||
class="width-full"/>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -308,26 +255,4 @@ const emit = defineEmits(['close'])
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.payment-method-config {
|
||||
margin: 6px 0 12px 24px;
|
||||
padding-left: 10px;
|
||||
border-left: 2px solid #d1d5db;
|
||||
}
|
||||
|
||||
.payment-method-config-row {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.payment-method-config-row label {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
color: #374151;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.required-marker {
|
||||
color: #ef4444;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,59 @@
|
||||
<script setup>
|
||||
import {reactive, watch} from "vue";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||
import {toast} from "vue3-toastify";
|
||||
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import AmountInput from "../../../../Views/Components/AmountInput.vue";
|
||||
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||
import Modal from "../../../../Views/Components/Modal.vue";
|
||||
import RichSelectBox from "../../../../Views/Components/RichSelectBox.vue";
|
||||
import {PAYMENT_METHOD_ICONS} from "../../../../../resources/js/constants/paymentMethodIcons.js";
|
||||
import {toast} from "vue3-toastify";
|
||||
import {request} from "../../../../../resources/js/components/HttpClient.js";
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const props = defineProps({
|
||||
event: Object,
|
||||
})
|
||||
|
||||
// Verfügbare (aktive) Zahlungsarten des Tenants inkl. optionsSchema + Default-Config.
|
||||
const availablePaymentMethods = ref([])
|
||||
// Auswahl der Zahlungsart-Slugs für dieses Event (Default: leer bei neuen Events).
|
||||
const paymentMethods = ref([])
|
||||
// pro-Event-Config je Zahlungsmethode: { slug: { option: value } }
|
||||
const paymentMethodConfigurations = reactive({})
|
||||
|
||||
// Optionen-Modal
|
||||
const showOptionsModal = ref(false)
|
||||
const optionsMethod = ref(null)
|
||||
|
||||
function openOptions(method) {
|
||||
optionsMethod.value = method
|
||||
showOptionsModal.value = true
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const response = await fetch('/api/v1/core/retrieve-event-setting-data');
|
||||
const data = await response.json();
|
||||
availablePaymentMethods.value = data.paymentMethods ?? []
|
||||
|
||||
paymentMethods.value = props.event.paymentMethods?.map(t => t.slug) ?? []
|
||||
|
||||
// Pro-Event-Config vorbelegen: bereits gespeicherte Pivot-Config des Events hat Vorrang,
|
||||
// sonst der Tenant-Standard (Snapshot-Quelle für neu zugewiesene Methoden).
|
||||
const eventConfigBySlug = {}
|
||||
for (const pm of props.event.paymentMethods ?? []) {
|
||||
eventConfigBySlug[pm.slug] = pm.configuration ?? {}
|
||||
}
|
||||
for (const method of availablePaymentMethods.value) {
|
||||
const source = eventConfigBySlug[method.slug] ?? method.configuration ?? {}
|
||||
const config = {}
|
||||
for (const option of method.optionsSchema ?? []) {
|
||||
config[option.name] = source[option.name] ?? ''
|
||||
}
|
||||
paymentMethodConfigurations[method.slug] = config
|
||||
}
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
const formData = reactive({
|
||||
"pft_1_active": true,
|
||||
@@ -75,6 +118,24 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (paymentMethods.value.length === 0) {
|
||||
toast.error('Mindestens eine Zahlungsmöglichkeit muss ausgewählt sein.')
|
||||
return;
|
||||
}
|
||||
|
||||
const paymentResponse = await request('/api/v1/event/details/' + props.event.id + '/payment-methods', {
|
||||
method: "POST",
|
||||
body: {
|
||||
paymentMethods: paymentMethods.value,
|
||||
paymentMethodConfigurations: paymentMethodConfigurations,
|
||||
}
|
||||
})
|
||||
|
||||
if (paymentResponse.status !== 'success') {
|
||||
toast.error(paymentResponse.message ?? 'Die Zahlungsmöglichkeiten konnten nicht gespeichert werden.')
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await request('/api/v1/event/details/' + props.event.id + '/participation-fees', {
|
||||
method: "POST",
|
||||
body: {
|
||||
@@ -286,10 +347,73 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4" style="padding-top: 30px;">
|
||||
<h4>Zahlungsmöglichkeiten</h4>
|
||||
<div v-for="paymentMethod in availablePaymentMethods" :key="paymentMethod.slug"
|
||||
class="payment-method-row">
|
||||
<input type="checkbox" :id="'paymentmethod' + paymentMethod.id" :value="paymentMethod.slug"
|
||||
v-model="paymentMethods"/>
|
||||
<label style="padding-left: 5px;" :for="'paymentmethod' + paymentMethod.id">{{
|
||||
paymentMethod.name
|
||||
}}</label>
|
||||
<label
|
||||
v-if="paymentMethods.includes(paymentMethod.slug) && (paymentMethod.optionsSchema?.length ?? 0) > 0"
|
||||
class="link"
|
||||
style="padding-left: 15px; font-size: 10pt;"
|
||||
@click="openOptions(paymentMethod)"
|
||||
>Optionen</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4" style="padding-top: 20px;">
|
||||
<input type="button" value="Speichern" @click="saveParticipationFees" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<Modal
|
||||
v-if="showOptionsModal"
|
||||
:show="showOptionsModal"
|
||||
:title="'Optionen: ' + (optionsMethod?.name ?? '')"
|
||||
width="700px"
|
||||
@close="showOptionsModal = false"
|
||||
>
|
||||
<div v-for="option in optionsMethod?.optionsSchema ?? []" :key="option.name" class="payment-method-config-row">
|
||||
<label>{{ option.label }}<span v-if="option.required" class="required-marker">*</span></label>
|
||||
<RichSelectBox v-if="option.type === 'icon'"
|
||||
v-model="paymentMethodConfigurations[optionsMethod.slug][option.name]"
|
||||
:options="PAYMENT_METHOD_ICONS"
|
||||
placeholder="Symbol wählen…"/>
|
||||
<TextEditor v-else-if="option.type === 'richtext'"
|
||||
v-model="paymentMethodConfigurations[optionsMethod.slug][option.name]"/>
|
||||
<input v-else type="text"
|
||||
v-model="paymentMethodConfigurations[optionsMethod.slug][option.name]"
|
||||
class="width-full"/>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.payment-method-row {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.payment-method-config-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.payment-method-config-row label {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
color: #374151;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.required-marker {
|
||||
color: #ef4444;
|
||||
margin-left: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,10 +15,13 @@ const selectedMethod = computed(() =>
|
||||
|
||||
const participantSchema = computed(() => selectedMethod.value?.participantOptionsSchema ?? [])
|
||||
|
||||
// Font-Awesome-Icon je Zahlungsart (Fallback: generische Karte).
|
||||
function iconFor(slug) {
|
||||
if (slug === 'PAYMENT_ACCOUNT_TRANSACTION') return 'building-columns'
|
||||
if (slug === 'PAYMENT_NOT_DEFINED') return 'money-bill-wave'
|
||||
// Font-Awesome-Icon je Zahlungsart: konfiguriertes Symbol hat Vorrang, sonst Slug-Default
|
||||
// (Fallback: generische Karte).
|
||||
function iconFor(method) {
|
||||
const configured = method.configuration?.icon
|
||||
if (configured) return configured
|
||||
if (method.slug === 'PAYMENT_ACCOUNT_TRANSACTION') return 'building-columns'
|
||||
if (method.slug === 'PAYMENT_NOT_DEFINED') return 'money-bill-wave'
|
||||
return 'credit-card'
|
||||
}
|
||||
|
||||
@@ -60,7 +63,7 @@ const canProceed = computed(() => {
|
||||
@keydown.space.prevent="selectMethod(method.slug)"
|
||||
>
|
||||
<div class="pay-card__badge">
|
||||
<Icon :name="iconFor(method.slug)"/>
|
||||
<Icon :name="iconFor(method)"/>
|
||||
</div>
|
||||
<div class="pay-card__body">
|
||||
<h4 class="pay-card__title">{{ method.name }}</h4>
|
||||
|
||||
Reference in New Issue
Block a user