Added Frontend

This commit is contained in:
2026-01-06 12:09:31 +01:00
250 changed files with 29602 additions and 0 deletions

View File

@@ -0,0 +1,223 @@
<script setup>
const dataStore = useDataStore()
const profileStore = useProfileStore()
const route = useRoute()
const router = useRouter()
const url = useRequestURL()
const supabase = useSupabaseClient()
const toast = useToast()
const showAddBankRequisition = ref(false)
const bicBankToAdd = ref("")
const bankData = ref({})
const showAlert = ref(false)
const reqData = ref({})
const bankaccounts = ref([])
const showReqData = ref(false)
const setupPage = async () => {
if(route.query.ref) {
reqData.value = await useFunctions().useBankingListRequisitions(route.query.ref)
if(reqData.value.accounts.length > 0){
showReqData.value = true
}
}
bankaccounts.value = await useEntities("bankaccounts").select()
}
const checkBIC = async () => {
bankData.value = await useFunctions().useBankingCheckInstitutions(bicBankToAdd.value)
showAlert.value = true
}
const generateLink = async (bankId) => {
try {
const link = await useFunctions().useBankingGenerateLink(bankId || bankData.value.id)
await navigateTo(link, {
open: {
target: "_blank"
}
})
} catch (error) {
console.log(error)
}
}
const addAccount = async (account) => {
let accountData = {
accountId: account.id,
ownerName: account.owner_name,
iban: account.iban,
tenant: profileStore.currentTenant,
bankId: account.institution_id
}
const {data,error} = await supabase.from("bankaccounts").insert(accountData).select()
if(error) {
toast.add({title: "Es gab einen Fehler bei hinzufügen des Accounts", color:"rose"})
} else if(data) {
toast.add({title: "Account erfolgreich hinzugefügt"})
}
}
const updateAccount = async (account) => {
let bankaccountId = bankaccounts.value.find(i => i.iban === account.iban).id
const res = await useEntities("bankaccounts").update(bankaccountId, {accountId: account.id, expired: false})
if(!res) {
console.log(error)
toast.add({title: "Es gab einen Fehler bei aktualisieren des Accounts", color:"rose"})
} else {
toast.add({title: "Account erfolgreich aktualisiert"})
reqData.value = null
setupPage()
}
}
setupPage()
</script>
<template>
<UDashboardNavbar title="Bankkonten">
<template #right>
<UButton
@click="showAddBankRequisition = true"
>
+ Bankverbindung
</UButton>
<USlideover
v-model="showAddBankRequisition"
>
<UCard
class="h-full"
>
<template #header>
<p>Bankverbindung hinzufügen</p>
</template>
<UFormGroup
label="BIC:"
class="flex-auto"
>
<InputGroup class="w-full">
<UInput
v-model="bicBankToAdd"
class="flex-auto"
@keydown.enter="checkBIC"
/>
<UButton
@click="checkBIC"
>
Check
</UButton>
</InputGroup>
</UFormGroup>
<UAlert
v-if="showAlert && bankData.id && bankData.countries.includes('DE')"
title="Bank gefunden"
icon="i-heroicons-check-circle"
color="primary"
variant="outline"
class="mt-3"
:actions="[{ variant: 'solid', color: 'primary', label: 'Verbinden',click: generateLink }]"
/>
<UAlert
v-else-if="showAlert && !bankData.id"
title="Bank nicht gefunden"
icon="i-heroicons-x-circle"
color="rose"
variant="outline"
class="mt-3"
/>
</UCard>
</USlideover>
</template>
</UDashboardNavbar>
<UModal v-model="showReqData">
<UCard>
<template #header>
Verfügbare Bankkonten
</template>
<div
v-for="account in reqData.accounts"
class="p-2 m-3 flex justify-between"
>
{{account.iban}} - {{account.owner_name}}
<UButton
@click="addAccount(account)"
v-if="!bankaccounts.find(i => i.iban === account.iban)"
>
Hinzufügen
</UButton>
<UButton
@click="updateAccount(account)"
v-else
>
Aktualisieren
</UButton>
</div>
</UCard>
</UModal>
<!-- <UButton @click="setupPage">Setup</UButton>
<div v-if="route.query.reqId">
{{reqData}}
</div>-->
<UTable
:rows="bankaccounts"
:columns="[
{
key: 'expired',
label: 'Aktiv'
},{
key: 'iban',
label: 'IBAN'
},{
key: 'bankId',
label: 'Bank'
},{
key: 'ownerName',
label: 'Kontoinhaber'
},{
key: 'balance',
label: 'Saldo'
},
]"
>
<template #expired-data="{row}">
<span v-if="row.expired" class="text-rose-600">Ausgelaufen</span>
<span v-else class="text-primary">Aktiv</span>
<UButton
v-if="row.expired"
variant="outline"
class="ml-2"
@click="generateLink(row.bankId)"
>Aktualisieren</UButton>
</template>
<template #balance-data="{row}">
{{row.balance ? row.balance.toFixed(2).replace(".",",") + ' €' : '-'}}
</template>
<template #iban-data="{row}">
{{row.iban.match(/.{1,5}/g).join(" ")}}
</template>
</UTable>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,137 @@
<script setup lang="ts">
const route = useRoute()
const mode = route.params.mode
const itemInfo = ref({})
const setup = async () => {
if(mode === "create") {
} else {
itemInfo.value = await useNuxtApp().$api(`/api/email/accounts/${route.params.id}`)
}
}
setup()
const createAccount = async () => {
const res = await useNuxtApp().$api(`/api/email/accounts`, {
method: "POST",
body: itemInfo.value,
})
if(res.success) {
navigateTo("/settings/emailaccounts")
}
}
const saveAccount = async () => {
const res = await useNuxtApp().$api(`/api/email/accounts/${route.params.id}`, {
method: "POST",
body: itemInfo.value,
})
if(res.success) {
navigateTo("/settings/emailaccounts")
}
}
</script>
<template>
<UDashboardNavbar :title="`E-Mail Konto ${mode === 'create' ? 'erstellen' : 'bearbeiten'}`">
<template #right>
<UButton
v-if="mode === 'create'"
@click="createAccount"
>
Erstellen
</UButton>
<UButton
v-if="mode === 'edit'"
@click="saveAccount"
>
Speichern
</UButton>
</template>
</UDashboardNavbar>
<UForm class="w-2/3 mx-auto mt-5">
<UFormGroup
label="E-Mail Adresse"
>
<UInput
v-model="itemInfo.email"
/>
</UFormGroup>
<UFormGroup
label="Passwort"
>
<UInput
type="password"
v-model="itemInfo.password"
placeholder="********"
/>
</UFormGroup>
<UDivider> IMAP </UDivider>
<UFormGroup
label="IMAP Host"
>
<UInput
v-model="itemInfo.imap_host"
/>
</UFormGroup>
<UFormGroup
label="IMAP Port"
>
<UInput
type="number"
v-model="itemInfo.imap_port"
/>
</UFormGroup>
<UFormGroup
label="IMAP SSL"
>
<UToggle
v-model="itemInfo.imap_ssl"
/>
</UFormGroup>
<UDivider> SMTP </UDivider>
<UFormGroup
label="SMTP Host"
>
<UInput
v-model="itemInfo.smtp_host"
/>
</UFormGroup>
<UFormGroup
label="SMTP Port"
>
<UInput
type="number"
v-model="itemInfo.smtp_port"
/>
</UFormGroup>
<UFormGroup
label="SMTP SSL"
>
<UToggle
v-model="itemInfo.smtp_ssl"
/>
</UFormGroup>
</UForm>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,99 @@
<script setup>
import axios from "axios"
const supabase = useSupabaseClient()
const dataStore = useDataStore()
const profileStore = useProfileStore()
const createEMailAddress = ref("")
const createEMailType = ref("imap")
const showEmailAddressModal = ref(false)
const items = ref([])
const setupPage = async () => {
items.value = await useNuxtApp().$api("/api/email/accounts")
}
const createAccount = async () => {
showEmailAddressModal.value = false
}
setupPage()
const templateColumns = [
{
key: "email",
label: "E-Mail Adresse:"
},
]
const selectedColumns = ref(templateColumns)
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
</script>
<template>
<UModal
v-model="showEmailAddressModal"
>
<UCard>
<template #header>
E-Mail Adresse
</template>
<!-- <UFormGroup
label="E-Mail Adresse:"
>
</UFormGroup>-->
<UInput
v-model="createEMailAddress"
/>
<!-- <UFormGroup
label="Account Typ:"
>
<USelectMenu
:options="[{key: 'imap',label:'IMAP'}]"
option-attribute="label"
value-attribute="key"
v-model="createEMailType"
/>
</UFormGroup>-->
<template #footer>
<UButton
@click="createAccount"
>
Erstellen
</UButton>
</template>
</UCard>
</UModal>
<UDashboardNavbar title="E-Mail Konten">
<template #right>
<UTooltip title="In der Beta nicht verfügbar">
<UButton
@click="navigateTo('/settings/emailaccounts/create')"
>
+ E-Mail Konto
</UButton>
</UTooltip>
</template>
</UDashboardNavbar>
<UTable
:rows="items"
:columns="columns"
class="w-full"
@select="(i) => navigateTo(`/settings/emailaccounts/edit/${i.id}`)"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine E-Mail Konten anzuzeigen' }"
>
</UTable>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,66 @@
<script setup>
import axios from "axios"
const {ownTenant} = storeToRefs(useDataStore())
const setupPrinter = async () => {
console.log(ownTenant.value.labelPrinterIp)
let printerUri = `http://${ownTenant.value.labelPrinterIp}/pstprnt`
labelPrinterURI.value = printerUri
console.log(printerUri)
}
/*const printLabel = async () => {
axios
.post(labelPrinterURI.value, `^XA^FO10,20^BCN,100^FD${}^XZ` )
.then(console.log)
.catch(console.log)
}*/
const labelPrinterURI = ref("")
</script>
<template>
<UPage>
<UCard>
<template #header>
Etikettendrucker
</template>
<UFormGroup
label="IP-Adresse:"
>
<UInput
v-model="labelPrinterURI"
/>
</UFormGroup>
<UButton
@click="setupPrinter"
>
Drucker Setup
</UButton>
<UButton
@click="printLabel"
>
Druck
</UButton>
</UCard>
<!-- <UCard>
<template #header>
A4 Drucker
</template>
</UCard>-->
</UPage>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,89 @@
<script setup>
const dataStore = useDataStore()
const profileStore = useProfileStore()
const router = useRouter()
const items = [{
label: 'Profil',
},{
label: 'Projekte',
content: 'This is the content shown for Tab1'
}, {
label: 'E-Mail',
content: 'And, this is the content for Tab2'
}, {
label: 'Dokumente'
}]
const colorMode = useColorMode()
const isLight = computed({
get() {
return colorMode.value !== 'dark'
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
</script>
<template>
<UDashboardNavbar title="Einstellungen">
</UDashboardNavbar>
<UTabs
:items="items"
class="h-100 p-5"
>
<template #item="{item}">
<UCard class="mt-5">
<div v-if="item.label === 'Profil'">
<UDivider
class="my-3"
label="Profil"
/>
<InputGroup>
<UButton
:icon="!isLight ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid'"
color="white"
variant="outline"
aria-label="Theme"
@click="isLight = !isLight"
/>
</InputGroup>
</div>
<div v-else-if="item.label === 'Projekte'">
<UDivider
label="Phasenvorlagen"
/>
</div>
<div v-else-if="item.label === 'Dokumente'">
<UDivider
label="Tags"
class="mb-3"
/>
<InputGroup>
<UBadge
v-for="tag in profileStore.ownTenant.tags.documents"
>
{{tag}}
</UBadge>
</InputGroup>
{{profileStore.ownTenant.tags}}
</div>
</UCard>
</template>
</UTabs>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,112 @@
<script setup>
const auth = useAuthStore()
const resources = {
customers: {
label: "Kunden"
},
vendors: {
label: "Lieferanten"
},
products: {
label: "Artikel"
},
spaces: {
label: "Lagerplätze"
},
invoices: {
label: "Rechnungen"
},
quotes: {
label: "Angebote"
},
inventoryitems: {
label: "Inventarartikel"
},
projects: {
label: "Projekte"
},
confirmationOrders: {
label: "Auftragsbestätigungen"
},
deliveryNotes: {
label: "Lieferscheine"
},
costcentres: {
label: "Kostenstellen"
}
}
const numberRanges = ref(auth.activeTenantData.numberRanges)
const updateNumberRanges = async (range) => {
const res = await useNuxtApp().$api(`/api/tenant/numberrange/${range}`,{
method: "PUT",
body: {
numberRange: numberRanges.value[range]
}
})
console.log(res)
}
</script>
<template>
<UDashboardNavbar
title="Nummernkreise bearbeiten"
>
</UDashboardNavbar>
<UDashboardToolbar>
<UAlert
title="Änderungen an diesen Werten betreffen nur neu Erstellte Einträge."
color="rose"
variant="outline"
icon="i-heroicons-exclamation-triangle"
/>
</UDashboardToolbar>
<table
class="m-3"
>
<tr class="text-left">
<th>Typ</th>
<th>Prefix</th>
<th>Nächste Nummer</th>
<th>Suffix</th>
</tr>
<tr
v-for="key in Object.keys(resources)"
>
<td>{{resources[key].label}}</td>
<td>
<UInput
v-model="numberRanges[key].prefix"
@change="updateNumberRanges(key)"
/>
</td>
<td>
<UInput
v-model="numberRanges[key].nextNumber"
@change="updateNumberRanges(key)"
type="number"
step="1"
/>
</td>
<td>
<UInput
v-model="numberRanges[key].suffix"
@change="updateNumberRanges(key)"
/>
</td>
</tr>
</table>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,23 @@
<script setup>
const dataStore = useDataStore()
const setupPage = async () => {
console.log()
}
</script>
<template>
<UDashboardNavbar
title="Eigene Felder"
>
</UDashboardNavbar>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,169 @@
<script setup>
const auth = useAuthStore()
const itemInfo = ref({
features: {},
businessInfo: {},
projectTypes: []
})
const setupPage = async () => {
itemInfo.value = auth.activeTenantData
console.log(itemInfo.value)
}
const features = ref(auth.activeTenantData.features)
const businessInfo = ref(auth.activeTenantData.businessInfo)
const updateTenant = async (newData) => {
const res = await useNuxtApp().$api(`/api/tenant/other/${auth.activeTenant}`, {
method: "PUT",
body: {
data: newData,
}
})
}
setupPage()
</script>
<template>
<UDashboardNavbar title="Firmeneinstellungen">
</UDashboardNavbar>
<UTabs
class="p-5"
:items="[
{
label: 'Dokubox'
},{
label: 'Rechnung & Kontakt'
}
]"
>
<template #item="{item}">
<div v-if="item.label === 'Dokubox'">
<UAlert
class="mt-5"
title="DOKUBOX"
>
<template #description>
<p>Die Dokubox ist eine E-Mail Inbox um deine Anhänge direkt als Dokumente zu importieren. Leite Deine E-Mails einfach an die folgende E-Mail Adresse weiter, diese ist für dein Unternehmen einzigartig. Die E-Mails werden dann alle 5 min abgerufen.</p>
<br><br>
<a
v-if="itemInfo.id"
:href="`mailto:${itemInfo.dokuboxkey}@fedeo-dokubox.de`"
>
{{itemInfo.dokuboxkey}}@fedeo-dokubox.de
</a>
</template>
</UAlert>
</div>
<div v-if="item.label === 'Rechnung & Kontakt'">
<UCard class="mt-5">
<UForm class="w-1/2">
<UFormGroup
label="Firmenname:"
>
<UInput v-model="businessInfo.name"/>
</UFormGroup>
<UFormGroup
label="Straße + Hausnummer:"
>
<UInput v-model="businessInfo.street"/>
</UFormGroup>
<UFormGroup
label="PLZ + Ort"
class="w-full"
>
<InputGroup class="w-full">
<UInput v-model="businessInfo.zip"/>
<UInput v-model="businessInfo.city" class="flex-auto"/>
</InputGroup>
</UFormGroup>
<UButton
class="mt-3"
@click="updateTenant({businessInfo: businessInfo})"
>
Speichern
</UButton>
</UForm>
</UCard>
</div>
<div v-else-if="item.label === 'Funktionen'">
<UCard class="mt-5">
<UAlert
title="Funktionen ausblenden"
description="Nur Funktionen mit gesetztem Haken sind im Unternehmen verfügbar. Diese Einstellungen gelten für alle Mitarbeiter und sind unabhängig von Berechtigungen."
color="rose"
variant="outline"
class="mb-5"
/>
<UCheckbox
label="Kalendar"
v-model="features.calendar"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Kontakte"
v-model="features.contacts"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Plantafel"
v-model="features.planningBoard"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Zeiterfassung"
v-model="features.timeTracking"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Anwesenheiten"
v-model="features.workingTimeTracking"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Lager"
v-model="features.inventory"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Fahrzeuge"
v-model="features.vehicles"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Buchhaltung"
v-model="features.accounting"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Projekte"
v-model="features.projects"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Verträge"
v-model="features.contracts"
@change="updateTenant({features: features})"
/>
<UCheckbox
label="Objekte"
v-model="features.objects"
@change="updateTenant({features: features})"
/>
</UCard>
</div>
</template>
</UTabs>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,220 @@
<script setup>
const dataStore = useDataStore()
defineShortcuts({
'+': () => {
editTemplateModalOpen.value = true
}
})
const editTemplateModalOpen = ref(false)
const itemInfo = ref({})
const texttemplates = ref([])
const loading = ref(true)
const setup = async () => {
texttemplates.value = (await useEntities("texttemplates").select()).filter(i => !i.archived)
loading.value = false
}
setup()
const expand = ref({
openedRows: [],
row: {}
})
</script>
<template>
<UDashboardNavbar
title="Text Vorlagen"
>
<template #right>
<UButton
@click="editTemplateModalOpen = true, itemInfo = {}"
>
+ Erstellen
</UButton>
</template>
</UDashboardNavbar>
<UDashboardPanelContent>
<UCard class="mx-5">
<template #header>
Variablen
</template>
<table>
<tr>
<th class="text-left">Variable</th>
<th class="text-left">Beschreibung</th>
</tr>
<tr>
<td>vorname</td>
<td>Vorname</td>
</tr>
<tr>
<td>nachname</td>
<td>Nachname</td>
</tr>
<tr>
<td>zahlungsziel_in_tagen</td>
<td>Zahlungsziel in Tagen</td>
</tr>
<tr>
<td>lohnkosten</td>
<td>Lohnkosten Verkauf</td>
</tr>
<tr>
<td>titel</td>
<td>Titel</td>
</tr>
<tr>
<td>anrede</td>
<td>Anrede</td>
</tr>
</table>
</UCard>
<UTable
class="mt-3"
:rows="texttemplates"
:loading-state="{ icon: 'i-heroicons-arrow-path-20-solid', label: 'Loading...' }"
:loading="loading"
v-model:expand="expand" :empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Textvorlagen anzuzeigen' }"
:columns="[{key:'name',label:'Name'},{key:'documentType',label:'Dokumententyp'},{key:'default',label:'Standard'},{key:'pos',label:'Position'}]"
>
<template #documentType-data="{row}">
{{dataStore.documentTypesForCreation[row.documentType].label}}
</template>
<template #default-data="{row}">
{{row.default ? "Ja" : "Nein"}}
</template>
<template #pos-data="{row}">
<span v-if="row.pos === 'startText'">Einleitung</span>
<span v-else-if="row.pos === 'endText'">Endtext</span>
</template>
<template #expand="{ row }">
<div class="p-4">
<p class="text-2xl">{{dataStore.documentTypesForCreation[row.documentType].label}}</p>
<p class="text-xl mt-3">{{row.pos === 'startText' ? 'Einleitung' : 'Ende'}}</p>
<p class="text-justify mt-3">{{row.text}}</p>
<UButton
class="mt-3 mr-3"
@click="itemInfo = row;
editTemplateModalOpen = true"
variant="outline"
>Bearbeiten</UButton>
<ButtonWithConfirm
color="rose"
variant="outline"
@confirmed="dataStore.updateItem('texttemplates',{id: row.id,archived: true}),
setup"
>
<template #button>
Archivieren
</template>
<template #header>
<span class="text-md dark:text-whitetext-black font-bold">Archivieren bestätigen</span>
</template>
Möchten Sie diesen Ausgangsbeleg wirklich archivieren?
</ButtonWithConfirm>
</div>
</template>
</UTable>
<!-- <div class="w-3/4 mx-auto mt-5">
<UCard
v-for="template in dataStore.texttemplates"
class="mb-3"
>
<p class="text-2xl">{{dataStore.documentTypesForCreation[template.documentType].label}}</p>
<p class="text-xl">{{template.pos === 'startText' ? 'Einleitung' : 'Ende'}}</p>
<p class="text-justify">{{template.text}}</p>
<UButton
@click="itemInfo = template;
editTemplateModalOpen = true"
icon="i-heroicons-pencil-solid"
variant="outline"
/>
</UCard>
</div>-->
</UDashboardPanelContent>
<UModal
v-model="editTemplateModalOpen"
>
<UCard class="h-full">
<template #header>
{{itemInfo.id ? 'Vorlage bearbeiten' : 'Vorlage erstellen'}}
</template>
<UForm class="h-full">
<UFormGroup
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormGroup>
<UFormGroup
label="Dokumententyp:"
>
<USelectMenu
:options="Object.keys(dataStore.documentTypesForCreation).filter(i => i !== 'serialInvoices').map(i => {
return {
label: dataStore.documentTypesForCreation[i].label,
key: i
}
})"
option-attribute="label"
value-attribute="key"
v-model="itemInfo.documentType"
/>
</UFormGroup>
<UFormGroup
label="Position:"
>
<USelectMenu
:options="[{label:'Einleitung',key: 'startText'},{label:'Ende',key: 'endText'}]"
option-attribute="label"
value-attribute="key"
v-model="itemInfo.pos"
/>
</UFormGroup>
<UFormGroup
label="Text:"
>
<UTextarea
v-model="itemInfo.text"
/>
</UFormGroup>
</UForm>
<!-- TODO: Update und Create -->
<template #footer>
<UButton
@click="dataStore.createNewItem('texttemplates',itemInfo);
editTemplateModalOpen = false"
v-if="!itemInfo.id"
>Erstellen</UButton>
<UButton
@click="dataStore.updateItem('texttemplates',itemInfo);
editTemplateModalOpen = false"
v-if="itemInfo.id"
>Speichern</UButton>
</template>
</UCard>
</UModal>
</template>
<style scoped>
</style>