Added IBAN Saving, Automatic Saving, added Mitglieder
This commit is contained in:
148
frontend/components/BankAccountAssignInput.vue
Normal file
148
frontend/components/BankAccountAssignInput.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
const toast = useToast()
|
||||
|
||||
const accounts = ref([])
|
||||
const ibanSearch = ref("")
|
||||
const showCreate = ref(false)
|
||||
|
||||
const createPayload = ref({
|
||||
iban: "",
|
||||
bic: "",
|
||||
bankName: "",
|
||||
description: ""
|
||||
})
|
||||
|
||||
const normalizeIban = (value) => String(value || "").replace(/\s+/g, "").toUpperCase()
|
||||
|
||||
const loadAccounts = async () => {
|
||||
accounts.value = await useEntities("entitybankaccounts").select()
|
||||
}
|
||||
|
||||
const assignedIds = computed(() => {
|
||||
return Array.isArray(props.modelValue) ? props.modelValue : []
|
||||
})
|
||||
|
||||
const assignedAccounts = computed(() => {
|
||||
return accounts.value.filter((a) => assignedIds.value.includes(a.id))
|
||||
})
|
||||
|
||||
const updateAssigned = (ids) => {
|
||||
emit("update:modelValue", ids)
|
||||
}
|
||||
|
||||
const assignByIban = async () => {
|
||||
const search = normalizeIban(ibanSearch.value)
|
||||
if (!search) return
|
||||
|
||||
const match = accounts.value.find((a) => normalizeIban(a.iban) === search)
|
||||
if (!match) {
|
||||
toast.add({ title: "Kein Bankkonto mit dieser IBAN gefunden.", color: "rose" })
|
||||
return
|
||||
}
|
||||
|
||||
if (assignedIds.value.includes(match.id)) {
|
||||
toast.add({ title: "Dieses Bankkonto ist bereits zugewiesen.", color: "amber" })
|
||||
return
|
||||
}
|
||||
|
||||
updateAssigned([...assignedIds.value, match.id])
|
||||
ibanSearch.value = ""
|
||||
}
|
||||
|
||||
const removeAssigned = (id) => {
|
||||
updateAssigned(assignedIds.value.filter((i) => i !== id))
|
||||
}
|
||||
|
||||
const createAndAssign = async () => {
|
||||
if (!createPayload.value.iban || !createPayload.value.bic || !createPayload.value.bankName) {
|
||||
toast.add({ title: "IBAN, BIC und Bankinstitut sind Pflichtfelder.", color: "rose" })
|
||||
return
|
||||
}
|
||||
|
||||
const created = await useEntities("entitybankaccounts").create(createPayload.value, true)
|
||||
await loadAccounts()
|
||||
updateAssigned([...assignedIds.value, created.id])
|
||||
createPayload.value = { iban: "", bic: "", bankName: "", description: "" }
|
||||
showCreate.value = false
|
||||
}
|
||||
|
||||
loadAccounts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
<div class="flex flex-wrap gap-2" v-if="assignedAccounts.length > 0">
|
||||
<UBadge
|
||||
v-for="account in assignedAccounts"
|
||||
:key="account.id"
|
||||
color="primary"
|
||||
variant="subtle"
|
||||
>
|
||||
{{ account.displayLabel || account.iban }}
|
||||
<UButton
|
||||
v-if="!disabled"
|
||||
variant="ghost"
|
||||
color="gray"
|
||||
size="2xs"
|
||||
icon="i-heroicons-x-mark"
|
||||
class="ml-1"
|
||||
@click="removeAssigned(account.id)"
|
||||
/>
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<InputGroup class="w-full">
|
||||
<UInput
|
||||
v-model="ibanSearch"
|
||||
class="flex-auto"
|
||||
placeholder="IBAN eingeben und zuweisen"
|
||||
:disabled="disabled"
|
||||
@keydown.enter.prevent="assignByIban"
|
||||
/>
|
||||
<UButton :disabled="disabled" @click="assignByIban">
|
||||
Zuweisen
|
||||
</UButton>
|
||||
<UButton :disabled="disabled" color="gray" variant="outline" @click="showCreate = true">
|
||||
Neu
|
||||
</UButton>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
<UModal v-model="showCreate">
|
||||
<UCard>
|
||||
<template #header>Neue Bankverbindung erstellen</template>
|
||||
<div class="space-y-3">
|
||||
<UFormGroup label="IBAN">
|
||||
<UInput v-model="createPayload.iban" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="BIC">
|
||||
<UInput v-model="createPayload.bic" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Bankinstitut">
|
||||
<UInput v-model="createPayload.bankName" />
|
||||
</UFormGroup>
|
||||
<UFormGroup label="Beschreibung (optional)">
|
||||
<UInput v-model="createPayload.description" />
|
||||
</UFormGroup>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton color="gray" variant="outline" @click="showCreate = false">Abbrechen</UButton>
|
||||
<UButton @click="createAndAssign">Erstellen und zuweisen</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</template>
|
||||
65
frontend/components/columnRenderings/bankAccounts.vue
Normal file
65
frontend/components/columnRenderings/bankAccounts.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
|
||||
const accounts = ref([])
|
||||
const showFullIban = ref(false)
|
||||
|
||||
const formatIban = (iban) => {
|
||||
const cleaned = String(iban || "").replace(/\s+/g, "").toUpperCase()
|
||||
if (!cleaned) return ""
|
||||
return cleaned.match(/.{1,4}/g)?.join(" ") || cleaned
|
||||
}
|
||||
|
||||
const maskIban = (iban) => {
|
||||
const cleaned = String(iban || "").replace(/\s+/g, "").toUpperCase()
|
||||
if (!cleaned) return ""
|
||||
if (cleaned.length <= 8) return cleaned
|
||||
return `${cleaned.slice(0, 4)} **** **** ${cleaned.slice(-4)}`
|
||||
}
|
||||
|
||||
const bankAccounts = computed(() => {
|
||||
const ids = Array.isArray(props.row?.infoData?.bankAccountIds) ? props.row.infoData.bankAccountIds : []
|
||||
if (!ids.length) return []
|
||||
return accounts.value
|
||||
.filter((a) => ids.includes(a.id))
|
||||
.map((a) => {
|
||||
const iban = formatIban(a.iban)
|
||||
const ibanDisplay = showFullIban.value ? iban : maskIban(a.iban)
|
||||
const parts = [ibanDisplay]
|
||||
if (a.bankName) parts.push(a.bankName)
|
||||
if (a.description) parts.push(`(${a.description})`)
|
||||
return {
|
||||
id: a.id,
|
||||
label: parts.filter(Boolean).join(" | ")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const setup = async () => {
|
||||
accounts.value = await useEntities("entitybankaccounts").select()
|
||||
}
|
||||
|
||||
setup()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div v-if="bankAccounts.length > 0" class="flex">
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
size="2xs"
|
||||
:icon="showFullIban ? 'i-heroicons-eye-slash' : 'i-heroicons-eye'"
|
||||
@click="showFullIban = !showFullIban"
|
||||
/>
|
||||
</div>
|
||||
<span v-for="account in bankAccounts" :key="account.id">{{ account.label }}</span>
|
||||
<span v-if="bankAccounts.length === 0">-</span>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user