2. Zwischenstand
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 15s
Build and Push Docker Images / build-frontend (push) Successful in 2m43s

This commit is contained in:
2026-03-21 22:56:56 +01:00
parent 68b2cbb0ee
commit 03bcc1a939
56 changed files with 1289 additions and 1302 deletions

View File

@@ -281,30 +281,30 @@ const moveFile = async () => {
<USeparator class="my-3" label="Datei zuweisen"/> <USeparator class="my-3" label="Datei zuweisen"/>
<UFormGroup <UFormField
label="Resource auswählen" label="Resource auswählen"
> >
<USelectMenu <USelectMenu
:options="resourceOptions" :items="resourceOptions"
v-model="resourceToAssign" v-model="resourceToAssign"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
@change="getItemsBySelectedResource" @change="getItemsBySelectedResource"
> >
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Eintrag auswählen:" label="Eintrag auswählen:"
> >
<USelectMenu <USelectMenu
:options="itemOptions" :items="itemOptions"
v-model="idToAssign" v-model="idToAssign"
:option-attribute="resourceOptions.find(i => i.value === resourceToAssign)? resourceOptions.find(i => i.value === resourceToAssign).optionAttr : 'name'" :label-key="resourceOptions.find(i => i.value === resourceToAssign)? resourceOptions.find(i => i.value === resourceToAssign).optionAttr : 'name'"
value-attribute="id" value-key="id"
@change="updateDocumentAssignment" @change="updateDocumentAssignment"
></USelectMenu> ></USelectMenu>
</UFormGroup> </UFormField>
@@ -314,9 +314,9 @@ const moveFile = async () => {
<USelectMenu <USelectMenu
class="flex-auto" class="flex-auto"
v-model="folderToMoveTo" v-model="folderToMoveTo"
value-attribute="id" value-key="id"
option-attribute="name" label-key="name"
:options="folders" :items="folders"
/> />
<UButton <UButton
@click="moveFile" @click="moveFile"
@@ -331,9 +331,9 @@ const moveFile = async () => {
<USelectMenu <USelectMenu
class="flex-auto" class="flex-auto"
v-model="props.documentData.type" v-model="props.documentData.type"
value-attribute="id" value-key="id"
option-attribute="name" label-key="name"
:options="filetypes" :items="filetypes"
@change="updateDocument" @change="updateDocument"
/> />
</InputGroup> </InputGroup>
@@ -343,9 +343,9 @@ const moveFile = async () => {
<USelectMenu <USelectMenu
class="flex-auto" class="flex-auto"
v-model="props.documentData.documentbox" v-model="props.documentData.documentbox"
value-attribute="id" value-key="id"
option-attribute="key" label-key="key"
:options="documentboxes" :items="documentboxes"
@change="updateDocument" @change="updateDocument"
/> />
</InputGroup> </InputGroup>

View File

@@ -107,7 +107,7 @@ const fileNames = computed(() => {
</div> </div>
</template> </template>
<UFormGroup <UFormField
label="Datei:" label="Datei:"
:help="selectedFiles.length > 0 ? `${selectedFiles.length} Datei(en) ausgewählt` : 'Ziehen Sie Dateien hierher oder klicken Sie'" :help="selectedFiles.length > 0 ? `${selectedFiles.length} Datei(en) ausgewählt` : 'Ziehen Sie Dateien hierher oder klicken Sie'"
> >
@@ -123,9 +123,9 @@ const fileNames = computed(() => {
<div v-if="selectedFiles.length > 0" class="mt-2 text-sm text-gray-500"> <div v-if="selectedFiles.length > 0" class="mt-2 text-sm text-gray-500">
Ausgewählt: <span class="font-medium text-gray-700 dark:text-gray-300">{{ fileNames }}</span> Ausgewählt: <span class="font-medium text-gray-700 dark:text-gray-300">{{ fileNames }}</span>
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Typ:" label="Typ:"
class="mt-3" class="mt-3"
> >
@@ -143,7 +143,7 @@ const fileNames = computed(() => {
<span v-else>Kein Typ ausgewählt</span> <span v-else>Kein Typ ausgewählt</span>
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<template #footer> <template #footer>
<UButton <UButton

View File

@@ -155,15 +155,15 @@ const filteredRows = computed(() => {
<USelectMenu <USelectMenu
v-model="selectedColumns" v-model="selectedColumns"
icon="i-heroicons-adjustments-horizontal-solid" icon="i-heroicons-adjustments-horizontal-solid"
:options="dataType.templateColumns.filter(i => !i.disabledInTable)" :items="dataType.templateColumns.filter(i => !i.disabledInTable)"
multiple multiple
class="hidden lg:block" class="hidden lg:block"
by="key" by="key"
:color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'" :color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'"
:ui-menu="{ width: 'min-w-max' }" :content="{ width: 'min-w-max' }"
@change="tempStore.modifyColumns(type,selectedColumns)" @change="tempStore.modifyColumns(type,selectedColumns)"
> >
<template> <template #default>
Spalten Spalten
</template> </template>
</USelectMenu> </USelectMenu>
@@ -172,11 +172,11 @@ const filteredRows = computed(() => {
icon="i-heroicons-adjustments-horizontal-solid" icon="i-heroicons-adjustments-horizontal-solid"
multiple multiple
v-model="selectedFilters" v-model="selectedFilters"
:options="selectableFilters" :items="selectableFilters"
:color="selectedFilters.length > 0 ? 'primary' : 'white'" :color="selectedFilters.length > 0 ? 'primary' : 'white'"
:ui-menu="{ width: 'min-w-max' }" :content="{ width: 'min-w-max' }"
> >
<template #label> <template #default>
Filter Filter
</template> </template>
</USelectMenu> </USelectMenu>

View File

@@ -28,14 +28,16 @@ defineShortcuts({
router.back() router.back()
}, },
'arrowleft': () => { 'arrowleft': () => {
if(openTab.value > 0){ const currentIndex = Number(openTab.value)
openTab.value -= 1 if(currentIndex > 0){
openTab.value = String(currentIndex - 1)
router.push(`${router.currentRoute.value.path}?tabIndex=${openTab.value}`) router.push(`${router.currentRoute.value.path}?tabIndex=${openTab.value}`)
} }
}, },
'arrowright': () => { 'arrowright': () => {
if(openTab.value < dataType.showTabs.length - 1) { const currentIndex = Number(openTab.value)
openTab.value += 1 if(currentIndex < dataType.showTabs.length - 1) {
openTab.value = String(currentIndex + 1)
router.push(`${router.currentRoute.value.path}?tabIndex=${openTab.value}`) router.push(`${router.currentRoute.value.path}?tabIndex=${openTab.value}`)
} }
}, },
@@ -51,7 +53,7 @@ const auth = useAuthStore()
const dataType = dataStore.dataTypes[type] const dataType = dataStore.dataTypes[type]
const openTab = ref(route.query.tabIndex || 0) const openTab = ref(String(route.query.tabIndex || 0))
@@ -97,7 +99,8 @@ const getAvailableQueryStringData = (keys) => {
} }
const onTabChange = (index) => { const onTabChange = (index) => {
router.push(`${router.currentRoute.value.path}?tabIndex=${index}`) openTab.value = String(index)
router.push(`${router.currentRoute.value.path}?tabIndex=${openTab.value}`)
} }
const changePinned = async () => { const changePinned = async () => {
@@ -255,9 +258,9 @@ const openCustomerInventoryLabelPrint = () => {
v-if="props.item.id && platform !== 'mobile'" v-if="props.item.id && platform !== 'mobile'"
class="p-5" class="p-5"
v-model="openTab" v-model="openTab"
@change="onTabChange" @update:model-value="onTabChange"
> >
<template #item="{item:tab}"> <template #content="{item:tab}">
<div v-if="tab.label === 'Informationen'" class="flex flex-row"> <div v-if="tab.label === 'Informationen'" class="flex flex-row">
<EntityShowSubInformation <EntityShowSubInformation

View File

@@ -96,15 +96,15 @@ setup()
<USelectMenu <USelectMenu
v-model="selectedColumns" v-model="selectedColumns"
icon="i-heroicons-adjustments-horizontal-solid" icon="i-heroicons-adjustments-horizontal-solid"
:options="dataType.templateColumns.filter(i => !i.disabledInTable)" :items="dataType.templateColumns.filter(i => !i.disabledInTable)"
multiple multiple
class="hidden lg:block" class="hidden lg:block"
by="key" by="key"
:color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'" :color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'"
:ui-menu="{ width: 'min-w-max' }" :content="{ width: 'min-w-max' }"
@change="tempStore.modifyColumns(type,selectedColumns)" @change="tempStore.modifyColumns(type,selectedColumns)"
> >
<template #label> <template #default>
Spalten Spalten
</template> </template>
</USelectMenu> </USelectMenu>

View File

@@ -181,49 +181,51 @@ const selectItem = (item) => {
</UButton> </UButton>
<UModal <UModal
prevent-close prevent-close
v-model="showFinalInvoiceConfig" v-model:open="showFinalInvoiceConfig"
> >
<UCard> <template #content>
<template #header> <UCard>
<div class="flex items-center justify-between"> <template #header>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white"> <div class="flex items-center justify-between">
Schlussrechnung konfigurieren <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
</h3> Schlussrechnung konfigurieren
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" /> </h3>
</div> <UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showFinalInvoiceConfig = false" />
</template> </div>
</template>
<UFormGroup <UFormField
label="Rechnungsvorlage" label="Rechnungsvorlage"
>
<USelectMenu
:options="props.item.createddocuments.filter(i => ['confirmationOrders','quotes'].includes(i.type))"
value-attribute="id"
option-attribute="documentNumber"
v-model="referenceDocument"
/>
</UFormGroup>
<UFormGroup
label="Abschlagsrechnungen"
>
<USelectMenu
:options="props.item.createddocuments.filter(i => ['advanceInvoices'].includes(i.type))"
multiple
value-attribute="id"
option-attribute="documentNumber"
v-model="advanceInvoicesToAdd"
/>
</UFormGroup>
<template #footer>
<UButton
@click="invoiceAdvanceInvoices"
> >
Weiter <USelectMenu
</UButton> :items="props.item.createddocuments.filter(i => ['confirmationOrders','quotes'].includes(i.type))"
</template> value-key="id"
</UCard> label-key="documentNumber"
v-model="referenceDocument"
/>
</UFormField>
<UFormField
label="Abschlagsrechnungen"
>
<USelectMenu
:items="props.item.createddocuments.filter(i => ['advanceInvoices'].includes(i.type))"
multiple
value-key="id"
label-key="documentNumber"
v-model="advanceInvoicesToAdd"
/>
</UFormField>
<template #footer>
<UButton
@click="invoiceAdvanceInvoices"
>
Weiter
</UButton>
</template>
</UCard>
</template>
</UModal> </UModal>
<UButton <UButton
@click="router.push(`/createDocument/edit/?${getAvailableQueryStringData({type: 'invoices'})}`)" @click="router.push(`/createDocument/edit/?${getAvailableQueryStringData({type: 'invoices'})}`)"
@@ -235,13 +237,13 @@ const selectItem = (item) => {
<USelectMenu <USelectMenu
v-model="selectedColumns" v-model="selectedColumns"
icon="i-heroicons-adjustments-horizontal-solid" icon="i-heroicons-adjustments-horizontal-solid"
:options="templateColumns" :items="templateColumns"
multiple multiple
class="hidden lg:block" class="hidden lg:block"
by="key" by="key"
@change="tempStore.modifyColumns('createddocuments',selectedColumns)" @change="tempStore.modifyColumns('createddocuments',selectedColumns)"
> >
<template #label> <template #default>
Spalten Spalten
</template> </template>
</USelectMenu> </USelectMenu>
@@ -252,31 +254,31 @@ const selectItem = (item) => {
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
class="w-full" class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="selectItem" :on-select="(row) => selectItem(row.original)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }"
style="height: 70vh" style="height: 70vh"
> >
<template #type-data="{row}"> <template #type-cell="{ row }">
{{dataStore.documentTypesForCreation[row.type].labelSingle}} {{ dataStore.documentTypesForCreation[row.original.type].labelSingle }}
</template> </template>
<template #state-data="{row}"> <template #state-cell="{ row }">
<span <span
v-if="row.state === 'Entwurf'" v-if="row.original.state === 'Entwurf'"
class="text-rose-500" class="text-error-500"
> >
{{row.state}} {{ row.original.state }}
</span> </span>
<span <span
v-if="row.state === 'Gebucht'" v-if="row.original.state === 'Gebucht'"
class="text-cyan-500" class="text-cyan-500"
> >
{{row.state}} {{ row.original.state }}
</span> </span>
<span <span
v-if="row.state === 'Abgeschlossen'" v-if="row.original.state === 'Abgeschlossen'"
class="text-primary-500" class="text-primary-500"
> >
{{row.state}} {{ row.original.state }}
</span> </span>
</template> </template>
<!-- <template #paid-data="{row}"> <!-- <template #paid-data="{row}">
@@ -285,19 +287,19 @@ const selectItem = (item) => {
<span v-else class="text-rose-600">Offen</span> <span v-else class="text-rose-600">Offen</span>
</div> </div>
</template>--> </template>-->
<template #reference-data="{row}"> <template #reference-cell="{ row }">
<span v-if="row === props.item.createddocuments[selectedItem]" class="text-primary-500 font-bold">{{row.documentNumber}}</span> <span v-if="row.original === props.item.createddocuments[selectedItem]" class="text-primary-500 font-bold">{{ row.original.documentNumber }}</span>
<span v-else>{{row.documentNumber}}</span> <span v-else>{{ row.original.documentNumber }}</span>
</template> </template>
<template #date-data="{row}"> <template #date-cell="{ row }">
<span v-if="row.date">{{row.date ? dayjs(row.date).format("DD.MM.YY") : ''}}</span> <span v-if="row.original.date">{{ row.original.date ? dayjs(row.original.date).format("DD.MM.YY") : '' }}</span>
<span v-if="row.documentDate">{{row.documentDate ? dayjs(row.documentDate).format("DD.MM.YY") : ''}}</span> <span v-if="row.original.documentDate">{{ row.original.documentDate ? dayjs(row.original.documentDate).format("DD.MM.YY") : '' }}</span>
</template> </template>
<template #dueDate-data="{row}"> <template #dueDate-cell="{ row }">
<span v-if="row.paymentDays && ['invoices','advanceInvoices'].includes(row.type)" >{{row.documentDate ? dayjs(row.documentDate).add(row.paymentDays,'day').format("DD.MM.YY") : ''}}</span> <span v-if="row.original.paymentDays && ['invoices','advanceInvoices'].includes(row.original.type)">{{ row.original.documentDate ? dayjs(row.original.documentDate).add(row.original.paymentDays,'day').format("DD.MM.YY") : '' }}</span>
</template> </template>
<template #amount-data="{row}"> <template #amount-cell="{ row }">
<span v-if="row.type !== 'deliveryNotes'">{{useCurrency(useSum().getCreatedDocumentSum(row, createddocuments))}}</span> <span v-if="row.original.type !== 'deliveryNotes'">{{ useCurrency(useSum().getCreatedDocumentSum(row.original, createddocuments)) }}</span>
</template> </template>
</UTable> </UTable>

View File

@@ -95,40 +95,42 @@ function isImage(file) {
<!-- 📱 PDF / IMG Viewer Slideover --> <!-- 📱 PDF / IMG Viewer Slideover -->
<UModal v-model:open="showViewer" side="bottom" class="h-[100dvh]" fullscreen> <UModal v-model:open="showViewer" side="bottom" class="h-[100dvh]" fullscreen>
<!-- Header --> <template #content>
<div class="p-4 border-b flex justify-between items-center flex-shrink-0"> <!-- Header -->
<h3 class="font-bold truncate max-w-[70vw]">{{ activeFile?.path?.split("/").pop() }}</h3> <div class="p-4 border-b flex justify-between items-center flex-shrink-0">
<UButton icon="i-heroicons-x-mark" variant="ghost" @click="closeViewer" /> <h3 class="font-bold truncate max-w-[70vw]">{{ activeFile?.path?.split("/").pop() }}</h3>
</div> <UButton icon="i-heroicons-x-mark" variant="ghost" @click="closeViewer" />
<!-- Content -->
<div class="flex-1 overflow-y-auto m-2">
<!-- PDF -->
<div v-if="activeFile && isPdf(activeFile)" class="h-full">
<PDFViewer
:no-controls="true"
:file-id="activeFile.id"
location="fileviewer-mobile"
class="h-full"
/>
</div> </div>
<!-- IMAGE --> <!-- Content -->
<div <div class="flex-1 overflow-y-auto m-2">
v-else-if="activeFile && isImage(activeFile)" <!-- PDF -->
class="p-4 flex justify-center" <div v-if="activeFile && isPdf(activeFile)" class="h-full">
> <PDFViewer
<img :no-controls="true"
:src="activeFile.url" :file-id="activeFile.id"
class="max-w-full max-h-[80vh] rounded-lg shadow" location="fileviewer-mobile"
class="h-full"
/>
</div>
<!-- IMAGE -->
<div
v-else-if="activeFile && isImage(activeFile)"
class="p-4 flex justify-center"
>
<img
:src="activeFile.url"
class="max-w-full max-h-[80vh] rounded-lg shadow"
/>
</div>
<UAlert
v-else
title="Nicht unterstützter Dateityp"
icon="i-heroicons-exclamation-triangle"
/> />
</div> </div>
</template>
<UAlert
v-else
title="Nicht unterstützter Dateityp"
icon="i-heroicons-exclamation-triangle"
/>
</div>
</UModal> </UModal>
</template> </template>

View File

@@ -79,19 +79,19 @@ const renderedAllocations = computed(() => {
v-if="props.item.statementallocations" v-if="props.item.statementallocations"
:data="renderedAllocations" :data="renderedAllocations"
:columns="normalizeTableColumns([{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}])" :columns="normalizeTableColumns([{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}])"
@select="(i) => selectAllocation(i)" :on-select="(i) => selectAllocation(i)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }"
> >
<template #amount-data="{row}"> <template #amount-cell="{row}">
<span class="text-right text-rose-600" v-if="row.amount < 0 || row.color === 'red'">{{useCurrency(row.amount)}}</span> <span class="text-right text-rose-600" v-if="row.original.amount < 0 || row.original.color === 'red'">{{useCurrency(row.original.amount)}}</span>
<span class="text-right text-primary-500" v-else-if="row.amount > 0 || row.color === 'green'">{{useCurrency(row.amount)}}</span> <span class="text-right text-primary-500" v-else-if="row.original.amount > 0 || row.original.color === 'green'">{{useCurrency(row.original.amount)}}</span>
<span v-else>{{useCurrency(row.amount)}}</span> <span v-else>{{useCurrency(row.original.amount)}}</span>
</template> </template>
<template #date-data="{row}"> <template #date-cell="{row}">
{{row.date ? dayjs(row.date).format('DD.MM.YYYY') : ''}} {{row.original.date ? dayjs(row.original.date).format('DD.MM.YYYY') : ''}}
</template> </template>
<template #description-data="{row}"> <template #description-cell="{row}">
{{row.description ? row.description : ''}} {{row.original.description ? row.original.description : ''}}
</template> </template>
</UTable> </UTable>
</UCard> </UCard>

View File

@@ -69,38 +69,38 @@ const columns = [
class="mt-3" class="mt-3"
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
:data="props.item.times" :data="props.item.times"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
> >
<template #state-data="{row}"> <template #state-cell="{ row }">
<span <span
v-if="row.state === 'Entwurf'" v-if="row.original.state === 'Entwurf'"
class="text-rose-500" class="text-error-500"
>{{row.state}}</span> >{{ row.original.state }}</span>
<span <span
v-if="row.state === 'Eingereicht'" v-if="row.original.state === 'Eingereicht'"
class="text-cyan-500" class="text-cyan-500"
>{{row.state}}</span> >{{ row.original.state }}</span>
<span <span
v-if="row.state === 'Bestätigt'" v-if="row.original.state === 'Bestätigt'"
class="text-primary-500" class="text-primary-500"
>{{row.state}}</span> >{{ row.original.state }}</span>
</template> </template>
<template #user-data="{row}"> <template #user-cell="{ row }">
{{row.profile ? row.profile.fullName : "" }} {{ row.original.profile ? row.original.profile.fullName : "" }}
</template> </template>
<template #startDate-data="{row}"> <template #startDate-cell="{ row }">
{{dayjs(row.startDate).format("DD.MM.YY HH:mm")}} {{ dayjs(row.original.startDate).format("DD.MM.YY HH:mm") }}
</template> </template>
<template #endDate-data="{row}"> <template #endDate-cell="{ row }">
{{dayjs(row.endDate).format("DD.MM.YY HH:mm")}} {{ dayjs(row.original.endDate).format("DD.MM.YY HH:mm") }}
</template> </template>
<template #duration-data="{row}"> <template #duration-cell="{ row }">
{{Math.floor(dayjs(row.endDate).diff(row.startDate, "minutes")/60)}}:{{String(dayjs(row.endDate).diff(row.startDate, "minutes") % 60).padStart(2,"0")}} h {{ Math.floor(dayjs(row.original.endDate).diff(row.original.startDate, "minutes") / 60) }}:{{ String(dayjs(row.original.endDate).diff(row.original.startDate, "minutes") % 60).padStart(2,"0") }} h
</template> </template>
<template #project-data="{row}"> <template #project-cell="{ row }">
{{row.project ? row.project.name : "" }} {{ row.original.project ? row.original.project.name : "" }}
</template> </template>
</UTable> </UTable>
</UCard> </UCard>

View File

@@ -123,7 +123,7 @@ function onSelect (option) {
/> />
<UModal <UModal
v-model="showCommandPalette" v-model:open="showCommandPalette"
> >
<template #content> <template #content>
<UCommandPalette <UCommandPalette

View File

@@ -270,29 +270,29 @@ watch(isHelpSlideoverOpen, async (isOpen) => {
@submit="addContactRequest" @submit="addContactRequest"
@reset="resetContactRequest" @reset="resetContactRequest"
> >
&lt;!&ndash; <UFormGroup &lt;!&ndash; <UFormField
label="Art:" label="Art:"
> >
<USelectMenu <USelectMenu
:options="['Hilfe','Software Problem / Bug','Funktionsanfrage','Kontakt','Sonstiges']" :options="['Hilfe','Software Problem / Bug','Funktionsanfrage','Kontakt','Sonstiges']"
v-model="contactRequestData.contactType" v-model="contactRequestData.contactType"
/> />
</UFormGroup>&ndash;&gt; </UFormField>&ndash;&gt;
<UFormGroup <UFormField
label="Titel:" label="Titel:"
> >
<UInput <UInput
v-model="contactRequestData.title" v-model="contactRequestData.title"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Nachricht:" label="Nachricht:"
> >
<UTextarea <UTextarea
v-model="contactRequestData.message" v-model="contactRequestData.message"
rows="6" rows="6"
/> />
</UFormGroup> </UFormField>
<InputGroup class="mt-3"> <InputGroup class="mt-3">
<UButton <UButton
type="submit" type="submit"

View File

@@ -76,7 +76,7 @@ const renderText = (text) => {
<template> <template>
<UModal <UModal
v-model="showAddHistoryItemModal" v-model:open="showAddHistoryItemModal"
> >
<template #content> <template #content>
@@ -90,7 +90,7 @@ const renderText = (text) => {
</div> </div>
</template> </template>
<UFormGroup <UFormField
label="Text:" label="Text:"
> >
<UTextarea <UTextarea
@@ -102,7 +102,7 @@ const renderText = (text) => {
<UKbd>{{metaSymbol}}</UKbd> <UKbd>Enter</UKbd> Speichern <UKbd>{{metaSymbol}}</UKbd> <UKbd>Enter</UKbd> Speichern
</template>--> </template>-->
</UFormGroup> </UFormField>
<template #footer> <template #footer>

View File

@@ -322,6 +322,21 @@ const links = computed(() => {
to: "/historyitems", to: "/historyitems",
icon: "i-heroicons-book-open" icon: "i-heroicons-book-open"
} : null, } : null,
...(has("projects") && featureEnabled("projects")) ? [{
label: "Projekte",
to: "/standardEntity/projects",
icon: "i-heroicons-clipboard-document-check"
}] : [],
...(has("contracts") && featureEnabled("contracts")) ? [{
label: "Verträge",
to: "/standardEntity/contracts",
icon: "i-heroicons-clipboard-document"
}] : [],
...(has("plants") && featureEnabled("plants")) ? [{
label: "Objekte",
to: "/standardEntity/plants",
icon: "i-heroicons-clipboard-document"
}] : [],
...(visibleOrganisationChildren.length > 0 ? [{ ...(visibleOrganisationChildren.length > 0 ? [{
label: "Organisation", label: "Organisation",
icon: "i-heroicons-rectangle-stack", icon: "i-heroicons-rectangle-stack",
@@ -371,21 +386,7 @@ const links = computed(() => {
children: visibleMasterDataChildren children: visibleMasterDataChildren
}] : []), }] : []),
...(has("projects") && featureEnabled("projects")) ? [{
label: "Projekte",
to: "/standardEntity/projects",
icon: "i-heroicons-clipboard-document-check"
}] : [],
...(has("contracts") && featureEnabled("contracts")) ? [{
label: "Verträge",
to: "/standardEntity/contracts",
icon: "i-heroicons-clipboard-document"
}] : [],
...(has("plants") && featureEnabled("plants")) ? [{
label: "Objekte",
to: "/standardEntity/plants",
icon: "i-heroicons-clipboard-document"
}] : [],
...(visibleSettingsChildren.length > 0 ? [{ ...(visibleSettingsChildren.length > 0 ? [{
label: "Einstellungen", label: "Einstellungen",
defaultOpen: false, defaultOpen: false,

View File

@@ -119,7 +119,7 @@ const setDeliveryDateToToday = () => {
<div class="space-y-5"> <div class="space-y-5">
<UFormGroup <UFormField
label="Datum der Ausführung" label="Datum der Ausführung"
:error="errors.deliveryDate" :error="errors.deliveryDate"
required required
@@ -134,9 +134,9 @@ const setDeliveryDateToToday = () => {
/> />
<UButton color="gray" variant="soft" size="lg" label="Heute" @click="setDeliveryDateToToday" /> <UButton color="gray" variant="soft" size="lg" label="Heute" @click="setDeliveryDateToToday" />
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
v-if="!context?.meta?.defaultProfileId && data?.profiles?.length > 0" v-if="!context?.meta?.defaultProfileId && data?.profiles?.length > 0"
label="Mitarbeiter" label="Mitarbeiter"
:error="errors.profile" :error="errors.profile"
@@ -144,16 +144,16 @@ const setDeliveryDateToToday = () => {
> >
<USelectMenu <USelectMenu
v-model="form.profile" v-model="form.profile"
:options="data.profiles" :items="data.profiles"
option-attribute="fullName" label-key="fullName"
value-attribute="id" value-key="id"
placeholder="Name auswählen..." placeholder="Name auswählen..."
searchable searchable
size="lg" size="lg"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
v-if="data?.projects?.length > 0" v-if="data?.projects?.length > 0"
:label="config.ui?.labels?.project || 'Projekt / Auftrag'" :label="config.ui?.labels?.project || 'Projekt / Auftrag'"
:error="errors.project" :error="errors.project"
@@ -161,16 +161,16 @@ const setDeliveryDateToToday = () => {
> >
<USelectMenu <USelectMenu
v-model="form.project" v-model="form.project"
:options="data.projects" :items="data.projects"
option-attribute="name" label-key="name"
value-attribute="id" value-key="id"
placeholder="Wählen..." placeholder="Wählen..."
searchable searchable
size="lg" size="lg"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
v-if="data?.services?.length > 0" v-if="data?.services?.length > 0"
:label="config?.ui?.labels?.service || 'Tätigkeit'" :label="config?.ui?.labels?.service || 'Tätigkeit'"
:error="errors.service" :error="errors.service"
@@ -178,16 +178,16 @@ const setDeliveryDateToToday = () => {
> >
<USelectMenu <USelectMenu
v-model="form.service" v-model="form.service"
:options="data.services" :items="data.services"
option-attribute="name" label-key="name"
value-attribute="id" value-key="id"
placeholder="Wählen..." placeholder="Wählen..."
searchable searchable
size="lg" size="lg"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Menge / Dauer" label="Menge / Dauer"
:error="errors.quantity" :error="errors.quantity"
required required
@@ -203,9 +203,9 @@ const setDeliveryDateToToday = () => {
<span class="text-gray-500 text-sm pr-2">{{ currentUnit }}</span> <span class="text-gray-500 text-sm pr-2">{{ currentUnit }}</span>
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
v-if="config?.features?.agriculture?.showDieselUsage" v-if="config?.features?.agriculture?.showDieselUsage"
label="Dieselverbrauch" label="Dieselverbrauch"
:error="errors.diesel" :error="errors.diesel"
@@ -216,11 +216,11 @@ const setDeliveryDateToToday = () => {
<span class="text-gray-500 text-xs">Liter</span> <span class="text-gray-500 text-xs">Liter</span>
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<UFormGroup :label="config?.ui?.labels?.description || 'Notiz / Vorkommnisse'"> <UFormField :label="config?.ui?.labels?.description || 'Notiz / Vorkommnisse'">
<UTextarea v-model="form.description" :rows="3" placeholder="Optional..." /> <UTextarea v-model="form.description" :rows="3" placeholder="Optional..." />
</UFormGroup> </UFormField>
</div> </div>

View File

@@ -1,55 +1,44 @@
<script setup> <script setup>
const { isHelpSlideoverOpen } = useDashboard()
const auth = useAuthStore() const auth = useAuthStore()
const items = computed(() => [ const userItems = computed(() => [[
[{ {
slot: 'account', label: 'Passwort aendern',
label: '',
disabled: true
}], [/*{
label: 'Mein Profil',
icon: 'i-heroicons-user',
to: `/profiles/show/${profileStore.activeProfile.id}`
},*/{
label: 'Passwort ändern',
icon: 'i-heroicons-shield-check', icon: 'i-heroicons-shield-check',
to: `/password-change` to: '/password-change'
},{ },
{
label: 'Abmelden', label: 'Abmelden',
icon: 'i-heroicons-arrow-left-on-rectangle', icon: 'i-heroicons-arrow-left-on-rectangle',
click: async () => { onSelect: async () => {
await auth.logout() await auth.logout()
} }
}] }
]) ]])
</script> </script>
<template> <template>
<UDropdown mode="hover" :items="items" :ui="{ width: 'w-full', item: { disabled: 'cursor-text select-text' } }" :popper="{ strategy: 'absolute', placement: 'top' }" class="w-full"> <UDropdownMenu
<template #default="slotProps"> :items="userItems"
<UButton color="gray" variant="ghost" class="w-full" :label="auth.user.email" :class="[slotProps?.open && 'bg-gray-50 dark:bg-gray-800']"> :content="{ align: 'start', side: 'top', sideOffset: 8 }"
<!-- <template #leading> :ui="{ content: 'w-[var(--reka-dropdown-menu-trigger-width)] max-w-[var(--reka-dropdown-menu-trigger-width)]' }"
<UAvatar :alt="auth.user.email" size="xs" /> class="block w-full"
</template>--> >
<template #default="{ open }">
<UButton
color="gray"
variant="ghost"
class="w-full min-w-0 justify-start gap-2 rounded-lg px-2.5 py-2 text-left"
:class="[open && 'bg-gray-100 dark:bg-gray-800']"
>
<span class="min-w-0 flex-1 truncate font-medium text-gray-900 dark:text-white">
{{ auth.user.email }}
</span>
<template #trailing> <template #trailing>
<UIcon name="i-heroicons-ellipsis-vertical" class="w-5 h-5 ml-auto" /> <UIcon name="i-heroicons-ellipsis-vertical" class="h-5 w-5 shrink-0" />
</template> </template>
</UButton> </UButton>
</template> </template>
</UDropdownMenu>
<template #account>
<div class="text-left">
<p>
Angemeldet als
</p>
<p class="truncate font-medium text-gray-900 dark:text-white">
{{auth.user.email}}
</p>
</div>
</template>
</UDropdown>
</template> </template>

View File

@@ -73,7 +73,7 @@ const startImport = () => {
Erstelltes Dokument Kopieren Erstelltes Dokument Kopieren
</template> </template>
<UFormGroup <UFormField
label="Dokumententyp:" label="Dokumententyp:"
class="mb-3" class="mb-3"
> >
@@ -85,7 +85,7 @@ const startImport = () => {
> >
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UCheckbox <UCheckbox
v-for="key in Object.keys(optionsToImport).filter(i => documentTypeToUse !== props.type ? !['startText','endText'].includes(i) : true)" v-for="key in Object.keys(optionsToImport).filter(i => documentTypeToUse !== props.type ? !['startText','endText'].includes(i) : true)"
v-model="optionsToImport[key]" v-model="optionsToImport[key]"

View File

@@ -25,7 +25,7 @@ setupPage()
v-if="openTasks.length > 0" v-if="openTasks.length > 0"
:data="openTasks" :data="openTasks"
:columns="normalizeTableColumns([{key:'name',label:'Name'},{key:'categorie',label:'Kategorie'}])" :columns="normalizeTableColumns([{key:'name',label:'Name'},{key:'categorie',label:'Kategorie'}])"
@select="(i) => router.push(`/tasks/show/${i.id}`)" :on-select="(i) => router.push(`/tasks/show/${i.id}`)"
/> />
<div v-else> <div v-else>
<p class="text-center font-bold">Keine offenen Aufgaben</p> <p class="text-center font-bold">Keine offenen Aufgaben</p>

View File

@@ -53,15 +53,15 @@ const stopStartedTime = async () => {
<p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p> <p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p>
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p> <p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p>
<UFormGroup <UFormField
class="mt-2" class="mt-2"
label="Notizen:" label="Notizen:"
> >
<UTextarea <UTextarea
v-model="runningTimeInfo.notes" v-model="runningTimeInfo.notes"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
class="mt-2" class="mt-2"
label="Projekt:" label="Projekt:"
> >
@@ -74,7 +74,7 @@ const stopStartedTime = async () => {
value-attribute="id" value-attribute="id"
option-attribute="name" option-attribute="name"
/> />
</UFormGroup> </UFormField>
<UButton <UButton
class="mt-3" class="mt-3"
@click="stopStartedTime" @click="stopStartedTime"

View File

@@ -49,14 +49,14 @@ const stopStartedTime = async () => {
<p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p> <p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p>
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p> <p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p>
<UFormGroup <UFormField
class="mt-2" class="mt-2"
label="Notizen:" label="Notizen:"
> >
<UTextarea <UTextarea
v-model="runningTimeInfo.notes" v-model="runningTimeInfo.notes"
/> />
</UFormGroup> </UFormField>
<UButton <UButton
class="mt-3" class="mt-3"
@click="stopStartedTime" @click="stopStartedTime"

View File

@@ -47,14 +47,14 @@ async function handlePrint() {
{{labelPrinter.printProgress}} {{labelPrinter.printProgress}}
<UFormGroup label="Breite"> <UFormField label="Breite">
<UInput v-model="labelWidth"><template #trailing>mm</template></UInput> <UInput v-model="labelWidth"><template #trailing>mm</template></UInput>
</UFormGroup> </UFormField>
<UFormGroup label="Höhe"> <UFormField label="Höhe">
<UInput v-model="labelHeight"><template #trailing>mm</template></UInput> <UInput v-model="labelHeight"><template #trailing>mm</template></UInput>
</UFormGroup> </UFormField>
<UFormGroup label="ZPL"> <UFormField label="ZPL">
<UTextarea v-model="zpl" rows="6" /> <UTextarea v-model="zpl" rows="6" />
</UFormGroup> </UFormField>
</UCard> </UCard>
</template> </template>

View File

@@ -99,16 +99,18 @@
</div> </div>
<UModal v-model:open="isCreateModalOpen"> <UModal v-model:open="isCreateModalOpen">
<div class="p-5"> <template #content>
<h3 class="font-bold mb-4">Neue Seite</h3> <div class="p-5">
<form @submit.prevent="createPage"> <h3 class="font-bold mb-4">Neue Seite</h3>
<UInput v-model="newTitle" placeholder="Titel..." autofocus /> <form @submit.prevent="createPage">
<div class="mt-4 flex justify-end gap-2"> <UInput v-model="newTitle" placeholder="Titel..." autofocus />
<UButton color="gray" variant="ghost" @click="isCreateModalOpen = false">Abbrechen</UButton> <div class="mt-4 flex justify-end gap-2">
<UButton type="submit" color="primary" :loading="isCreating">Erstellen</UButton> <UButton color="gray" variant="ghost" @click="isCreateModalOpen = false">Abbrechen</UButton>
</div> <UButton type="submit" color="primary" :loading="isCreating">Erstellen</UButton>
</form> </div>
</div> </form>
</div>
</template>
</UModal> </UModal>
</div> </div>
@@ -163,7 +165,7 @@ async function selectPage(id: string) {
const data = await $api(`/api/wiki/${id}`, { method: 'GET' }) const data = await $api(`/api/wiki/${id}`, { method: 'GET' })
selectedPage.value = data selectedPage.value = data
} catch (e) { } catch (e) {
toast.add({ title: 'Fehler beim Laden', color: 'red' }) toast.add({ title: 'Fehler beim Laden', color: 'error' })
} finally { } finally {
loadingContent.value = false loadingContent.value = false
} }

View File

@@ -272,7 +272,7 @@ onMounted(() => {
<template #footer="{ collapsed }"> <template #footer="{ collapsed }">
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UColorModeToggle :class="[collapsed ? 'mx-auto' : 'ml-3']"/> <UColorModeSwitch :class="[collapsed ? 'mx-auto' : 'ml-3']"/>
<UDashboardSidebarCollapse v-if="collapsed" class="mx-auto" /> <UDashboardSidebarCollapse v-if="collapsed" class="mx-auto" />
</div> </div>

View File

@@ -256,31 +256,31 @@ onMounted(loadData)
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
:data="periods" :data="periods"
:loading="loading" :loading="loading"
:empty-state="{ icon: 'i-heroicons-calculator', label: 'Keine Daten für die USt-Auswertung vorhanden' }" :empty="{ icon: 'i-heroicons-calculator', label: 'Keine Daten für die USt-Auswertung vorhanden' }"
> >
<template #label-data="{ row }"> <template #label-cell="{ row }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span>{{ row.label }}</span> <span>{{ row.original.label }}</span>
<UBadge v-if="row.isCurrent" color="primary" variant="soft">Aktuell</UBadge> <UBadge v-if="row.original.isCurrent" color="primary" variant="soft">Aktuell</UBadge>
</div> </div>
</template> </template>
<template #outputTax-data="{ row }"> <template #outputTax-cell="{ row }">
{{ formatCurrency(row.outputTax) }} {{ formatCurrency(row.original.outputTax) }}
</template> </template>
<template #inputTax-data="{ row }"> <template #inputTax-cell="{ row }">
{{ formatCurrency(row.inputTax) }} {{ formatCurrency(row.original.inputTax) }}
</template> </template>
<template #balance-data="{ row }"> <template #balance-cell="{ row }">
<span :class="row.balance >= 0 ? 'text-amber-600 dark:text-amber-400 font-medium' : 'text-emerald-600 dark:text-emerald-400 font-medium'"> <span :class="row.original.balance >= 0 ? 'text-amber-600 dark:text-amber-400 font-medium' : 'text-emerald-600 dark:text-emerald-400 font-medium'">
{{ formatCurrency(row.balance) }} {{ formatCurrency(row.original.balance) }}
</span> </span>
</template> </template>
<template #documents-data="{ row }"> <template #documents-cell="{ row }">
{{ row.outputCount }} / {{ row.inputCount }} {{ row.original.outputCount }} / {{ row.original.inputCount }}
</template> </template>
</UTable> </UTable>
</UCard> </UCard>

View File

@@ -198,16 +198,16 @@ setupPage()
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
class="w-full" class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/accounts/show/${i.id}`)" :on-select="(i) => router.push(`/accounts/show/${i.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }"
> >
<template #allocations-data="{row}"> <template #allocations-cell="{row}">
<span v-if="dataLoaded">{{row.allocations ? row.allocations : null}}</span> <span v-if="dataLoaded">{{row.original.allocations ? row.original.allocations : null}}</span>
<USkeleton v-else class="h-4 w-[250px]" /> <USkeleton v-else class="h-4 w-[250px]" />
</template> </template>
<template #saldo-data="{row}"> <template #saldo-cell="{row}">
<span v-if="dataLoaded">{{row.allocations ? useCurrency(row.saldo) : null}}</span> <span v-if="dataLoaded">{{row.original.allocations ? useCurrency(row.original.saldo) : null}}</span>
<USkeleton v-else class="h-4 w-[250px]" /> <USkeleton v-else class="h-4 w-[250px]" />
</template> </template>
</UTable> </UTable>

View File

@@ -106,7 +106,7 @@ const saldo = computed(() => {
</UDashboardNavbar> </UDashboardNavbar>
<UDashboardPanelContent> <UDashboardPanelContent>
<UTabs :items="[{label: 'Information'},{label: 'Buchungen'}]"> <UTabs :items="[{label: 'Information'},{label: 'Buchungen'}]">
<template #item="{item}"> <template #content="{item}">
<UCard class="mt-5" v-if="item.label === 'Information'"> <UCard class="mt-5" v-if="item.label === 'Information'">
<div class="text-wrap"> <div class="text-wrap">
<table class="w-full" v-if="itemInfo"> <table class="w-full" v-if="itemInfo">
@@ -139,19 +139,19 @@ const saldo = computed(() => {
v-if="statementallocations" v-if="statementallocations"
:data="renderedAllocations" :data="renderedAllocations"
:columns="normalizeTableColumns([{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}])" :columns="normalizeTableColumns([{key:'amount', label:'Betrag'},{key:'date', label:'Datum'},{key:'partner', label:'Partner'},{key:'description', label:'Beschreibung'}])"
@select="(i) => selectAllocation(i)" :on-select="(i) => selectAllocation(i)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Buchungen anzuzeigen' }"
> >
<template #amount-data="{row}"> <template #amount-cell="{row}">
<span class="text-right text-rose-600" v-if="row.amount < 0 || row.color === 'red'">{{useCurrency(row.amount)}}</span> <span class="text-right text-rose-600" v-if="row.original.amount < 0 || row.original.color === 'red'">{{useCurrency(row.original.amount)}}</span>
<span class="text-right text-primary-500" v-else-if="row.amount > 0 || row.color === 'green'">{{useCurrency(row.amount)}}</span> <span class="text-right text-primary-500" v-else-if="row.original.amount > 0 || row.original.color === 'green'">{{useCurrency(row.original.amount)}}</span>
<span v-else>{{useCurrency(row.amount)}}</span> <span v-else>{{useCurrency(row.original.amount)}}</span>
</template> </template>
<template #date-data="{row}"> <template #date-cell="{row}">
{{row.date ? dayjs(row.date).format('DD.MM.YYYY') : ''}} {{row.original.date ? dayjs(row.original.date).format('DD.MM.YYYY') : ''}}
</template> </template>
<template #description-data="{row}"> <template #description-cell="{row}">
{{row.description ? row.description : ''}} {{row.original.description ? row.original.description : ''}}
</template> </template>
</UTable> </UTable>
</UCard> </UCard>

View File

@@ -602,16 +602,17 @@ onMounted(() => {
<PageLeaveGuard :when="isSyncing"/> <PageLeaveGuard :when="isSyncing"/>
<UModal v-model:open="suggestionsModalOpen" :ui="{ width: 'sm:max-w-6xl' }"> <UModal v-model:open="suggestionsModalOpen" :ui="{ width: 'sm:max-w-6xl' }">
<UCard> <template #content>
<template #header> <UCard>
<div class="flex items-center justify-between gap-3"> <template #header>
<div> <div class="flex items-center justify-between gap-3">
<div class="text-lg font-semibold">Vorschlaege fuer Bankbuchungen</div> <div>
<div class="text-sm text-gray-500">Direkte Zuweisung von passenden Rechnungen und Eingangsbelegen</div> <div class="text-lg font-semibold">Vorschlaege fuer Bankbuchungen</div>
<div class="text-sm text-gray-500">Direkte Zuweisung von passenden Rechnungen und Eingangsbelegen</div>
</div>
<UBadge color="primary" variant="subtle">{{ suggestionCount }}</UBadge>
</div> </div>
<UBadge color="primary" variant="subtle">{{ suggestionCount }}</UBadge> </template>
</div>
</template>
<div v-if="rowsWithSuggestions.length > 0" class="grid grid-cols-1 lg:grid-cols-3 gap-4 min-h-[520px]"> <div v-if="rowsWithSuggestions.length > 0" class="grid grid-cols-1 lg:grid-cols-3 gap-4 min-h-[520px]">
<div class="lg:col-span-1 border rounded-lg overflow-hidden dark:border-gray-800"> <div class="lg:col-span-1 border rounded-lg overflow-hidden dark:border-gray-800">
@@ -701,10 +702,11 @@ onMounted(() => {
</div> </div>
</div> </div>
<div v-else class="py-10 text-center text-gray-400"> <div v-else class="py-10 text-center text-gray-400">
<UIcon name="i-heroicons-sparkles" class="w-10 h-10 mx-auto mb-2 opacity-30"/> <UIcon name="i-heroicons-sparkles" class="w-10 h-10 mx-auto mb-2 opacity-30"/>
<p>Keine Vorschlaege fuer die aktuelle Filterung vorhanden.</p> <p>Keine Vorschlaege fuer die aktuelle Filterung vorhanden.</p>
</div> </div>
</UCard> </UCard>
</template>
</UModal> </UModal>
</template> </template>

View File

@@ -571,14 +571,14 @@ setup()
class="p-4 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 shadow-sm shrink-0 z-10"> class="p-4 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 shadow-sm shrink-0 z-10">
<div class="grid grid-cols-12 gap-4 items-end"> <div class="grid grid-cols-12 gap-4 items-end">
<div class="col-span-12 md:col-span-3"> <div class="col-span-12 md:col-span-3">
<UFormGroup label="Betrag" size="sm"> <UFormField label="Betrag" size="sm">
<UInput v-model="manualAllocationSum" type="number" step="0.01"> <UInput v-model="manualAllocationSum" type="number" step="0.01">
<template #trailing><span class="text-gray-500 text-xs">EUR</span></template> <template #trailing><span class="text-gray-500 text-xs">EUR</span></template>
</UInput> </UInput>
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-12 md:col-span-5"> <div class="col-span-12 md:col-span-5">
<UFormGroup label="Konto / Manuelle Buchung" size="sm"> <UFormField label="Konto / Manuelle Buchung" size="sm">
<div class="flex gap-1"> <div class="flex gap-1">
<USelectMenu <USelectMenu
class="w-full" class="w-full"
@@ -607,7 +607,7 @@ setup()
/> />
</UTooltip> </UTooltip>
</div> </div>
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-12 md:col-span-4 flex justify-end gap-2 pb-0.5"> <div class="col-span-12 md:col-span-4 flex justify-end gap-2 pb-0.5">
<UButton variant="soft" color="gray" icon="i-heroicons-adjustments-horizontal" <UButton variant="soft" color="gray" icon="i-heroicons-adjustments-horizontal"

View File

@@ -1283,7 +1283,9 @@ const generateDocument = async () => {
} }
const onChangeTab = (index) => { const onChangeTab = (index) => {
if (index === 1) { selectedTab.value = String(index)
if (String(index) === "1") {
generateDocument() generateDocument()
} }
} }
@@ -1441,13 +1443,13 @@ const saveDocument = async (state, resetup = false) => {
if (resetup) await setupPage() if (resetup) await setupPage()
} }
const selectedTab = ref(0) const selectedTab = ref("0")
const closeDocument = async () => { const closeDocument = async () => {
if(selectedTab.value === 0) { if(selectedTab.value === "0") {
await generateDocument() await generateDocument()
selectedTab.value = 1 selectedTab.value = "1"
} else { } else {
loaded.value = false loaded.value = false
@@ -1628,7 +1630,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
@click="closeDocument" @click="closeDocument"
v-if="itemInfo.id && itemInfo.type !== 'serialInvoices'" v-if="itemInfo.id && itemInfo.type !== 'serialInvoices'"
> >
{{selectedTab === 0 ? "Vorschau zeigen" : "Fertigstellen"}} {{selectedTab === '0' ? "Vorschau zeigen" : "Fertigstellen"}}
</UButton> </UButton>
<UButton <UButton
icon="i-mdi-content-save" icon="i-mdi-content-save"
@@ -1640,8 +1642,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</template> </template>
</UDashboardNavbar> </UDashboardNavbar>
<UDashboardPanelContent> <UDashboardPanelContent>
<UTabs class="p-5" :items="tabItems" @change="onChangeTab" v-if="loaded" v-model="selectedTab"> <UTabs class="p-5" :items="tabItems" @update:model-value="onChangeTab" v-if="loaded" v-model="selectedTab">
<template #item="{item}"> <template #content="{item}">
<div v-if="item.label === 'Editor'"> <div v-if="item.label === 'Editor'">
<UAlert <UAlert
class="my-5" class="my-5"
@@ -1664,7 +1666,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<InputGroup> <InputGroup>
<div class="w-1/3 mr-5"> <div class="w-1/3 mr-5">
<UFormGroup <UFormField
label="Dokumenttyp:" label="Dokumenttyp:"
> >
<InputGroup> <InputGroup>
@@ -1689,14 +1691,15 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</InputGroup> </InputGroup>
<USlideover <USlideover
v-model="showAdvanceInvoiceCalcModal" v-model:open="showAdvanceInvoiceCalcModal"
> >
<UCard class="h-full"> <template #body>
<template #header> <UCard class="h-full">
<UButton @click="importPositions">Übernehmen</UButton> <template #header>
</template> <UButton @click="importPositions">Übernehmen</UButton>
</template>
<UFormGroup <UFormField
label="Gesamtsumme:" label="Gesamtsumme:"
> >
<UInput <UInput
@@ -1705,8 +1708,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
v-model="advanceInvoiceData.totalSumNet" v-model="advanceInvoiceData.totalSumNet"
@focusout="advanceInvoiceData.part = advanceInvoiceData.totalSumNet / 100 * advanceInvoiceData.partPerPecentage" @focusout="advanceInvoiceData.part = advanceInvoiceData.totalSumNet / 100 * advanceInvoiceData.partPerPecentage"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Prozent:" label="Prozent:"
> >
<UInput <UInput
@@ -1715,27 +1718,26 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
v-model="advanceInvoiceData.partPerPecentage" v-model="advanceInvoiceData.partPerPecentage"
@focusout="advanceInvoiceData.part = advanceInvoiceData.totalSumNet / 100 * advanceInvoiceData.partPerPecentage" @focusout="advanceInvoiceData.part = advanceInvoiceData.totalSumNet / 100 * advanceInvoiceData.partPerPecentage"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Abzurechnender Anteil:" label="Abzurechnender Anteil:"
> >
<UInput <UInput
type="number" type="number"
:step="0.01" :step="0.01"
v-model="advanceInvoiceData.part" v-model="advanceInvoiceData.part"
@focusout="advanceInvoiceData.partPerPecentage = Number((advanceInvoiceData.part / advanceInvoiceData.totalSumNet * 100).toFixed(2))" @focusout="advanceInvoiceData.partPerPecentage = Number((advanceInvoiceData.part / advanceInvoiceData.totalSumNet * 100).toFixed(2))"
/> />
</UFormGroup> </UFormField>
</UCard>
</template>
</UCard>
</USlideover> </USlideover>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Steuertyp:" label="Steuertyp:"
v-if="['invoices','advanceInvoices','quotes','confirmationOrders','serialInvoices'].includes(itemInfo.type)" v-if="['invoices','advanceInvoices','quotes','confirmationOrders','serialInvoices'].includes(itemInfo.type)"
> >
@@ -1747,9 +1749,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
@change="setTaxType" @change="setTaxType"
class="w-full" class="w-full"
></USelectMenu> ></USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Briefpapier:" label="Briefpapier:"
> >
<USelectMenu <USelectMenu
@@ -1767,9 +1769,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
{{ itemInfo.letterhead ? letterheads.find(i => i.id === itemInfo.letterhead).name : "Kein Briefpapier gewählt" }} {{ itemInfo.letterhead ? letterheads.find(i => i.id === itemInfo.letterhead).name : "Kein Briefpapier gewählt" }}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Kunde:" label="Kunde:"
> >
<div class="flex flex-row"> <div class="flex flex-row">
@@ -1860,8 +1862,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</UAlert> </UAlert>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Ansprechpartner:" label="Ansprechpartner:"
v-if="itemInfo.customer ? customers.find(i => i.id === itemInfo.customer).isCompany : false " v-if="itemInfo.customer ? customers.find(i => i.id === itemInfo.customer).isCompany : false "
> >
@@ -1915,9 +1917,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
/> />
</InputGroup> </InputGroup>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Adresse:" label="Adresse:"
> >
<UInput <UInput
@@ -1949,10 +1951,10 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
:color="itemInfo.address.city ? 'primary' : 'error'" :color="itemInfo.address.city ? 'primary' : 'error'"
/> />
</InputGroup> </InputGroup>
</UFormGroup> </UFormField>
</div> </div>
<div class="w-2/3"> <div class="w-2/3">
<UFormGroup <UFormField
:label="itemInfo.documentNumberTitle + ':'" :label="itemInfo.documentNumberTitle + ':'"
v-if="itemInfo.type !== 'serialInvoices'" v-if="itemInfo.type !== 'serialInvoices'"
> >
@@ -1961,10 +1963,10 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
placeholder="XXXX" placeholder="XXXX"
disabled disabled
/> />
</UFormGroup> </UFormField>
<InputGroup class="w-full"> <InputGroup class="w-full">
<UFormGroup <UFormField
class="w-80 mr-1" class="w-80 mr-1"
label="Lieferdatumsart:" label="Lieferdatumsart:"
v-if="itemInfo.type !== 'serialInvoices'" v-if="itemInfo.type !== 'serialInvoices'"
@@ -1975,8 +1977,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
:label="`${itemInfo.deliveryDateType}${['Lieferzeitraum', 'Leistungszeitraum'].includes(itemInfo.deliveryDateType) ? ' Start' : ''}:`" :label="`${itemInfo.deliveryDateType}${['Lieferzeitraum', 'Leistungszeitraum'].includes(itemInfo.deliveryDateType) ? ' Start' : ''}:`"
v-if="itemInfo.type !== 'serialInvoices' && itemInfo.deliveryDateType !== 'Kein Lieferdatum anzeigen'" v-if="itemInfo.type !== 'serialInvoices' && itemInfo.deliveryDateType !== 'Kein Lieferdatum anzeigen'"
class="mr-1" class="mr-1"
@@ -1994,8 +1996,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
:label="itemInfo.deliveryDateType + ' Ende:'" :label="itemInfo.deliveryDateType + ' Ende:'"
v-if="itemInfo.type !== 'serialInvoices' && ['Lieferzeitraum','Leistungszeitraum'].includes(itemInfo.deliveryDateType)" v-if="itemInfo.type !== 'serialInvoices' && ['Lieferzeitraum','Leistungszeitraum'].includes(itemInfo.deliveryDateType)"
> >
@@ -2012,11 +2014,11 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<LazyDatePicker v-model="itemInfo.deliveryDateEnd" @close="close"/> <LazyDatePicker v-model="itemInfo.deliveryDateEnd" @close="close"/>
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
</InputGroup> </InputGroup>
<InputGroup class="w-full"> <InputGroup class="w-full">
<UFormGroup <UFormField
label="Belegdatum:" label="Belegdatum:"
class="mr-1" class="mr-1"
v-if="itemInfo.type !== 'serialInvoices'" v-if="itemInfo.type !== 'serialInvoices'"
@@ -2032,10 +2034,10 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<LazyDatePicker v-model="itemInfo.documentDate" @close="close"/> <LazyDatePicker v-model="itemInfo.documentDate" @close="close"/>
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
class="w-full" class="w-full"
label="Zahlungsziel in Tagen:" label="Zahlungsziel in Tagen:"
> >
@@ -2043,8 +2045,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
type="number" type="number"
v-model="itemInfo.paymentDays" v-model="itemInfo.paymentDays"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
class="w-full" class="w-full"
label="Zahlungsart:" label="Zahlungsart:"
> >
@@ -2066,8 +2068,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
{{itemInfo.payment_type === 'transfer' ? "Überweisung" : "SEPA-Lastschrift"}} {{itemInfo.payment_type === 'transfer' ? "Überweisung" : "SEPA-Lastschrift"}}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
class="w-full" class="w-full"
label="Individueller Aufschlag:" label="Individueller Aufschlag:"
> >
@@ -2081,9 +2083,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span> <span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
</InputGroup> </InputGroup>
<UFormGroup <UFormField
label="Mitarbeiter:" label="Mitarbeiter:"
> >
<USelectMenu <USelectMenu
@@ -2093,22 +2095,22 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
value-attribute="id" value-attribute="id"
@change="setContactPersonData" @change="setContactPersonData"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Kontakt Telefon:" label="Kontakt Telefon:"
> >
<UInput <UInput
v-model="itemInfo.contactTel" v-model="itemInfo.contactTel"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Kontakt E-Mail:" label="Kontakt E-Mail:"
> >
<UInput <UInput
v-model="itemInfo.contactEMail" v-model="itemInfo.contactEMail"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Objekt:" label="Objekt:"
> >
<InputGroup> <InputGroup>
@@ -2145,8 +2147,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
/> />
</InputGroup> </InputGroup>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Projekt:" label="Projekt:"
> >
<InputGroup> <InputGroup>
@@ -2184,8 +2186,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
/> />
</InputGroup> </InputGroup>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Vertrag:" label="Vertrag:"
> >
<InputGroup> <InputGroup>
@@ -2223,7 +2225,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
/> />
</InputGroup> </InputGroup>
</UFormGroup> </UFormField>
</div> </div>
</InputGroup> </InputGroup>
@@ -2235,7 +2237,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<div class="flex flex-row"> <div class="flex flex-row">
<div class="w-1/3"> <div class="w-1/3">
<UFormGroup <UFormField
label="Datum erste Ausführung:" label="Datum erste Ausführung:"
> >
<UPopover :popper="{ placement: 'bottom-start' }"> <UPopover :popper="{ placement: 'bottom-start' }">
@@ -2249,8 +2251,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<LazyDatePicker v-model="itemInfo.serialConfig.firstExecution" @close="close"/> <LazyDatePicker v-model="itemInfo.serialConfig.firstExecution" @close="close"/>
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Datum letzte Ausführung:" label="Datum letzte Ausführung:"
> >
<UPopover :popper="{ placement: 'bottom-start' }"> <UPopover :popper="{ placement: 'bottom-start' }">
@@ -2264,7 +2266,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<LazyDatePicker v-model="itemInfo.serialConfig.executionUntil" @close="close"/> <LazyDatePicker v-model="itemInfo.serialConfig.executionUntil" @close="close"/>
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
<UCheckbox <UCheckbox
v-model="itemInfo.serialConfig.active" v-model="itemInfo.serialConfig.active"
label="Aktiv" label="Aktiv"
@@ -2273,22 +2275,22 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</div> </div>
<div class="w-2/3"> <div class="w-2/3">
<UFormGroup <UFormField
label="Intervall:" label="Intervall:"
> >
<USelectMenu <USelectMenu
v-model="itemInfo.serialConfig.intervall" v-model="itemInfo.serialConfig.intervall"
:options="['wöchentlich','2 - wöchentlich', 'monatlich', 'vierteljährlich','halbjährlich', 'jährlich']" :options="['wöchentlich','2 - wöchentlich', 'monatlich', 'vierteljährlich','halbjährlich', 'jährlich']"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Richtung:" label="Richtung:"
> >
<USelectMenu <USelectMenu
v-model="itemInfo.serialConfig.dateDirection" v-model="itemInfo.serialConfig.dateDirection"
:options="['Rückwirkend','Im Voraus']" :options="['Rückwirkend','Im Voraus']"
/> />
</UFormGroup> </UFormField>
<UAlert <UAlert
title="Anfangs- und Enddatum" title="Anfangs- und Enddatum"
description="Für das Anfangs- und Enddatum werden jeweils der ersten und letzte Tag des ausgewählten Intervalls und der Richtung automatisch ausgewählt" description="Für das Anfangs- und Enddatum werden jeweils der ersten und letzte Tag des ausgewählten Intervalls und der Richtung automatisch ausgewählt"
@@ -2305,23 +2307,23 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
class="my-3" class="my-3"
/> />
<UFormGroup <UFormField
label="Titel:" label="Titel:"
> >
<UInput v-model="itemInfo.title" disabled/> <UInput v-model="itemInfo.title" disabled/>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Beschreibung:" label="Beschreibung:"
class="mt-3" class="mt-3"
> >
<UInput v-model="itemInfo.description"/> <UInput v-model="itemInfo.description"/>
</UFormGroup> </UFormField>
<USeparator <USeparator
class="my-3" class="my-3"
/> />
<UFormGroup <UFormField
label="Vorlage auswählen" label="Vorlage auswählen"
> >
<USelectMenu <USelectMenu
@@ -2337,17 +2339,17 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
{{ texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert" }} {{ texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? texttemplates.find(i => i.text === itemInfo.startText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert" }}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Einleitung:" label="Einleitung:"
> >
<UTextarea <UTextarea
v-model="itemInfo.startText" v-model="itemInfo.startText"
:rows="6" :rows="6"
/> />
</UFormGroup> </UFormField>
<USeparator <USeparator
@@ -2460,7 +2462,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</template> </template>
<InputGroup class="w-full"> <InputGroup class="w-full">
<UFormGroup label="Artikelkategorie:"> <UFormField label="Artikelkategorie:">
<USelectMenu <USelectMenu
v-if="productcategories.length > 0" v-if="productcategories.length > 0"
:options="[{name: 'Nicht zugeordnet',id:'not set'},...productcategories]" :options="[{name: 'Nicht zugeordnet',id:'not set'},...productcategories]"
@@ -2468,7 +2470,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
option-attribute="name" option-attribute="name"
v-model="selectedProductcategorie" v-model="selectedProductcategorie"
/> />
</UFormGroup> </UFormField>
</InputGroup> </InputGroup>
<UTable <UTable
:rows="selectedProductcategorie !== 'not set' ? products.filter(i => i.productcategories.includes(selectedProductcategorie)) : products.filter(i => i.productcategories.length === 0)" :rows="selectedProductcategorie !== 'not set' ? products.filter(i => i.productcategories.includes(selectedProductcategorie)) : products.filter(i => i.productcategories.length === 0)"
@@ -2532,7 +2534,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</template> </template>
<InputGroup class="w-full"> <InputGroup class="w-full">
<UFormGroup label="Leistungskategorie:"> <UFormField label="Leistungskategorie:">
<USelectMenu <USelectMenu
v-if="servicecategories.length > 0" v-if="servicecategories.length > 0"
:options="[{name: 'Nicht zugeordnet',id:'not set'},...servicecategories]" :options="[{name: 'Nicht zugeordnet',id:'not set'},...servicecategories]"
@@ -2540,7 +2542,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
option-attribute="name" option-attribute="name"
v-model="selectedServicecategorie" v-model="selectedServicecategorie"
/> />
</UFormGroup> </UFormField>
</InputGroup> </InputGroup>
<UTable <UTable
:rows="selectedServicecategorie !== 'not set' ? services.filter(i => i.servicecategories.includes(selectedServicecategorie)) : services.filter(i => i.servicecategories.length === 0)" :rows="selectedServicecategorie !== 'not set' ? services.filter(i => i.servicecategories.includes(selectedServicecategorie)) : services.filter(i => i.servicecategories.length === 0)"
@@ -2671,7 +2673,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
@click="row.showEditDiesel = true" @click="row.showEditDiesel = true"
/> />
<UModal v-model:open="row.showEdit"> <UModal v-model:open="row.showEdit">
<UCard> <template #content>
<UCard>
<!-- <template #header> <!-- <template #header>
Zeile bearbeiten Zeile bearbeiten
</template>--> </template>-->
@@ -2685,7 +2688,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</div> </div>
</template> </template>
<InputGroup> <InputGroup>
<UFormGroup <UFormField
label="Anzahl:" label="Anzahl:"
class="flex-auto" class="flex-auto"
> >
@@ -2695,8 +2698,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
:step="units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).step : '1' " :step="units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).step : '1' "
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Einheit:" label="Einheit:"
class="flex-auto" class="flex-auto"
> >
@@ -2711,10 +2714,10 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
{{ units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).name : "Keine Einheit gewählt" }} {{ units.find(i => i.id === row.unit) ? units.find(i => i.id === row.unit).name : "Keine Einheit gewählt" }}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
</InputGroup> </InputGroup>
<UFormGroup <UFormField
label="Einzelpreis:" label="Einzelpreis:"
v-if="itemInfo.type !== 'deliveryNotes'" v-if="itemInfo.type !== 'deliveryNotes'"
> >
@@ -2743,8 +2746,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
class="text-gray-500 dark:text-gray-400 text-xs"> </span> class="text-gray-500 dark:text-gray-400 text-xs"> </span>
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Umsatzsteuer:" label="Umsatzsteuer:"
class="mt-3" class="mt-3"
v-if="itemInfo.type !== 'deliveryNotes'" v-if="itemInfo.type !== 'deliveryNotes'"
@@ -2761,9 +2764,9 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
{{ row.taxPercent }} % {{ row.taxPercent }} %
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Rabatt:" label="Rabatt:"
class="mt-3" class="mt-3"
v-if="itemInfo.type !== 'deliveryNotes'" v-if="itemInfo.type !== 'deliveryNotes'"
@@ -2778,25 +2781,25 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
<span class="text-gray-500 dark:text-gray-400 text-xs">%</span> <span class="text-gray-500 dark:text-gray-400 text-xs">%</span>
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<InputGroup class="w-full mt-3"> <InputGroup class="w-full mt-3">
<UFormGroup <UFormField
label="Optional:" label="Optional:"
> >
<UToggle <USwitch
:disabled="row.alternative" :disabled="row.alternative"
v-model="row.optional" v-model="row.optional"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
class="ml-3" class="ml-3"
label="Alternativ:" label="Alternativ:"
> >
<UToggle <USwitch
:disabled="row.optional" :disabled="row.optional"
v-model="row.alternative" v-model="row.alternative"
/> />
</UFormGroup> </UFormField>
</InputGroup> </InputGroup>
<UAlert <UAlert
@@ -2819,7 +2822,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
</li> </li>
</ul> </ul>
<UFormGroup <UFormField
label="Beschreibung:" label="Beschreibung:"
class="mt-3" class="mt-3"
> >
@@ -2829,7 +2832,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
> >
</UTextarea> </UTextarea>
</UFormGroup> </UFormField>
<!-- <template #footer> <!-- <template #footer>
<UButton <UButton
@@ -2838,16 +2841,16 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
Speichern Speichern
</UButton> </UButton>
</template>--> </template>-->
</UCard> </UCard>
</template>
</UModal> </UModal>
<UModal v-model:open="row.showEditDiesel"> <UModal v-model:open="row.showEditDiesel">
<UCard> <template #content>
<template #header> <UCard>
Dieselverbrauch bearbeiten <template #header>
</template> Dieselverbrauch bearbeiten
<UFormGroup </template>
<UFormField
label="Menge Diesel:" label="Menge Diesel:"
> >
<UInput <UInput
@@ -2861,8 +2864,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
L L
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Preis Diesel:" label="Preis Diesel:"
> >
<UInput <UInput
@@ -2875,8 +2878,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
€/L €/L
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Menge AdBlue:" label="Menge AdBlue:"
> >
<UInput <UInput
@@ -2888,8 +2891,8 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
L L
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Preis AdBlue:" label="Preis AdBlue:"
> >
<UInput <UInput
@@ -2901,18 +2904,17 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
€/L €/L
</template> </template>
</UInput> </UInput>
</UFormGroup> </UFormField>
<template #footer> <template #footer>
<UButton <UButton
@click="row.showEditDiesel = false, @click="row.showEditDiesel = false,
processDieselPosition()" processDieselPosition()"
> >
Speichern Speichern
</UButton> </UButton>
</template> </template>
</UCard> </UCard>
</template>
</UModal> </UModal>
</td> </td>
<td <td
@@ -3155,7 +3157,7 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
class="my-3" class="my-3"
/> />
<UFormGroup <UFormField
label="Vorlage auswählen" label="Vorlage auswählen"
> >
<USelectMenu <USelectMenu
@@ -3171,16 +3173,16 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
{{texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert"}} {{texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)) ? texttemplates.find(i => i.text === itemInfo.endText && (itemInfo.type === "serialInvoices" ? i.documentType === "invoices" : i.documentType === itemInfo.type)).name : "Keine Vorlage ausgewählt oder Vorlage verändert"}}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Nachbemerkung:" label="Nachbemerkung:"
> >
<UTextarea <UTextarea
v-model="itemInfo.endText" v-model="itemInfo.endText"
:rows="6" :rows="6"
/> />
</UFormGroup> </UFormField>
</div> </div>
<div v-else-if="item.label === 'Vorschau'"> <div v-else-if="item.label === 'Vorschau'">
<PDFViewer <PDFViewer

View File

@@ -55,82 +55,82 @@
{{ getRowsForTab(item.key).length }} {{ getRowsForTab(item.key).length }}
</UBadge> </UBadge>
</template> </template>
<template #item="{item}"> <template #content="{item}">
<div style="height: 80vh; overflow-y: scroll"> <div style="height: 80vh; overflow-y: scroll">
<UTable <UTable
:columns="normalizeTableColumns(getColumnsForTab(item.key))" :columns="normalizeTableColumns(getColumnsForTab(item.key))"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }"
:data="getRowsForTab(item.key)" :data="getRowsForTab(item.key)"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
class="w-full" class="w-full"
@select="selectItem" :on-select="selectItem"
> >
<template #type-data="{row}"> <template #type-cell="{row}">
<span v-if="row.type === 'cancellationInvoices'" class="text-cyan-500">{{ <span v-if="row.original.type === 'cancellationInvoices'" class="text-cyan-500">{{
dataStore.documentTypesForCreation[row.type].labelSingle dataStore.documentTypesForCreation[row.original.type].labelSingle
}} für {{ filteredRows.find(i => row.createddocument?.id === i.id)?.documentNumber }}</span> }} für {{ filteredRows.find(i => row.original.createddocument?.id === i.id)?.documentNumber }}</span>
<span v-else>{{ dataStore.documentTypesForCreation[row.type].labelSingle }}</span> <span v-else>{{ dataStore.documentTypesForCreation[row.original.type].labelSingle }}</span>
</template> </template>
<template #state-data="{row}"> <template #state-cell="{row}">
<span v-if="row.state === 'Entwurf'" class="text-rose-500">{{ row.state }}</span> <span v-if="row.original.state === 'Entwurf'" class="text-rose-500">{{ row.original.state }}</span>
<span <span
v-if="row.state === 'Gebucht' && !hasCancellationInvoice(row)" v-if="row.original.state === 'Gebucht' && !hasCancellationInvoice(row.original)"
class="text-primary-500" class="text-primary-500"
> >
{{ row.state }} {{ row.original.state }}
</span> </span>
<span <span
v-else-if="row.state === 'Gebucht' && hasCancellationInvoice(row) && ['invoices','advanceInvoices'].includes(row.type)" v-else-if="row.original.state === 'Gebucht' && hasCancellationInvoice(row.original) && ['invoices','advanceInvoices'].includes(row.original.type)"
class="text-cyan-500" class="text-cyan-500"
> >
Storniert mit {{ getCancellationInvoice(row)?.documentNumber }} Storniert mit {{ getCancellationInvoice(row.original)?.documentNumber }}
</span> </span>
<span v-else-if="row.state === 'Gebucht'" class="text-primary-500">{{ row.state }}</span> <span v-else-if="row.original.state === 'Gebucht'" class="text-primary-500">{{ row.original.state }}</span>
</template> </template>
<template #partner-data="{row}"> <template #partner-cell="{row}">
<span v-if="row.customer && row.customer.name.length < 21">{{ row.customer ? row.customer.name : "" }}</span> <span v-if="row.original.customer && row.original.customer.name.length < 21">{{ row.original.customer ? row.original.customer.name : "" }}</span>
<UTooltip v-else-if="row.customer && row.customer.name.length > 20" :text="row.customer.name"> <UTooltip v-else-if="row.original.customer && row.original.customer.name.length > 20" :text="row.original.customer.name">
{{ row.customer.name.substring(0, 20) }}... {{ row.original.customer.name.substring(0, 20) }}...
</UTooltip> </UTooltip>
</template> </template>
<template #reference-data="{row}"> <template #reference-cell="{row}">
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{ row.documentNumber }}</span> <span v-if="row.original === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{ row.original.documentNumber }}</span>
<span v-else>{{ row.documentNumber }}</span> <span v-else>{{ row.original.documentNumber }}</span>
</template> </template>
<template #date-data="{row}"> <template #date-cell="{row}">
<span v-if="row.date">{{ row.date ? dayjs(row.date).format("DD.MM.YY") : '' }}</span> <span v-if="row.original.date">{{ row.original.date ? dayjs(row.original.date).format("DD.MM.YY") : '' }}</span>
<span v-if="row.documentDate">{{ row.documentDate ? dayjs(row.documentDate).format("DD.MM.YY") : '' }}</span> <span v-if="row.original.documentDate">{{ row.original.documentDate ? dayjs(row.original.documentDate).format("DD.MM.YY") : '' }}</span>
</template> </template>
<template #dueDate-data="{row}"> <template #dueDate-cell="{row}">
<span <span
v-if="row.state === 'Gebucht' && row.paymentDays && ['invoices','advanceInvoices'].includes(row.type) && !hasCancellationInvoice(row)" v-if="row.original.state === 'Gebucht' && row.original.paymentDays && ['invoices','advanceInvoices'].includes(row.original.type) && !hasCancellationInvoice(row.original)"
:class="dayjs(row.documentDate).add(row.paymentDays,'day').diff(dayjs()) <= 0 && !isPaid(row) ? ['text-rose-500'] : '' " :class="dayjs(row.original.documentDate).add(row.original.paymentDays,'day').diff(dayjs()) <= 0 && !isPaid(row.original) ? ['text-rose-500'] : '' "
> >
{{ row.documentDate ? dayjs(row.documentDate).add(row.paymentDays, 'day').format("DD.MM.YY") : '' }} {{ row.original.documentDate ? dayjs(row.original.documentDate).add(row.original.paymentDays, 'day').format("DD.MM.YY") : '' }}
</span> </span>
</template> </template>
<template #paid-data="{row}"> <template #paid-cell="{row}">
<div <div
v-if="(row.type === 'invoices' ||row.type === 'advanceInvoices') && row.state === 'Gebucht' && !hasCancellationInvoice(row)"> v-if="(row.original.type === 'invoices' ||row.original.type === 'advanceInvoices') && row.original.state === 'Gebucht' && !hasCancellationInvoice(row.original)">
<span v-if="useSum().getIsPaid(row,items)" class="text-primary-500">Bezahlt</span> <span v-if="useSum().getIsPaid(row.original,items)" class="text-primary-500">Bezahlt</span>
<span v-else class="text-rose-600">Offen</span> <span v-else class="text-rose-600">Offen</span>
</div> </div>
</template> </template>
<template #amount-data="{row}"> <template #amount-cell="{row}">
<span v-if="row.type !== 'deliveryNotes'">{{ displayCurrency(useSum().getCreatedDocumentSum(row, items)) }}</span> <span v-if="row.original.type !== 'deliveryNotes'">{{ displayCurrency(useSum().getCreatedDocumentSum(row.original, items)) }}</span>
</template> </template>
<template #amountOpen-data="{row}"> <template #amountOpen-cell="{row}">
<span <span
v-if="!['deliveryNotes','cancellationInvoices','quotes','confirmationOrders'].includes(row.type) && row.state !== 'Entwurf' && !hasCancellationInvoice(row) && !useSum().getIsPaid(row,items) "> v-if="!['deliveryNotes','cancellationInvoices','quotes','confirmationOrders'].includes(row.original.type) && row.original.state !== 'Entwurf' && !hasCancellationInvoice(row.original) && !useSum().getIsPaid(row.original,items) ">
{{ displayCurrency(useSum().getCreatedDocumentOpenAmount(row, items)) }} {{ displayCurrency(useSum().getCreatedDocumentOpenAmount(row.original, items)) }}
</span> </span>
</template> </template>
</UTable> </UTable>

View File

@@ -44,15 +44,15 @@
<USelectMenu <USelectMenu
v-model="selectedFilters" v-model="selectedFilters"
icon="i-heroicons-adjustments-horizontal-solid" icon="i-heroicons-adjustments-horizontal-solid"
:options="filterOptions" :items="filterOptions"
option-attribute="name" label-key="name"
value-attribute="name" value-key="name"
multiple multiple
class="hidden lg:block" class="hidden lg:block"
:color="selectedFilters.length > 0 ? 'primary' : 'white'" :color="selectedFilters.length > 0 ? 'primary' : 'white'"
:ui-menu="{ width: 'min-w-max' }" :content="{ width: 'min-w-max' }"
> >
<template #label> <template #default>
Filter Filter
</template> </template>
</USelectMenu> </USelectMenu>
@@ -97,12 +97,12 @@
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
class="w-full" class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(row) => router.push(`/createDocument/edit/${row.id}`)" :on-select="(row) => router.push(`/createDocument/edit/${row.id}`)"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }"
> >
<template #actions-data="{ row }"> <template #actions-cell="{ row }">
<div @click.stop> <div @click.stop>
<UDropdown :items="getActionItems(row)" :popper="{ placement: 'bottom-end' }"> <UDropdown :items="getActionItems(row.original)" :popper="{ placement: 'bottom-end' }">
<UButton <UButton
color="gray" color="gray"
variant="ghost" variant="ghost"
@@ -112,51 +112,52 @@
</div> </div>
</template> </template>
<template #type-data="{row}"> <template #type-cell="{row}">
{{dataStore.documentTypesForCreation[row.type].labelSingle}} {{dataStore.documentTypesForCreation[row.original.type].labelSingle}}
</template> </template>
<template #partner-data="{row}"> <template #partner-cell="{row}">
<span v-if="row.customer">{{row.customer ? row.customer.name : ""}}</span> <span v-if="row.original.customer">{{row.original.customer ? row.original.customer.name : ""}}</span>
</template> </template>
<template #amount-data="{row}"> <template #amount-cell="{row}">
{{displayCurrency(calculateDocSum(row))}} {{displayCurrency(calculateDocSum(row.original))}}
</template> </template>
<template #serialConfig.active-data="{row}"> <template #serialConfig.active-cell="{row}">
<span v-if="row.serialConfig.active" class="text-primary">Ja</span> <span v-if="row.original.serialConfig.active" class="text-primary">Ja</span>
<span v-else class="text-rose-600">Nein</span> <span v-else class="text-rose-600">Nein</span>
</template> </template>
<template #contract-data="{row}"> <template #contract-cell="{row}">
<span v-if="row.contract">{{row.contract.contractNumber}} - {{row.contract.name}}</span> <span v-if="row.original.contract">{{row.original.contract.contractNumber}} - {{row.original.contract.name}}</span>
</template> </template>
<template #serialConfig.intervall-data="{row}"> <template #serialConfig.intervall-cell="{row}">
<span v-if="row.serialConfig?.intervall === 'monatlich'">Monatlich</span> <span v-if="row.original.serialConfig?.intervall === 'monatlich'">Monatlich</span>
<span v-if="row.serialConfig?.intervall === 'vierteljährlich'">Quartalsweise</span> <span v-if="row.original.serialConfig?.intervall === 'vierteljährlich'">Quartalsweise</span>
</template> </template>
<template #payment_type-data="{row}"> <template #payment_type-cell="{row}">
<span v-if="row.payment_type === 'transfer'">Überweisung</span> <span v-if="row.original.payment_type === 'transfer'">Überweisung</span>
<span v-else-if="row.payment_type === 'direct-debit'">SEPA - Einzug</span> <span v-else-if="row.original.payment_type === 'direct-debit'">SEPA - Einzug</span>
</template> </template>
</UTable> </UTable>
<UModal v-model:open="showExecutionModal" :ui="{ width: 'sm:max-w-4xl' }"> <UModal v-model:open="showExecutionModal" :ui="{ width: 'sm:max-w-4xl' }">
<UCard> <template #content>
<template #header> <UCard>
<div class="flex items-center justify-between"> <template #header>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white"> <div class="flex items-center justify-between">
Serienrechnungen manuell ausführen <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
</h3> Serienrechnungen manuell ausführen
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showExecutionModal = false" /> </h3>
</div> <UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showExecutionModal = false" />
</template> </div>
</template>
<div class="space-y-4"> <div class="space-y-4">
<UFormGroup label="Ausführungsdatum (Belegdatum)" help="Dieses Datum steuert auch den Leistungszeitraum (z.B. Vormonat bei 'Rückwirkend')."> <UFormField label="Ausführungsdatum (Belegdatum)" help="Dieses Datum steuert auch den Leistungszeitraum (z.B. Vormonat bei 'Rückwirkend').">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UInput type="date" v-model="executionDate" class="flex-1" /> <UInput type="date" v-model="executionDate" class="flex-1" />
<UButton color="gray" variant="soft" label="Heute" @click="setExecutionDateToToday" /> <UButton color="gray" variant="soft" label="Heute" @click="setExecutionDateToToday" />
</div> </div>
</UFormGroup> </UFormField>
<USeparator label="Vorlagen auswählen" /> <USeparator label="Vorlagen auswählen" />
@@ -172,9 +173,9 @@
<USelectMenu <USelectMenu
v-model="selectedExecutionIntervall" v-model="selectedExecutionIntervall"
:options="executionIntervallOptions" :items="executionIntervallOptions"
option-attribute="label" label-key="label"
value-attribute="value" value-key="value"
size="sm" size="sm"
class="w-full sm:w-52" class="w-full sm:w-52"
/> />
@@ -208,20 +209,20 @@
:columns="normalizeTableColumns(executionColumns)" :columns="normalizeTableColumns(executionColumns)"
:ui="{ th: { base: 'whitespace-nowrap' } }" :ui="{ th: { base: 'whitespace-nowrap' } }"
> >
<template #partner-data="{row}"> <template #partner-cell="{row}">
{{row.customer ? row.customer.name : "-"}} {{row.original.customer ? row.original.customer.name : "-"}}
</template> </template>
<template #amount-data="{row}"> <template #amount-cell="{row}">
{{displayCurrency(calculateDocSum(row))}} {{displayCurrency(calculateDocSum(row.original))}}
</template> </template>
<template #serialConfig.intervall-data="{row}"> <template #serialConfig.intervall-cell="{row}">
{{ getIntervallLabel(row.serialConfig?.intervall) }} {{ getIntervallLabel(row.original.serialConfig?.intervall) }}
</template> </template>
<template #contract-data="{row}"> <template #contract-cell="{row}">
{{row.contract?.contractNumber}} - {{row.contract?.name}} {{row.original.contract?.contractNumber}} - {{row.original.contract?.name}}
</template> </template>
<template #plant-data="{row}"> <template #plant-cell="{row}">
{{ row.plant?.name || "-" }} {{ row.original.plant?.name || "-" }}
</template> </template>
</UTable> </UTable>
</div> </div>
@@ -231,58 +232,61 @@
</div> </div>
</div> </div>
<template #footer> <template #footer>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<UButton color="white" @click="showExecutionModal = false">Abbrechen</UButton> <UButton color="white" @click="showExecutionModal = false">Abbrechen</UButton>
<UButton <UButton
color="primary" color="primary"
:loading="isExecuting" :loading="isExecuting"
:disabled="selectedExecutionRows.length === 0" :disabled="selectedExecutionRows.length === 0"
@click="executeSerialInvoices" @click="executeSerialInvoices"
> >
Jetzt ausführen Jetzt ausführen
</UButton> </UButton>
</div> </div>
</template> </template>
</UCard> </UCard>
</template>
</UModal> </UModal>
<USlideover v-model:open="showExecutionsSlideover" :ui="{ width: 'w-screen max-w-md' }"> <USlideover v-model:open="showExecutionsSlideover" :ui="{ width: 'w-screen max-w-md' }">
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }"> <template #body>
<template #header> <UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<div class="flex items-center justify-between"> <template #header>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white"> <div class="flex items-center justify-between">
Alle Ausführungen <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
</h3> Alle Ausführungen
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showExecutionsSlideover = false" /> </h3>
</div> <UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showExecutionsSlideover = false" />
</template>
<div class="space-y-4 overflow-y-auto h-full p-1">
<div v-if="executionsLoading" class="flex justify-center py-4">
<UIcon name="i-heroicons-arrow-path" class="animate-spin text-gray-400 w-6 h-6" />
</div>
<div v-else-if="completedExecutions.length === 0" class="text-center text-gray-500 py-8">
Keine abgeschlossenen Ausführungen gefunden.
</div>
<div v-for="exec in completedExecutions" :key="exec.id" class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
<div class="flex justify-between items-start mb-2">
<span class="text-sm font-semibold">{{ dayjs(exec.createdAt).format('DD.MM.YYYY HH:mm') }}</span>
<UBadge :color="getStatusColor(exec.status)" variant="subtle" size="xs">
{{ getStatusLabel(exec.status) }}
</UBadge>
</div> </div>
<div class="text-xs text-gray-500 grid grid-cols-2 gap-2 mt-2"> </template>
<div>
<UIcon name="i-heroicons-check-circle" class="text-green-500 w-3 h-3 align-text-bottom" /> <div class="space-y-4 overflow-y-auto h-full p-1">
{{exec.summary}} <div v-if="executionsLoading" class="flex justify-center py-4">
<UIcon name="i-heroicons-arrow-path" class="animate-spin text-gray-400 w-6 h-6" />
</div>
<div v-else-if="completedExecutions.length === 0" class="text-center text-gray-500 py-8">
Keine abgeschlossenen Ausführungen gefunden.
</div>
<div v-for="exec in completedExecutions" :key="exec.id" class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
<div class="flex justify-between items-start mb-2">
<span class="text-sm font-semibold">{{ dayjs(exec.createdAt).format('DD.MM.YYYY HH:mm') }}</span>
<UBadge :color="getStatusColor(exec.status)" variant="subtle" size="xs">
{{ getStatusLabel(exec.status) }}
</UBadge>
</div>
<div class="text-xs text-gray-500 grid grid-cols-2 gap-2 mt-2">
<div>
<UIcon name="i-heroicons-check-circle" class="text-green-500 w-3 h-3 align-text-bottom" />
{{exec.summary}}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </UCard>
</UCard> </template>
</USlideover> </USlideover>
</template> </template>

View File

@@ -13,6 +13,7 @@ const itemInfo = ref({
}) })
const showDocument = ref(false) const showDocument = ref(false)
const uri = ref("") const uri = ref("")
const openTab = ref("0")
const setupPage = async () => { const setupPage = async () => {
letterheads.value = await useEntities("letterheads").select("*") letterheads.value = await useEntities("letterheads").select("*")
@@ -23,7 +24,9 @@ const setupPage = async () => {
setupPage() setupPage()
const onChangeTab = (index) => { const onChangeTab = (index) => {
if(index === 1) { openTab.value = String(index)
if(String(index) === "1") {
generateDocument() generateDocument()
} }
} }
@@ -78,8 +81,8 @@ const contentChanged = (content) => {
<UDashboardNavbar title="Anschreiben bearbeiten"/> <UDashboardNavbar title="Anschreiben bearbeiten"/>
{{itemInfo}} {{itemInfo}}
<UDashboardPanelContent> <UDashboardPanelContent>
<UTabs @change="onChangeTab" :items="[{label: 'Editor'},{label: 'Vorschau'}]"> <UTabs v-model="openTab" @update:model-value="onChangeTab" :items="[{label: 'Editor'},{label: 'Vorschau'}]">
<template #item="{item}"> <template #content="{item}">
<div v-if="item.label === 'Editor'"> <div v-if="item.label === 'Editor'">
<Tiptap <Tiptap
class="mt-3" class="mt-3"

View File

@@ -210,34 +210,34 @@ const sendEmail = async () => {
<div class="scrollContainer mt-3"> <div class="scrollContainer mt-3">
<div class="flex-col flex w-full"> <div class="flex-col flex w-full">
<UFormGroup <UFormField
label="Absender" label="Absender"
> >
<USelectMenu <USelectMenu
:options="emailAccounts" :items="emailAccounts"
option-attribute="email" label-key="email"
value-attribute="id" value-key="id"
v-model="emailData.account" v-model="emailData.account"
/> />
</UFormGroup> </UFormField>
<USeparator class="my-3"/> <USeparator class="my-3"/>
<UFormGroup <UFormField
label="Empfänger" label="Empfänger"
> >
<UInput <UInput
class="w-full my-1" class="w-full my-1"
v-model="emailData.to" v-model="emailData.to"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Kopie" label="Kopie"
> >
<UInput <UInput
class="w-full my-1" class="w-full my-1"
v-model="emailData.cc" v-model="emailData.cc"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Blindkopie" label="Blindkopie"
> >
<UInput <UInput
@@ -245,15 +245,15 @@ const sendEmail = async () => {
placeholder="" placeholder=""
v-model="emailData.bcc" v-model="emailData.bcc"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Betreff" label="Betreff"
> >
<UInput <UInput
class="w-full my-1" class="w-full my-1"
v-model="emailData.subject" v-model="emailData.subject"
/> />
</UFormGroup> </UFormField>
</div> </div>
<USeparator class="my-3"/> <USeparator class="my-3"/>
<div id="parentAttachments" class="flex flex-col justify-center mt-3"> <div id="parentAttachments" class="flex flex-col justify-center mt-3">

View File

@@ -120,28 +120,29 @@ const createExport = async () => {
{ key: 'download', label: 'Download' }, { key: 'download', label: 'Download' },
])" ])"
> >
<template #created_at-data="{row}"> <template #created_at-cell="{row}">
{{dayjs(row.created_at).format("DD.MM.YYYY HH:mm")}} {{dayjs(row.original.created_at).format("DD.MM.YYYY HH:mm")}}
</template> </template>
<template #start_date-data="{row}"> <template #start_date-cell="{row}">
{{dayjs(row.start_date).format("DD.MM.YYYY HH:mm")}} {{dayjs(row.original.start_date).format("DD.MM.YYYY HH:mm")}}
</template> </template>
<template #end_date-data="{row}"> <template #end_date-cell="{row}">
{{dayjs(row.end_date).format("DD.MM.YYYY HH:mm")}} {{dayjs(row.original.end_date).format("DD.MM.YYYY HH:mm")}}
</template> </template>
<template #valid_until-data="{row}"> <template #valid_until-cell="{row}">
{{dayjs(row.valid_until).format("DD.MM.YYYY HH:mm")}} {{dayjs(row.original.valid_until).format("DD.MM.YYYY HH:mm")}}
</template> </template>
<template #download-data="{row}"> <template #download-cell="{row}">
<UButton @click="downloadFile(row)">Download</UButton> <UButton @click="downloadFile(row.original)">Download</UButton>
</template> </template>
</UTable> </UTable>
<UModal v-model:open="showCreateExportModal"> <UModal v-model:open="showCreateExportModal">
<UCard> <template #content>
<template #header> <UCard>
Export erstellen <template #header>
</template> Export erstellen
</template>
<div class="mb-6 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700"> <div class="mb-6 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700">
<div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Schnellauswahl</div> <div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Schnellauswahl</div>
@@ -180,7 +181,7 @@ const createExport = async () => {
</div> </div>
</div> </div>
<div class="flex gap-4"> <div class="flex gap-4">
<UFormGroup label="Start:" class="flex-1"> <UFormField label="Start:" class="flex-1">
<UPopover :popper="{ placement: 'bottom-start' }"> <UPopover :popper="{ placement: 'bottom-start' }">
<UButton <UButton
icon="i-heroicons-calendar-days-20-solid" icon="i-heroicons-calendar-days-20-solid"
@@ -192,9 +193,9 @@ const createExport = async () => {
<LazyDatePicker v-model="createExportData.start_date" @close="close" /> <LazyDatePicker v-model="createExportData.start_date" @close="close" />
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
<UFormGroup label="Ende:" class="flex-1"> <UFormField label="Ende:" class="flex-1">
<UPopover :popper="{ placement: 'bottom-start' }"> <UPopover :popper="{ placement: 'bottom-start' }">
<UButton <UButton
icon="i-heroicons-calendar-days-20-solid" icon="i-heroicons-calendar-days-20-solid"
@@ -206,18 +207,19 @@ const createExport = async () => {
<LazyDatePicker v-model="createExportData.end_date" @close="close" /> <LazyDatePicker v-model="createExportData.end_date" @close="close" />
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
</div> </div>
<template #footer> <template #footer>
<div class="flex justify-end"> <div class="flex justify-end">
<UButton @click="createExport"> <UButton @click="createExport">
Erstellen Erstellen
</UButton> </UButton>
</div> </div>
</template> </template>
</UCard> </UCard>
</template>
</UModal> </UModal>
</template> </template>

View File

@@ -350,8 +350,8 @@ const syncdokubox = async () => {
<UBreadcrumb :links="breadcrumbLinks"/> <UBreadcrumb :links="breadcrumbLinks"/>
</template> </template>
<template #right> <template #right>
<USelectMenu v-model="displayMode" :options="displayModes" value-attribute="key" class="w-32" :ui-menu="{ zIndex: 'z-50' }"> <USelectMenu v-model="displayMode" :items="displayModes" value-key="key" class="w-32" :content="{ zIndex: 'z-50' }">
<template #label> <template #default>
<UIcon :name="displayModes.find(i => i.key === displayMode).icon" class="w-4 h-4"/> <UIcon :name="displayModes.find(i => i.key === displayMode).icon" class="w-4 h-4"/>
<span>{{ displayModes.find(i => i.key === displayMode).label }}</span> <span>{{ displayModes.find(i => i.key === displayMode).label }}</span>
</template> </template>
@@ -454,29 +454,30 @@ const syncdokubox = async () => {
</UDashboardPanelContent> </UDashboardPanelContent>
<UModal v-model:open="createFolderModalOpen"> <UModal v-model:open="createFolderModalOpen">
<UCard> <template #content>
<template #header><h3 class="font-bold">Ordner erstellen</h3></template> <UCard>
<template #header><h3 class="font-bold">Ordner erstellen</h3></template>
<div class="space-y-4"> <div class="space-y-4">
<UFormGroup label="Name" required> <UFormField label="Name" required>
<UInput v-model="createFolderData.name" autofocus @keyup.enter="createFolder"/> <UInput v-model="createFolderData.name" autofocus @keyup.enter="createFolder"/>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Standard Dateityp (Tag)" label="Standard Dateityp (Tag)"
:help="isParentTypeMandatory ? 'Vom übergeordneten Ordner vorgegeben' : ''" :help="isParentTypeMandatory ? 'Vom übergeordneten Ordner vorgegeben' : ''"
> >
<USelectMenu <USelectMenu
v-model="createFolderData.standardFiletype" v-model="createFolderData.standardFiletype"
:options="filetags" :items="filetags"
value-attribute="id" value-key="id"
option-attribute="name" label-key="name"
placeholder="Kein Standardtyp" placeholder="Kein Standardtyp"
searchable searchable
clear-search-on-close clear-search-on-close
:disabled="isParentTypeMandatory" :disabled="isParentTypeMandatory"
/> />
</UFormGroup> </UFormField>
<UCheckbox <UCheckbox
v-model="createFolderData.standardFiletypeIsOptional" v-model="createFolderData.standardFiletypeIsOptional"
@@ -485,27 +486,30 @@ const syncdokubox = async () => {
/> />
</div> </div>
<template #footer> <template #footer>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<UButton color="gray" @click="createFolderModalOpen = false">Abbrechen</UButton> <UButton color="gray" @click="createFolderModalOpen = false">Abbrechen</UButton>
<UButton color="primary" @click="createFolder">Erstellen</UButton> <UButton color="primary" @click="createFolder">Erstellen</UButton>
</div> </div>
</template> </template>
</UCard> </UCard>
</template>
</UModal> </UModal>
<UModal v-model:open="renameModalOpen"> <UModal v-model:open="renameModalOpen">
<UCard> <template #content>
<template #header><h3 class="font-bold">Umbenennen</h3></template> <UCard>
<UFormGroup label="Neuer Name"> <template #header><h3 class="font-bold">Umbenennen</h3></template>
<UInput v-model="renameData.name" autofocus @keyup.enter="updateName"/> <UFormField label="Neuer Name">
</UFormGroup> <UInput v-model="renameData.name" autofocus @keyup.enter="updateName"/>
<template #footer> </UFormField>
<div class="flex justify-end gap-2"> <template #footer>
<UButton color="gray" @click="renameModalOpen = false">Abbrechen</UButton> <div class="flex justify-end gap-2">
<UButton color="primary" @click="updateName">Speichern</UButton> <UButton color="gray" @click="renameModalOpen = false">Abbrechen</UButton>
</div> <UButton color="primary" @click="updateName">Speichern</UButton>
</template> </div>
</UCard> </template>
</UCard>
</template>
</UModal> </UModal>
</template> </template>

View File

@@ -330,7 +330,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
</template> </template>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<UFormGroup label="Lieferant / Partner" class="md:col-span-2"> <UFormField label="Lieferant / Partner" class="md:col-span-2">
<div class="flex gap-2"> <div class="flex gap-2">
<USelectMenu <USelectMenu
class="w-full" class="w-full"
@@ -365,37 +365,37 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
@return-data="(data) => itemInfo.vendor = data.id" @return-data="(data) => itemInfo.vendor = data.id"
/> />
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup label="Rechnungsnummer"> <UFormField label="Rechnungsnummer">
<UInput v-model="itemInfo.reference" icon="i-heroicons-hashtag" :disabled="mode === 'show'" /> <UInput v-model="itemInfo.reference" icon="i-heroicons-hashtag" :disabled="mode === 'show'" />
</UFormGroup> </UFormField>
<UFormGroup label="Zahlart"> <UFormField label="Zahlart">
<USelectMenu v-model="itemInfo.paymentType" :options="['Überweisung', 'Lastschrift', 'Kreditkarte', 'PayPal', 'Bar', 'Sonstiges']" :disabled="mode === 'show'" /> <USelectMenu v-model="itemInfo.paymentType" :options="['Überweisung', 'Lastschrift', 'Kreditkarte', 'PayPal', 'Bar', 'Sonstiges']" :disabled="mode === 'show'" />
</UFormGroup> </UFormField>
<UFormGroup label="Rechnungsdatum"> <UFormField label="Rechnungsdatum">
<UPopover :popper="{ placement: 'bottom-start' }"> <UPopover :popper="{ placement: 'bottom-start' }">
<UButton block color="white" icon="i-heroicons-calendar" :label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : '-'" :disabled="mode === 'show'" /> <UButton block color="white" icon="i-heroicons-calendar" :label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : '-'" :disabled="mode === 'show'" />
<template #panel="{ close }"> <template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.date" @close="() => { if(!itemInfo.dueDate) itemInfo.dueDate = itemInfo.date; close() }" /> <LazyDatePicker v-model="itemInfo.date" @close="() => { if(!itemInfo.dueDate) itemInfo.dueDate = itemInfo.date; close() }" />
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
<UFormGroup label="Fälligkeitsdatum"> <UFormField label="Fälligkeitsdatum">
<UPopover :popper="{ placement: 'bottom-start' }"> <UPopover :popper="{ placement: 'bottom-start' }">
<UButton block color="white" icon="i-heroicons-calendar" :label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : '-'" :disabled="mode === 'show'" /> <UButton block color="white" icon="i-heroicons-calendar" :label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : '-'" :disabled="mode === 'show'" />
<template #panel="{ close }"> <template #panel="{ close }">
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" /> <LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
</template> </template>
</UPopover> </UPopover>
</UFormGroup> </UFormField>
<UFormGroup label="Beschreibung / Notiz" class="md:col-span-2"> <UFormField label="Beschreibung / Notiz" class="md:col-span-2">
<UTextarea v-model="itemInfo.description" :rows="2" autoresize :disabled="mode === 'show'" /> <UTextarea v-model="itemInfo.description" :rows="2" autoresize :disabled="mode === 'show'" />
</UFormGroup> </UFormField>
</div> </div>
</UCard> </UCard>
@@ -404,7 +404,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
<h3 class="font-semibold text-lg">Positionen</h3> <h3 class="font-semibold text-lg">Positionen</h3>
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-2 text-sm">
<span :class="{'font-bold': !useNetMode, 'opacity-50': useNetMode}">Brutto</span> <span :class="{'font-bold': !useNetMode, 'opacity-50': useNetMode}">Brutto</span>
<UToggle v-model="useNetMode" color="primary" :disabled="mode === 'show'" /> <USwitch v-model="useNetMode" color="primary" :disabled="mode === 'show'" />
<span :class="{'font-bold': useNetMode, 'opacity-50': !useNetMode}">Netto Eingabe</span> <span :class="{'font-bold': useNetMode, 'opacity-50': !useNetMode}">Netto Eingabe</span>
</div> </div>
</div> </div>
@@ -428,7 +428,7 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
<div class="grid grid-cols-12 gap-3"> <div class="grid grid-cols-12 gap-3">
<div class="col-span-12 md:col-span-6"> <div class="col-span-12 md:col-span-6">
<UFormGroup label="Konto / Kategorie"> <UFormField label="Konto / Kategorie">
<USelectMenu <USelectMenu
v-model="item.account" v-model="item.account"
:options="accounts" :options="accounts"
@@ -446,11 +446,11 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
{{ accounts.find(a => a.id === item.account)?.label || 'Auswählen' }} {{ accounts.find(a => a.id === item.account)?.label || 'Auswählen' }}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-12 md:col-span-6"> <div class="col-span-12 md:col-span-6">
<UFormGroup label="Kostenstelle"> <UFormField label="Kostenstelle">
<USelectMenu <USelectMenu
v-model="item.costCentre" v-model="item.costCentre"
:options="costcentres" :options="costcentres"
@@ -464,11 +464,11 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
{{ costcentres.find(c => c.id === item.costCentre)?.name || 'Keine' }} {{ costcentres.find(c => c.id === item.costCentre)?.name || 'Keine' }}
</template> </template>
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-12 md:col-span-3"> <div class="col-span-12 md:col-span-3">
<UFormGroup label="Betrag (Netto)"> <UFormField label="Betrag (Netto)">
<UInput <UInput
type="number" type="number"
step="0.01" step="0.01"
@@ -478,11 +478,11 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
> >
<template #trailing>€</template> <template #trailing>€</template>
</UInput> </UInput>
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-12 md:col-span-3"> <div class="col-span-12 md:col-span-3">
<UFormGroup label="Betrag (Brutto)"> <UFormField label="Betrag (Brutto)">
<UInput <UInput
type="number" type="number"
step="0.01" step="0.01"
@@ -492,11 +492,11 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
> >
<template #trailing>€</template> <template #trailing>€</template>
</UInput> </UInput>
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-6 md:col-span-3"> <div class="col-span-6 md:col-span-3">
<UFormGroup label="Steuerschlüssel"> <UFormField label="Steuerschlüssel">
<USelectMenu <USelectMenu
v-model="item.taxType" v-model="item.taxType"
:options="taxOptions" :options="taxOptions"
@@ -505,15 +505,15 @@ const hasBlockingIncomingInvoiceErrors = computed(() => blockingIncomingInvoiceE
:disabled="mode === 'show'" :disabled="mode === 'show'"
@change="recalculateItem(item, 'taxType')" @change="recalculateItem(item, 'taxType')"
/> />
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-6 md:col-span-3"> <div class="col-span-6 md:col-span-3">
<UFormGroup label="Steuerbetrag" help="Automatisch berechnet"> <UFormField label="Steuerbetrag" help="Automatisch berechnet">
<UInput :model-value="item.amountTax" disabled color="gray" > <UInput :model-value="item.amountTax" disabled color="gray" >
<template #trailing>€</template> <template #trailing>€</template>
</UInput> </UInput>
</UFormGroup> </UFormField>
</div> </div>
<div class="col-span-12 flex justify-end gap-2"> <div class="col-span-12 flex justify-end gap-2">

View File

@@ -203,15 +203,15 @@ const selectIncomingInvoice = (invoice) => {
<USelectMenu <USelectMenu
v-model="selectedColumns" v-model="selectedColumns"
icon="i-heroicons-adjustments-horizontal-solid" icon="i-heroicons-adjustments-horizontal-solid"
:options="dataType.templateColumns.filter(i => !i.disabledInTable)" :items="dataType.templateColumns.filter(i => !i.disabledInTable)"
multiple multiple
class="hidden lg:block" class="hidden lg:block"
by="key" by="key"
:color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'" :color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'"
:ui-menu="{ width: 'min-w-max' }" :content="{ width: 'min-w-max' }"
@change="tempStore.modifyColumns(type,selectedColumns)" @change="tempStore.modifyColumns(type,selectedColumns)"
> >
<template #label> <template #default>
Spalten Spalten
</template> </template>
</USelectMenu> </USelectMenu>
@@ -220,11 +220,11 @@ const selectIncomingInvoice = (invoice) => {
icon="i-heroicons-adjustments-horizontal-solid" icon="i-heroicons-adjustments-horizontal-solid"
multiple multiple
v-model="selectedFilters" v-model="selectedFilters"
:options="selectableFilters" :items="selectableFilters"
:color="selectedFilters.length > 0 ? 'primary' : 'white'" :color="selectedFilters.length > 0 ? 'primary' : 'white'"
:ui-menu="{ width: 'min-w-max' }" :content="{ width: 'min-w-max' }"
> >
<template #label> <template #default>
Filter Filter
</template> </template>
</USelectMenu> </USelectMenu>
@@ -244,42 +244,42 @@ const selectIncomingInvoice = (invoice) => {
{{filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' ).length}} {{filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' ).length}}
</UBadge> </UBadge>
</template> </template>
<template #item="{item}"> <template #content="{item}">
<div style="height: 80dvh; overflow-y: scroll"> <div style="height: 80dvh; overflow-y: scroll">
<UTable <UTable
v-model:sort="sort" v-model:sorting="sort"
sort-mode="manual" sort-mode="manual"
@update:sort="setupPage" @update:sorting="setupPage"
:data="filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' )" :data="filteredRows.filter(i => item.label === 'Gebucht' ? i.state === 'Gebucht' : i.state !== 'Gebucht' )"
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
class="w-full" class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => selectIncomingInvoice(i) " :on-select="(i) => selectIncomingInvoice(i) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Belege anzuzeigen' }"
> >
<template #reference-data="{row}"> <template #reference-cell="{row}">
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.reference}}</span> <span v-if="row.original === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.original.reference}}</span>
<span v-else>{{row.reference}}</span> <span v-else>{{row.original.reference}}</span>
</template> </template>
<template #state-data="{row}"> <template #state-cell="{row}">
<span v-if="row.state === 'Vorbereitet'" class="text-cyan-500">{{row.state}}</span> <span v-if="row.original.state === 'Vorbereitet'" class="text-cyan-500">{{row.original.state}}</span>
<span v-else-if="row.state === 'Entwurf'" class="text-red-500">{{row.state}}</span> <span v-else-if="row.original.state === 'Entwurf'" class="text-red-500">{{row.original.state}}</span>
<span v-else-if="row.state === 'Gebucht'" class="text-primary-500">{{row.state}}</span> <span v-else-if="row.original.state === 'Gebucht'" class="text-primary-500">{{row.original.state}}</span>
</template> </template>
<template #date-data="{row}"> <template #date-cell="{row}">
{{dayjs(row.date).format("DD.MM.YYYY")}} {{dayjs(row.original.date).format("DD.MM.YYYY")}}
</template> </template>
<template #vendor-data="{row}"> <template #vendor-cell="{row}">
{{row.vendor ? row.vendor.name : ""}} {{row.original.vendor ? row.original.vendor.name : ""}}
</template> </template>
<template #amount-data="{row}"> <template #amount-cell="{row}">
{{displayCurrency(sum.getIncomingInvoiceSum(row))}} {{displayCurrency(sum.getIncomingInvoiceSum(row.original))}}
</template> </template>
<template #dueDate-data="{row}"> <template #dueDate-cell="{row}">
<span v-if="row.dueDate">{{dayjs(row.dueDate).format("DD.MM.YYYY")}}</span> <span v-if="row.original.dueDate">{{dayjs(row.original.dueDate).format("DD.MM.YYYY")}}</span>
</template> </template>
<template #paid-data="{row}"> <template #paid-cell="{row}">
<span v-if="isPaid(row)" class="text-primary-500">Bezahlt</span> <span v-if="isPaid(row.original)" class="text-primary-500">Bezahlt</span>
<span v-else class="text-rose-600">Offen</span> <span v-else class="text-rose-600">Offen</span>
</template> </template>
</UTable> </UTable>

View File

@@ -454,24 +454,25 @@ onMounted(() => {
</UDashboardPanelContent> </UDashboardPanelContent>
<UModal v-model:open="isAbsenceModalOpen"> <UModal v-model:open="isAbsenceModalOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }"> <template #content>
<template #header> <UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<div class="flex items-center justify-between"> <template #header>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white"> <div class="flex items-center justify-between">
{{ absenceModalTitle }} <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
</h3> {{ absenceModalTitle }}
<UButton </h3>
color="gray" <UButton
variant="ghost" color="gray"
icon="i-heroicons-x-mark-20-solid" variant="ghost"
class="-my-1" icon="i-heroicons-x-mark-20-solid"
@click="isAbsenceModalOpen = false" class="-my-1"
/> @click="isAbsenceModalOpen = false"
</div> />
</template> </div>
</template>
<div class="space-y-4"> <div class="space-y-4">
<UFormGroup label="Profil"> <UFormField label="Profil">
<USelectMenu <USelectMenu
v-model="absenceForm.userId" v-model="absenceForm.userId"
:options="profileOptions" :options="profileOptions"
@@ -479,51 +480,52 @@ onMounted(() => {
option-attribute="label" option-attribute="label"
searchable searchable
/> />
</UFormGroup> </UFormField>
<UFormGroup label="Typ"> <UFormField label="Typ">
<USelectMenu <USelectMenu
v-model="absenceForm.type" v-model="absenceForm.type"
:options="absenceTypeOptions" :options="absenceTypeOptions"
value-attribute="value" value-attribute="value"
option-attribute="label" option-attribute="label"
/> />
</UFormGroup> </UFormField>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<UFormGroup label="Start"> <UFormField label="Start">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UInput v-model="absenceForm.startDate" type="date" class="flex-1" /> <UInput v-model="absenceForm.startDate" type="date" class="flex-1" />
<UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('startDate')" /> <UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('startDate')" />
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup label="Ende"> <UFormField label="Ende">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UInput v-model="absenceForm.endDate" type="date" class="flex-1" /> <UInput v-model="absenceForm.endDate" type="date" class="flex-1" />
<UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('endDate')" /> <UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('endDate')" />
</div> </div>
</UFormGroup> </UFormField>
</div> </div>
<UFormGroup label="Notiz"> <UFormField label="Notiz">
<UTextarea <UTextarea
v-model="absenceForm.description" v-model="absenceForm.description"
:placeholder="absenceForm.type === 'sick' ? 'z. B. Krankmeldung eingegangen' : 'z. B. Sommerurlaub'" :placeholder="absenceForm.type === 'sick' ? 'z. B. Krankmeldung eingegangen' : 'z. B. Sommerurlaub'"
/> />
</UFormGroup> </UFormField>
</div> </div>
<template #footer> <template #footer>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<UButton color="gray" variant="soft" @click="isAbsenceModalOpen = false"> <UButton color="gray" variant="soft" @click="isAbsenceModalOpen = false">
Abbrechen Abbrechen
</UButton> </UButton>
<UButton color="primary" :loading="savingAbsence" @click="saveAbsence"> <UButton color="primary" :loading="savingAbsence" @click="saveAbsence">
Speichern Speichern
</UButton> </UButton>
</div> </div>
</template> </template>
</UCard> </UCard>
</template>
</UModal> </UModal>
</div> </div>
</template> </template>

View File

@@ -9,18 +9,20 @@ defineShortcuts({
router.push("/projecttypes") router.push("/projecttypes")
}, },
'arrowleft': () => { 'arrowleft': () => {
if(openTab.value > 0){ const currentIndex = Number(openTab.value)
openTab.value -= 1 if(currentIndex > 0){
openTab.value = String(currentIndex - 1)
} }
}, },
'arrowright': () => { 'arrowright': () => {
if(openTab.value < 3) { const currentIndex = Number(openTab.value)
openTab.value += 1 if(currentIndex < 3) {
openTab.value = String(currentIndex + 1)
} }
}, },
}) })
const openTab = ref(0) const openTab = ref("0")
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
@@ -129,7 +131,7 @@ const addPhase = () => {
v-if="itemInfo.id && mode == 'show'" v-if="itemInfo.id && mode == 'show'"
v-model="openTab" v-model="openTab"
> >
<template #item="{ item }"> <template #content="{ item }">
<div v-if="item.label === 'Informationen'" class="flex flex-row"> <div v-if="item.label === 'Informationen'" class="flex flex-row">
<div class="w-1/2 mr-3"> <div class="w-1/2 mr-3">
<UCard class="mt-5"> <UCard class="mt-5">
@@ -159,13 +161,13 @@ const addPhase = () => {
description="Achtung Änderungen an diesem Projekttypen betreffen nur Projekte die damit neu erstellt werden. Bestehende Projekte bleiben unverändert." description="Achtung Änderungen an diesem Projekttypen betreffen nur Projekte die damit neu erstellt werden. Bestehende Projekte bleiben unverändert."
/> />
<UFormGroup <UFormField
label="Name:" label="Name:"
> >
<UInput <UInput
v-model="itemInfo.name" v-model="itemInfo.name"
/> />
</UFormGroup> </UFormField>
<USeparator class="mt-5"> <USeparator class="mt-5">
Initiale Phasen Initiale Phasen
@@ -248,36 +250,38 @@ const addPhase = () => {
</UButton> </UButton>
<UModal v-model:open="openQuickActionModal"> <UModal v-model:open="openQuickActionModal">
<UCard> <template #content>
<div class="flex items-center justify-between"> <UCard>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white"> <div class="flex items-center justify-between">
Schnellaktion hinzufügen <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
</h3> Schnellaktion hinzufügen
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="openQuickActionModal = false" /> </h3>
</div> <UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="openQuickActionModal = false" />
<div class="flex flex-col"> </div>
<UButton <div class="flex flex-col">
class="my-1" <UButton
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Angebot',link:'/createDocument/edit/?type=quotes'})">Angebot Erstellen</UButton> class="my-1"
<UButton @click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Angebot',link:'/createDocument/edit/?type=quotes'})">Angebot Erstellen</UButton>
class="my-1" <UButton
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Auftrag',link:'/createDocument/edit/?type=confirmationOrders'})">Auftrag Erstellen</UButton> class="my-1"
<UButton @click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Auftrag',link:'/createDocument/edit/?type=confirmationOrders'})">Auftrag Erstellen</UButton>
class="my-1" <UButton
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Lieferschein',link:'/createDocument/edit/?type=deliveryNotes'})">Lieferschein Erstellen</UButton> class="my-1"
<UButton @click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Lieferschein',link:'/createDocument/edit/?type=deliveryNotes'})">Lieferschein Erstellen</UButton>
class="my-1" <UButton
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Rechnung',link:'/createDocument/edit/?type=invoices'})">Rechnung Erstellen</UButton> class="my-1"
<UButton @click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Rechnung',link:'/createDocument/edit/?type=invoices'})">Rechnung Erstellen</UButton>
class="my-1" <UButton
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Aufgabe',link:'/tasks/create'})">Aufgabe Erstellen</UButton> class="my-1"
<UButton @click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Aufgabe',link:'/tasks/create'})">Aufgabe Erstellen</UButton>
class="my-1" <UButton
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Termin',link:'/events/edit'})">Termin Erstellen</UButton> class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Termin',link:'/events/edit'})">Termin Erstellen</UButton>
</div> </div>
</UCard> </UCard>
</template>
</UModal> </UModal>
</td> </td>
<td> <td>

View File

@@ -91,12 +91,12 @@ const filteredRows = computed(() => {
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
class="w-full" class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
@select="(i) => router.push(`/projecttypes/show/${i.id}`) " :on-select="(i) => router.push(`/projecttypes/show/${i.id}`) "
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Projekttypen anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Projekttypen anzuzeigen' }"
> >
<template #name-data="{row}"> <template #name-cell="{row}">
<span class="text-primary-500 font-bold" v-if="row === filteredRows[selectedItem]">{{ row.name }}</span> <span class="text-primary-500 font-bold" v-if="row.original === filteredRows[selectedItem]">{{ row.original.name }}</span>
<span v-else>{{ row.name }}</span> <span v-else>{{ row.original.name }}</span>
</template> </template>
</UTable> </UTable>

View File

@@ -8,13 +8,15 @@ defineShortcuts({
router.push("/roles") router.push("/roles")
}, },
'arrowleft': () => { 'arrowleft': () => {
if(openTab.value > 0){ const currentIndex = Number(openTab.value)
openTab.value -= 1 if(currentIndex > 0){
openTab.value = String(currentIndex - 1)
} }
}, },
'arrowright': () => { 'arrowright': () => {
if(openTab.value < 3) { const currentIndex = Number(openTab.value)
openTab.value += 1 if(currentIndex < 3) {
openTab.value = String(currentIndex + 1)
} }
}, },
}) })
@@ -32,7 +34,7 @@ const mode = ref(route.params.mode || "show")
const itemInfo = ref({ const itemInfo = ref({
rights: [] rights: []
}) })
const openTab = ref(0) const openTab = ref("0")
//Functions //Functions
const setupPage = async () => { const setupPage = async () => {
@@ -120,7 +122,7 @@ setupPage()
class="p-5" class="p-5"
v-model="openTab" v-model="openTab"
> >
<template #item="{item}"> <template #content="{item}">
<div v-if="item.label === 'Informationen'" class="mt-5 flex flex-row"> <div v-if="item.label === 'Informationen'" class="mt-5 flex flex-row">
<div class="w-1/2 mr-5"> <div class="w-1/2 mr-5">
<UCard> <UCard>
@@ -157,15 +159,15 @@ setupPage()
v-else-if="mode == 'edit' || mode == 'create'" v-else-if="mode == 'edit' || mode == 'create'"
class="p-5" class="p-5"
> >
<UFormGroup <UFormField
label="Name:" label="Name:"
> >
<UInput <UInput
v-model="itemInfo.name" v-model="itemInfo.name"
autofocus autofocus
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Rechte:" label="Rechte:"
> >
<USelectMenu <USelectMenu
@@ -176,14 +178,14 @@ setupPage()
multiple multiple
> >
</USelectMenu> </USelectMenu>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Beschreibung:" label="Beschreibung:"
> >
<UTextarea <UTextarea
v-model="itemInfo.description" v-model="itemInfo.description"
/> />
</UFormGroup> </UFormField>
</UForm> </UForm>
</template> </template>

View File

@@ -51,7 +51,7 @@ const savingUser = ref(false)
const savingTenant = ref(false) const savingTenant = ref(false)
const creatingUser = ref(false) const creatingUser = ref(false)
const creatingTenant = ref(false) const creatingTenant = ref(false)
const activeTab = ref(0) const activeTab = ref("0")
const createUserModalOpen = ref(false) const createUserModalOpen = ref(false)
const createTenantModalOpen = ref(false) const createTenantModalOpen = ref(false)
const createdUserPassword = ref("") const createdUserPassword = ref("")
@@ -472,7 +472,7 @@ onMounted(async () => {
:items="tabItems" :items="tabItems"
class="admin-tabs h-full" class="admin-tabs h-full"
> >
<template #item="{ item }"> <template #content="{ item }">
<div v-if="item.label === 'Benutzer'" class="admin-grid mt-5 grid grid-cols-1 xl:grid-cols-3 gap-5"> <div v-if="item.label === 'Benutzer'" class="admin-grid mt-5 grid grid-cols-1 xl:grid-cols-3 gap-5">
<UCard class="admin-card xl:col-span-1"> <UCard class="admin-card xl:col-span-1">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
@@ -494,7 +494,7 @@ onMounted(async () => {
v-if="!loading" v-if="!loading"
:data="userTableRows" :data="userTableRows"
:columns="normalizedUserTableColumns" :columns="normalizedUserTableColumns"
@select="selectUser" :on-select="selectUser"
/> />
<USkeleton v-else class="h-80" /> <USkeleton v-else class="h-80" />
@@ -519,49 +519,49 @@ onMounted(async () => {
</div> </div>
<UForm :state="userForm" class="grid grid-cols-1 md:grid-cols-2 gap-6"> <UForm :state="userForm" class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UFormGroup label="E-Mail"> <UFormField label="E-Mail">
<UInput v-model="userForm.email" /> <UInput v-model="userForm.email" />
</UFormGroup> </UFormField>
<UFormGroup label="Profil Vorname"> <UFormField label="Profil Vorname">
<UInput v-model="userForm.profile_defaults.first_name" /> <UInput v-model="userForm.profile_defaults.first_name" />
</UFormGroup> </UFormField>
<UFormGroup label="Profil Nachname"> <UFormField label="Profil Nachname">
<UInput v-model="userForm.profile_defaults.last_name" /> <UInput v-model="userForm.profile_defaults.last_name" />
</UFormGroup> </UFormField>
<UFormGroup label="Tenants"> <UFormField label="Tenants">
<USelectMenu <USelectMenu
:model-value="userForm.tenant_ids" :model-value="userForm.tenant_ids"
:options="tenantOptions" :items="tenantOptions"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
multiple multiple
@update:model-value="updateUserTenants" @update:model-value="updateUserTenants"
/> />
</UFormGroup> </UFormField>
<UFormGroup label="Administrative Freigabe"> <UFormField label="Administrative Freigabe">
<div class="flex items-center gap-3 h-10"> <div class="flex items-center gap-3 h-10">
<UToggle v-model="userForm.is_admin" /> <USwitch v-model="userForm.is_admin" />
<span class="text-sm text-gray-600">Darf Administrationsseite und Admin-API nutzen</span> <span class="text-sm text-gray-600">Darf Administrationsseite und Admin-API nutzen</span>
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup label="Multi-Tenant"> <UFormField label="Multi-Tenant">
<div class="flex items-center gap-3 h-10"> <div class="flex items-center gap-3 h-10">
<UToggle v-model="userForm.multiTenant" /> <USwitch v-model="userForm.multiTenant" />
<span class="text-sm text-gray-600">Benutzer darf mehreren Tenants zugeordnet sein</span> <span class="text-sm text-gray-600">Benutzer darf mehreren Tenants zugeordnet sein</span>
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup label="Passwortwechsel erzwingen"> <UFormField label="Passwortwechsel erzwingen">
<div class="flex items-center gap-3 h-10"> <div class="flex items-center gap-3 h-10">
<UToggle v-model="userForm.must_change_password" /> <USwitch v-model="userForm.must_change_password" />
<span class="text-sm text-gray-600">Beim nächsten Login muss das Passwort geändert werden</span> <span class="text-sm text-gray-600">Beim nächsten Login muss das Passwort geändert werden</span>
</div> </div>
</UFormGroup> </UFormField>
</UForm> </UForm>
<div> <div>
@@ -586,33 +586,33 @@ onMounted(async () => {
</div> </div>
</div> </div>
<UFormGroup label="Rolle"> <UFormField label="Rolle">
<USelectMenu <USelectMenu
:model-value="getRoleForTenant(tenantId)" :model-value="getRoleForTenant(tenantId)"
:options="getRoleOptionsForTenant(tenantId)" :items="getRoleOptionsForTenant(tenantId)"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
placeholder="Rolle auswählen" placeholder="Rolle auswählen"
@update:model-value="(value) => setRoleForTenant(tenantId, value)" @update:model-value="(value) => setRoleForTenant(tenantId, value)"
/> />
</UFormGroup> </UFormField>
<UFormGroup label="Freies Profil"> <UFormField label="Freies Profil">
<USelectMenu <USelectMenu
:model-value="getProfileAssignmentForTenant(tenantId)" :model-value="getProfileAssignmentForTenant(tenantId)"
:options="[ :items="[
{ label: 'Neues Profil erzeugen', value: null }, { label: 'Neues Profil erzeugen', value: null },
...getFreeProfilesForTenant(tenantId).map((profile) => ({ ...getFreeProfilesForTenant(tenantId).map((profile) => ({
label: profile.full_name || `${profile.first_name} ${profile.last_name}`, label: profile.full_name || `${profile.first_name} ${profile.last_name}`,
value: profile.id, value: profile.id,
})) }))
]" ]"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
placeholder="Profil auswählen" placeholder="Profil auswählen"
@update:model-value="(value) => setProfileAssignmentForTenant(tenantId, value)" @update:model-value="(value) => setProfileAssignmentForTenant(tenantId, value)"
/> />
</UFormGroup> </UFormField>
</div> </div>
</UCard> </UCard>
</div> </div>
@@ -676,7 +676,7 @@ onMounted(async () => {
v-if="!loading" v-if="!loading"
:data="tenantTableRows" :data="tenantTableRows"
:columns="normalizedTenantTableColumns" :columns="normalizedTenantTableColumns"
@select="selectTenant" :on-select="selectTenant"
/> />
<USkeleton v-else class="h-80" /> <USkeleton v-else class="h-80" />
@@ -701,13 +701,13 @@ onMounted(async () => {
</div> </div>
<UForm :state="tenantForm" class="grid grid-cols-1 md:grid-cols-2 gap-6"> <UForm :state="tenantForm" class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UFormGroup label="Name"> <UFormField label="Name">
<UInput v-model="tenantForm.name" /> <UInput v-model="tenantForm.name" />
</UFormGroup> </UFormField>
<UFormGroup label="Kürzel"> <UFormField label="Kürzel">
<UInput v-model="tenantForm.short" /> <UInput v-model="tenantForm.short" />
</UFormGroup> </UFormField>
</UForm> </UForm>
<div> <div>
@@ -743,49 +743,50 @@ onMounted(async () => {
</UDashboardPanelContent> </UDashboardPanelContent>
<UModal v-model:open="createUserModalOpen"> <UModal v-model:open="createUserModalOpen">
<UCard> <template #content>
<template #header> <UCard>
<div class="text-lg font-semibold">Benutzer anlegen</div> <template #header>
</template> <div class="text-lg font-semibold">Benutzer anlegen</div>
</template>
<UForm <UForm
:state="createUserForm" :state="createUserForm"
class="space-y-4" class="space-y-4"
@submit.prevent="createUser" @submit.prevent="createUser"
> >
<UFormGroup label="E-Mail"> <UFormField label="E-Mail">
<UInput v-model="createUserForm.email" type="email" /> <UInput v-model="createUserForm.email" type="email" />
</UFormGroup> </UFormField>
<UFormGroup label="Initialpasswort"> <UFormField label="Initialpasswort">
<UInput <UInput
v-model="createUserForm.password" v-model="createUserForm.password"
type="text" type="text"
placeholder="Leer lassen für automatisches Passwort" placeholder="Leer lassen für automatisches Passwort"
/> />
</UFormGroup> </UFormField>
<UFormGroup label="Vorname für neues Profil"> <UFormField label="Vorname für neues Profil">
<UInput v-model="createUserForm.first_name" /> <UInput v-model="createUserForm.first_name" />
</UFormGroup> </UFormField>
<UFormGroup label="Nachname für neues Profil"> <UFormField label="Nachname für neues Profil">
<UInput v-model="createUserForm.last_name" /> <UInput v-model="createUserForm.last_name" />
</UFormGroup> </UFormField>
<UFormGroup label="Administrative Freigabe"> <UFormField label="Administrative Freigabe">
<div class="flex items-center gap-3 h-10"> <div class="flex items-center gap-3 h-10">
<UToggle v-model="createUserForm.is_admin" /> <USwitch v-model="createUserForm.is_admin" />
<span class="text-sm text-gray-600">Benutzer darf die Administration öffnen</span> <span class="text-sm text-gray-600">Benutzer darf die Administration öffnen</span>
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup label="Multi-Tenant"> <UFormField label="Multi-Tenant">
<div class="flex items-center gap-3 h-10"> <div class="flex items-center gap-3 h-10">
<UToggle v-model="createUserForm.multiTenant" /> <USwitch v-model="createUserForm.multiTenant" />
<span class="text-sm text-gray-600">Mehrere Tenant-Zuordnungen erlauben</span> <span class="text-sm text-gray-600">Mehrere Tenant-Zuordnungen erlauben</span>
</div> </div>
</UFormGroup> </UFormField>
<div class="flex justify-end gap-3 pt-2"> <div class="flex justify-end gap-3 pt-2">
<UButton color="gray" variant="soft" @click="createUserModalOpen = false"> <UButton color="gray" variant="soft" @click="createUserModalOpen = false">
@@ -795,28 +796,30 @@ onMounted(async () => {
Benutzer anlegen Benutzer anlegen
</UButton> </UButton>
</div> </div>
</UForm> </UForm>
</UCard> </UCard>
</template>
</UModal> </UModal>
<UModal v-model:open="createTenantModalOpen"> <UModal v-model:open="createTenantModalOpen">
<UCard> <template #content>
<template #header> <UCard>
<div class="text-lg font-semibold">Tenant anlegen</div> <template #header>
</template> <div class="text-lg font-semibold">Tenant anlegen</div>
</template>
<UForm <UForm
:state="createTenantForm" :state="createTenantForm"
class="space-y-4" class="space-y-4"
@submit.prevent="createTenant" @submit.prevent="createTenant"
> >
<UFormGroup label="Name"> <UFormField label="Name">
<UInput v-model="createTenantForm.name" /> <UInput v-model="createTenantForm.name" />
</UFormGroup> </UFormField>
<UFormGroup label="Kürzel"> <UFormField label="Kürzel">
<UInput v-model="createTenantForm.short" /> <UInput v-model="createTenantForm.short" />
</UFormGroup> </UFormField>
<UAlert <UAlert
title="Seed-Daten" title="Seed-Daten"
@@ -833,8 +836,9 @@ onMounted(async () => {
Tenant anlegen Tenant anlegen
</UButton> </UButton>
</div> </div>
</UForm> </UForm>
</UCard> </UCard>
</template>
</UModal> </UModal>
<div class="mx-5 mb-5"> <div class="mx-5 mb-5">

View File

@@ -101,16 +101,17 @@ setupPage()
+ Bankverbindung + Bankverbindung
</UButton> </UButton>
<USlideover <USlideover
v-model="showAddBankRequisition" v-model:open="showAddBankRequisition"
> >
<UCard <template #body>
class="h-full" <UCard
> class="h-full"
<template #header> >
<p>Bankverbindung hinzufügen</p> <template #header>
</template> <p>Bankverbindung hinzufügen</p>
</template>
<UFormGroup <UFormField
label="BIC:" label="BIC:"
class="flex-auto" class="flex-auto"
> >
@@ -127,7 +128,7 @@ setupPage()
</UButton> </UButton>
</InputGroup> </InputGroup>
</UFormGroup> </UFormField>
<UAlert <UAlert
v-if="showAlert && bankData.id && bankData.countries.includes('DE')" v-if="showAlert && bankData.id && bankData.countries.includes('DE')"
title="Bank gefunden" title="Bank gefunden"
@@ -137,46 +138,49 @@ setupPage()
class="mt-3" class="mt-3"
:actions="[{ variant: 'solid', color: 'primary', label: 'Verbinden',click: generateLink }]" :actions="[{ variant: 'solid', color: 'primary', label: 'Verbinden',click: generateLink }]"
/> />
<UAlert <UAlert
v-else-if="showAlert && !bankData.id" v-else-if="showAlert && !bankData.id"
title="Bank nicht gefunden" title="Bank nicht gefunden"
icon="i-heroicons-x-circle" icon="i-heroicons-x-circle"
color="error" color="error"
variant="outline" variant="outline"
class="mt-3" class="mt-3"
/> />
</UCard> </UCard>
</template>
</USlideover> </USlideover>
</template> </template>
</UDashboardNavbar> </UDashboardNavbar>
<UModal v-model:open="showReqData"> <UModal v-model:open="showReqData">
<UCard> <template #content>
<template #header> <UCard>
Verfügbare Bankkonten <template #header>
</template> Verfügbare Bankkonten
<div </template>
v-for="account in reqData.accounts" <div
:key="account.id" v-for="account in reqData.accounts"
class="p-2 m-3 flex justify-between" :key="account.id"
> class="p-2 m-3 flex justify-between"
{{account.iban}} - {{account.owner_name}} >
{{account.iban}} - {{account.owner_name}}
<UButton <UButton
@click="addAccount(account)" @click="addAccount(account)"
v-if="!bankaccounts.find(i => i.iban === account.iban)" v-if="!bankaccounts.find(i => i.iban === account.iban)"
> >
Hinzufügen Hinzufügen
</UButton> </UButton>
<UButton <UButton
@click="updateAccount(account)" @click="updateAccount(account)"
v-else v-else
> >
Aktualisieren Aktualisieren
</UButton> </UButton>
</div> </div>
</UCard> </UCard>
</template>
</UModal> </UModal>
<UTable <UTable
@@ -200,21 +204,21 @@ setupPage()
}, },
])" ])"
> >
<template #expired-data="{row}"> <template #expired-cell="{ row }">
<span v-if="row.expired" class="text-rose-600">Ausgelaufen</span> <span v-if="row.original.expired" class="text-error-600">Ausgelaufen</span>
<span v-else class="text-primary">Aktiv</span> <span v-else class="text-primary">Aktiv</span>
<UButton <UButton
v-if="row.expired" v-if="row.original.expired"
variant="outline" variant="outline"
class="ml-2" class="ml-2"
@click="generateLink(row.bankId)" @click="generateLink(row.original.bankId)"
>Aktualisieren</UButton> >Aktualisieren</UButton>
</template> </template>
<template #balance-data="{row}"> <template #balance-cell="{ row }">
{{row.balance ? row.balance.toFixed(2).replace(".",",") + ' €' : '-'}} {{ row.original.balance ? row.original.balance.toFixed(2).replace(".",",") + ' €' : '-' }}
</template> </template>
<template #iban-data="{row}"> <template #iban-cell="{ row }">
{{row.iban.match(/.{1,5}/g).join(" ")}} {{ row.original.iban.match(/.{1,5}/g).join(" ") }}
</template> </template>
</UTable> </UTable>

View File

@@ -57,15 +57,15 @@ const saveAccount = async () => {
</template> </template>
</UDashboardNavbar> </UDashboardNavbar>
<UForm class="w-2/3 mx-auto mt-5"> <UForm class="w-2/3 mx-auto mt-5">
<UFormGroup <UFormField
label="E-Mail Adresse" label="E-Mail Adresse"
> >
<UInput <UInput
v-model="itemInfo.email" v-model="itemInfo.email"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Passwort" label="Passwort"
> >
<UInput <UInput
@@ -73,61 +73,61 @@ const saveAccount = async () => {
v-model="itemInfo.password" v-model="itemInfo.password"
placeholder="********" placeholder="********"
/> />
</UFormGroup> </UFormField>
<USeparator label="IMAP"/> <USeparator label="IMAP"/>
<UFormGroup <UFormField
label="IMAP Host" label="IMAP Host"
> >
<UInput <UInput
v-model="itemInfo.imap_host" v-model="itemInfo.imap_host"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="IMAP Port" label="IMAP Port"
> >
<UInput <UInput
type="number" type="number"
v-model="itemInfo.imap_port" v-model="itemInfo.imap_port"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="IMAP SSL" label="IMAP SSL"
> >
<UToggle <USwitch
v-model="itemInfo.imap_ssl" v-model="itemInfo.imap_ssl"
/> />
</UFormGroup> </UFormField>
<USeparator label="SMTP"/> <USeparator label="SMTP"/>
<UFormGroup <UFormField
label="SMTP Host" label="SMTP Host"
> >
<UInput <UInput
v-model="itemInfo.smtp_host" v-model="itemInfo.smtp_host"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="SMTP Port" label="SMTP Port"
> >
<UInput <UInput
type="number" type="number"
v-model="itemInfo.smtp_port" v-model="itemInfo.smtp_port"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="SMTP SSL" label="SMTP SSL"
> >
<UToggle <USwitch
v-model="itemInfo.smtp_ssl" v-model="itemInfo.smtp_ssl"
/> />
</UFormGroup> </UFormField>
</UForm> </UForm>
</template> </template>

View File

@@ -32,23 +32,23 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
<template> <template>
<UModal <UModal
v-model="showEmailAddressModal" v-model:open="showEmailAddressModal"
> >
<template #content> <template #content>
<UCard> <UCard>
<template #header> <template #header>
E-Mail Adresse E-Mail Adresse
</template> </template>
<!-- <UFormGroup <!-- <UFormField
label="E-Mail Adresse:" label="E-Mail Adresse:"
> >
</UFormGroup>--> </UFormField>-->
<UInput <UInput
v-model="createEMailAddress" v-model="createEMailAddress"
/> />
<!-- <UFormGroup <!-- <UFormField
label="Account Typ:" label="Account Typ:"
> >
<USelectMenu <USelectMenu
@@ -57,7 +57,7 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
value-attribute="key" value-attribute="key"
v-model="createEMailType" v-model="createEMailType"
/> />
</UFormGroup>--> </UFormField>-->
<template #footer> <template #footer>
<UButton <UButton
@click="createAccount" @click="createAccount"
@@ -85,9 +85,9 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
:data="items" :data="items"
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
class="w-full" class="w-full"
@select="(i) => navigateTo(`/settings/emailaccounts/edit/${i.id}`)" :on-select="(i) => navigateTo(`/settings/emailaccounts/edit/${i.id}`)"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine E-Mail Konten anzuzeigen' }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine E-Mail Konten anzuzeigen' }"
> >
</UTable> </UTable>
</template> </template>

View File

@@ -33,13 +33,13 @@ const labelPrinterURI = ref("")
Etikettendrucker Etikettendrucker
</template> </template>
<UFormGroup <UFormField
label="IP-Adresse:" label="IP-Adresse:"
> >
<UInput <UInput
v-model="labelPrinterURI" v-model="labelPrinterURI"
/> />
</UFormGroup> </UFormField>
<UButton <UButton
@click="setupPrinter" @click="setupPrinter"

View File

@@ -37,7 +37,7 @@ const isLight = computed({
:items="items" :items="items"
class="h-100 p-5" class="h-100 p-5"
> >
<template #item="{item}"> <template #content="{item}">
<UCard class="mt-5"> <UCard class="mt-5">
<div v-if="item.label === 'Profil'"> <div v-if="item.label === 'Profil'">
@@ -86,4 +86,4 @@ const isLight = computed({
<style scoped> <style scoped>
</style> </style>

View File

@@ -168,7 +168,7 @@ setupPage()
} }
]" ]"
> >
<template #item="{item}"> <template #content="{item}">
<div v-if="item.label === 'Dokubox'"> <div v-if="item.label === 'Dokubox'">
<UAlert <UAlert
class="mt-5" class="mt-5"
@@ -190,17 +190,17 @@ setupPage()
<div v-if="item.label === 'Rechnung & Kontakt'"> <div v-if="item.label === 'Rechnung & Kontakt'">
<UCard class="mt-5"> <UCard class="mt-5">
<UForm class="w-1/2"> <UForm class="w-1/2">
<UFormGroup <UFormField
label="Firmenname:" label="Firmenname:"
> >
<UInput v-model="businessInfo.name"/> <UInput v-model="businessInfo.name"/>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Straße + Hausnummer:" label="Straße + Hausnummer:"
> >
<UInput v-model="businessInfo.street"/> <UInput v-model="businessInfo.street"/>
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="PLZ + Ort" label="PLZ + Ort"
class="w-full" class="w-full"
> >
@@ -208,41 +208,41 @@ setupPage()
<UInput v-model="businessInfo.zip"/> <UInput v-model="businessInfo.zip"/>
<UInput v-model="businessInfo.city" class="flex-auto"/> <UInput v-model="businessInfo.city" class="flex-auto"/>
</InputGroup> </InputGroup>
</UFormGroup> </UFormField>
<UButton <UButton
class="mt-3" class="mt-3"
@click="updateTenant({businessInfo: businessInfo})" @click="updateTenant({businessInfo: businessInfo})"
> >
Speichern Speichern
</UButton> </UButton>
<UFormGroup <UFormField
label="Kontenrahmen:" label="Kontenrahmen:"
class="mt-6" class="mt-6"
> >
<USelectMenu <USelectMenu
v-model="accountChart" v-model="accountChart"
:options="accountChartOptions" :items="accountChartOptions"
option-attribute="label" label-key="label"
value-attribute="value" value-key="value"
/> />
</UFormGroup> </UFormField>
<UButton <UButton
class="mt-3" class="mt-3"
@click="updateTenant({accountChart: accountChart})" @click="updateTenant({accountChart: accountChart})"
> >
Kontenrahmen speichern Kontenrahmen speichern
</UButton> </UButton>
<UFormGroup <UFormField
label="USt-Auswertung:" label="USt-Auswertung:"
class="mt-6" class="mt-6"
> >
<USelectMenu <USelectMenu
v-model="taxEvaluationPeriod" v-model="taxEvaluationPeriod"
:options="TAX_EVALUATION_PERIOD_OPTIONS" :items="TAX_EVALUATION_PERIOD_OPTIONS"
option-attribute="label" label-key="label"
value-attribute="value" value-key="value"
/> />
</UFormGroup> </UFormField>
<UButton <UButton
class="mt-3" class="mt-3"
@click="updateTenant({taxEvaluationPeriod: taxEvaluationPeriod})" @click="updateTenant({taxEvaluationPeriod: taxEvaluationPeriod})"

View File

@@ -163,7 +163,7 @@ const getDocLabel = (type) => {
:data="texttemplates" :data="texttemplates"
:loading="loading" :loading="loading"
v-model:expand="expand" v-model:expand="expand"
:empty-state="{ icon: 'i-heroicons-document-text', label: 'Keine Textvorlagen gefunden' }" :empty="{ icon: 'i-heroicons-document-text', label: 'Keine Textvorlagen gefunden' }"
:columns="normalizeTableColumns([ :columns="normalizeTableColumns([
{ key: 'name', label: 'Bezeichnung' }, { key: 'name', label: 'Bezeichnung' },
{ key: 'documentType', label: 'Verwendung' }, { key: 'documentType', label: 'Verwendung' },
@@ -172,33 +172,33 @@ const getDocLabel = (type) => {
{ key: 'actions', label: '' } { key: 'actions', label: '' }
])" ])"
> >
<template #name-data="{ row }"> <template #name-cell="{ row }">
<span class="font-medium text-gray-900 dark:text-white">{{ row.name }}</span> <span class="font-medium text-gray-900 dark:text-white">{{ row.original.name }}</span>
</template> </template>
<template #documentType-data="{ row }"> <template #documentType-cell="{ row }">
<UBadge color="gray" variant="soft"> <UBadge color="gray" variant="soft">
{{ getDocLabel(row.documentType) }} {{ getDocLabel(row.original.documentType) }}
</UBadge> </UBadge>
</template> </template>
<template #pos-data="{ row }"> <template #pos-cell="{ row }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UIcon <UIcon
:name="row.pos === 'startText' ? 'i-heroicons-bars-arrow-down' : 'i-heroicons-bars-arrow-up'" :name="row.original.pos === 'startText' ? 'i-heroicons-bars-arrow-down' : 'i-heroicons-bars-arrow-up'"
class="w-4 h-4 text-gray-500" class="w-4 h-4 text-gray-500"
/> />
<span>{{ row.pos === 'startText' ? 'Einleitung' : 'Endtext' }}</span> <span>{{ row.original.pos === 'startText' ? 'Einleitung' : 'Endtext' }}</span>
</div> </div>
</template> </template>
<template #default-data="{ row }"> <template #default-cell="{ row }">
<UIcon v-if="row.default" name="i-heroicons-check-circle-20-solid" class="text-green-500"/> <UIcon v-if="row.original.default" name="i-heroicons-check-circle-20-solid" class="text-green-500"/>
<span v-else class="text-gray-400">-</span> <span v-else class="text-gray-400">-</span>
</template> </template>
<template #actions-data="{ row }"> <template #actions-cell="{ row }">
<UButton color="gray" variant="ghost" icon="i-heroicons-pencil-square" @click="openModal(row)"/> <UButton color="gray" variant="ghost" icon="i-heroicons-pencil-square" @click="openModal(row.original)"/>
</template> </template>
<template #expand="{ row }"> <template #expand="{ row }">
@@ -206,7 +206,7 @@ const getDocLabel = (type) => {
<div class="mb-4"> <div class="mb-4">
<h4 class="text-sm font-bold uppercase text-gray-500 mb-1">Vorschau</h4> <h4 class="text-sm font-bold uppercase text-gray-500 mb-1">Vorschau</h4>
<p class="text-gray-800 dark:text-gray-200 whitespace-pre-line p-3 bg-white dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-700 text-sm"> <p class="text-gray-800 dark:text-gray-200 whitespace-pre-line p-3 bg-white dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-700 text-sm">
{{ row.text }} {{ row.original.text }}
</p> </p>
</div> </div>
@@ -237,25 +237,26 @@ const getDocLabel = (type) => {
</UDashboardPanelContent> </UDashboardPanelContent>
<UModal v-model:open="editTemplateModalOpen" :ui="{ width: 'sm:max-w-4xl' }"> <UModal v-model:open="editTemplateModalOpen" :ui="{ width: 'sm:max-w-4xl' }">
<UCard> <template #content>
<template #header> <UCard>
<div class="flex justify-between items-center"> <template #header>
<h3 class="text-lg font-semibold"> <div class="flex justify-between items-center">
{{ itemInfo.id ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen' }} <h3 class="text-lg font-semibold">
</h3> {{ itemInfo.id ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen' }}
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark" @click="editTemplateModalOpen = false"/> </h3>
</div> <UButton color="gray" variant="ghost" icon="i-heroicons-x-mark" @click="editTemplateModalOpen = false"/>
</template> </div>
</template>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 space-y-4"> <div class="lg:col-span-2 space-y-4">
<UFormGroup label="Bezeichnung" required> <UFormField label="Bezeichnung" required>
<UInput v-model="itemInfo.name" placeholder="z.B. Standard Angebotstext" icon="i-heroicons-tag"/> <UInput v-model="itemInfo.name" placeholder="z.B. Standard Angebotstext" icon="i-heroicons-tag"/>
</UFormGroup> </UFormField>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<UFormGroup label="Dokumententyp" required> <UFormField label="Dokumententyp" required>
<USelectMenu <USelectMenu
v-model="itemInfo.documentType" v-model="itemInfo.documentType"
:options="Object.keys(dataStore.documentTypesForCreation || {}) :options="Object.keys(dataStore.documentTypesForCreation || {})
@@ -264,9 +265,9 @@ const getDocLabel = (type) => {
option-attribute="label" option-attribute="label"
value-attribute="key" value-attribute="key"
/> />
</UFormGroup> </UFormField>
<UFormGroup label="Position" required> <UFormField label="Position" required>
<USelectMenu <USelectMenu
v-model="itemInfo.pos" v-model="itemInfo.pos"
:options="[ :options="[
@@ -276,10 +277,10 @@ const getDocLabel = (type) => {
option-attribute="label" option-attribute="label"
value-attribute="key" value-attribute="key"
/> />
</UFormGroup> </UFormField>
</div> </div>
<UFormGroup label="Text Inhalt" required help="Klicken Sie rechts auf eine Variable, um sie einzufügen."> <UFormField label="Text Inhalt" required help="Klicken Sie rechts auf eine Variable, um sie einzufügen.">
<UTextarea <UTextarea
ref="textareaRef" ref="textareaRef"
v-model="itemInfo.text" v-model="itemInfo.text"
@@ -287,7 +288,7 @@ const getDocLabel = (type) => {
placeholder="Sehr geehrte Damen und Herren..." placeholder="Sehr geehrte Damen und Herren..."
class="font-mono text-sm" class="font-mono text-sm"
/> />
</UFormGroup> </UFormField>
<UCheckbox v-model="itemInfo.default" label="Als Standard für diesen Typ verwenden"/> <UCheckbox v-model="itemInfo.default" label="Als Standard für diesen Typ verwenden"/>
</div> </div>
@@ -342,34 +343,35 @@ const getDocLabel = (type) => {
</div> </div>
<template #footer> <template #footer>
<div class="flex justify-end gap-3"> <div class="flex justify-end gap-3">
<UButton color="gray" variant="ghost" @click="editTemplateModalOpen = false"> <UButton color="gray" variant="ghost" @click="editTemplateModalOpen = false">
Abbrechen Abbrechen
</UButton> </UButton>
<UButton <UButton
v-if="!itemInfo.id" v-if="!itemInfo.id"
color="primary" color="primary"
:loading="isSaving" :loading="isSaving"
@click="handleCreate" @click="handleCreate"
icon="i-heroicons-plus" icon="i-heroicons-plus"
> >
Erstellen Erstellen
</UButton> </UButton>
<UButton <UButton
v-else v-else
color="primary" color="primary"
:loading="isSaving" :loading="isSaving"
@click="handleUpdate" @click="handleUpdate"
icon="i-heroicons-check" icon="i-heroicons-check"
> >
Speichern Speichern
</UButton> </UButton>
</div> </div>
</template> </template>
</UCard> </UCard>
</template>
</UModal> </UModal>
</template> </template>

View File

@@ -182,75 +182,75 @@ onMounted(fetchProfile)
<USeparator label="Persönliche Daten" /> <USeparator label="Persönliche Daten" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4"> <UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Vorname"> <UFormField label="Vorname">
<UInput v-model="profile.first_name" /> <UInput v-model="profile.first_name" />
</UFormGroup> </UFormField>
<UFormGroup label="Nachname"> <UFormField label="Nachname">
<UInput v-model="profile.last_name" /> <UInput v-model="profile.last_name" />
</UFormGroup> </UFormField>
<UFormGroup label="E-Mail"> <UFormField label="E-Mail">
<UInput v-model="profile.email" /> <UInput v-model="profile.email" />
</UFormGroup> </UFormField>
<UFormGroup label="Telefon (Mobil)"> <UFormField label="Telefon (Mobil)">
<UInput v-model="profile.mobile_tel" /> <UInput v-model="profile.mobile_tel" />
</UFormGroup> </UFormField>
<UFormGroup label="Telefon (Festnetz)"> <UFormField label="Telefon (Festnetz)">
<UInput v-model="profile.fixed_tel" /> <UInput v-model="profile.fixed_tel" />
</UFormGroup> </UFormField>
<UFormGroup label="Geburtstag"> <UFormField label="Geburtstag">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UInput type="date" v-model="profile.birthday" class="flex-1" /> <UInput type="date" v-model="profile.birthday" class="flex-1" />
<UButton color="gray" variant="soft" label="Heute" @click="setProfileDate('birthday')" /> <UButton color="gray" variant="soft" label="Heute" @click="setProfileDate('birthday')" />
</div> </div>
</UFormGroup> </UFormField>
</UForm> </UForm>
</UCard> </UCard>
<UCard v-if="!pending && profile" class="mt-3"> <UCard v-if="!pending && profile" class="mt-3">
<USeparator label="Vertragsinformationen" /> <USeparator label="Vertragsinformationen" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4"> <UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Vertragsart"> <UFormField label="Vertragsart">
<UInput v-model="profile.contract_type"/> <UInput v-model="profile.contract_type"/>
</UFormGroup> </UFormField>
<UFormGroup label="Status"> <UFormField label="Status">
<UInput v-model="profile.status"/> <UInput v-model="profile.status"/>
</UFormGroup> </UFormField>
<UFormGroup label="Position"> <UFormField label="Position">
<UInput v-model="profile.position"/> <UInput v-model="profile.position"/>
</UFormGroup> </UFormField>
<UFormGroup label="Qualifikation"> <UFormField label="Qualifikation">
<UInput v-model="profile.qualification"/> <UInput v-model="profile.qualification"/>
</UFormGroup> </UFormField>
<UFormGroup label="Eintrittsdatum"> <UFormField label="Eintrittsdatum">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<UInput type="date" v-model="profile.entry_date" class="flex-1" /> <UInput type="date" v-model="profile.entry_date" class="flex-1" />
<UButton color="gray" variant="soft" label="Heute" @click="setProfileDate('entry_date')" /> <UButton color="gray" variant="soft" label="Heute" @click="setProfileDate('entry_date')" />
</div> </div>
</UFormGroup> </UFormField>
<UFormGroup label="Wöchentliche Arbeitszeit (Std)"> <UFormField label="Wöchentliche Arbeitszeit (Std)">
<UInput type="number" v-model="profile.weekly_working_hours" /> <UInput type="number" v-model="profile.weekly_working_hours" />
</UFormGroup> </UFormField>
<UFormGroup label="Bezahlte Urlaubstage (Jahr)"> <UFormField label="Bezahlte Urlaubstage (Jahr)">
<UInput type="number" v-model="profile.annual_paid_leave_days" /> <UInput type="number" v-model="profile.annual_paid_leave_days" />
</UFormGroup> </UFormField>
<UFormGroup label="Aktiv"> <UFormField label="Aktiv">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<UToggle v-model="profile.active" color="primary" /> <USwitch v-model="profile.active" color="primary" />
<span class="text-sm text-gray-600"> <span class="text-sm text-gray-600">
</span> </span>
</div> </div>
</UFormGroup> </UFormField>
</UForm> </UForm>
</UCard> </UCard>
@@ -259,19 +259,19 @@ onMounted(fetchProfile)
<USeparator label="Adresse & Standort" /> <USeparator label="Adresse & Standort" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4"> <UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Straße und Hausnummer"> <UFormField label="Straße und Hausnummer">
<UInput v-model="profile.address_street"/> <UInput v-model="profile.address_street"/>
</UFormGroup> </UFormField>
<UFormGroup label="PLZ"> <UFormField label="PLZ">
<UInput type="text" v-model="profile.address_zip" @focusout="checkZip"/> <UInput type="text" v-model="profile.address_zip" @focusout="checkZip"/>
</UFormGroup> </UFormField>
<UFormGroup label="Ort"> <UFormField label="Ort">
<UInput v-model="profile.address_city"/> <UInput v-model="profile.address_city"/>
</UFormGroup> </UFormField>
<UFormGroup label="Bundesland"> <UFormField label="Bundesland">
<USelectMenu <USelectMenu
v-model="profile.state_code" v-model="profile.state_code"
:options="bundeslaender" :options="bundeslaender"
@@ -279,7 +279,7 @@ onMounted(fetchProfile)
option-attribute="name" option-attribute="name"
placeholder="Bundesland auswählen" placeholder="Bundesland auswählen"
/> />
</UFormGroup> </UFormField>
</UForm> </UForm>
</UCard> </UCard>
@@ -315,21 +315,21 @@ onMounted(fetchProfile)
<UCard v-if="!pending && profile" class="mt-3"> <UCard v-if="!pending && profile" class="mt-3">
<USeparator label="Sonstiges" /> <USeparator label="Sonstiges" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4"> <UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Kleidergröße (Oberteil)"> <UFormField label="Kleidergröße (Oberteil)">
<UInput v-model="profile.clothing_size_top" /> <UInput v-model="profile.clothing_size_top" />
</UFormGroup> </UFormField>
<UFormGroup label="Kleidergröße (Hose)"> <UFormField label="Kleidergröße (Hose)">
<UInput v-model="profile.clothing_size_bottom" /> <UInput v-model="profile.clothing_size_bottom" />
</UFormGroup> </UFormField>
<UFormGroup label="Schuhgröße"> <UFormField label="Schuhgröße">
<UInput v-model="profile.clothing_size_shoe" /> <UInput v-model="profile.clothing_size_shoe" />
</UFormGroup> </UFormField>
<UFormGroup label="Token-ID"> <UFormField label="Token-ID">
<UInput v-model="profile.token_id" /> <UInput v-model="profile.token_id" />
</UFormGroup> </UFormField>
</UForm> </UForm>
</UCard> </UCard>

View File

@@ -41,7 +41,7 @@
<UTable <UTable
:data="items" :data="items"
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
@select="(i) => navigateTo(`/staff/profiles/${i.id}`)" :on-select="(i) => navigateTo(`/staff/profiles/${i.id}`)"
> >
</UTable> </UTable>

View File

@@ -672,70 +672,72 @@ const truncateValue = (value, maxLength) => {
</UDashboardPanelContent> </UDashboardPanelContent>
<!-- Mobile Filter Slideover --> <!-- Mobile Filter Slideover -->
<USlideover <USlideover
v-model="showMobileFilter" v-model:open="showMobileFilter"
side="bottom" side="bottom"
:ui="{ width: '100%', height: 'auto', maxHeight: '90vh' }" :ui="{ width: '100%', height: 'auto', maxHeight: '90vh' }"
class="pb-[env(safe-area-inset-bottom)]" class="pb-[env(safe-area-inset-bottom)]"
> >
<!-- Header --> <template #body>
<div class="p-4 border-b flex items-center justify-between flex-shrink-0"> <!-- Header -->
<h2 class="text-xl font-bold">Filter</h2> <div class="p-4 border-b flex items-center justify-between flex-shrink-0">
<UButton <h2 class="text-xl font-bold">Filter</h2>
icon="i-heroicons-x-mark" <UButton
variant="ghost" icon="i-heroicons-x-mark"
@click="showMobileFilter = false" variant="ghost"
/> @click="showMobileFilter = false"
</div>
<!-- Scrollable content -->
<div class="flex-1 overflow-y-auto p-4 space-y-6">
<div
v-for="column in dataType.templateColumns.filter(c => c.distinct)"
:key="column.key"
class="space-y-2"
>
<p class="font-semibold">{{ column.label }}</p>
<USelectMenu
v-model="columnsToFilter[column.key]"
:options="itemsMeta?.distinctValues?.[column.key]"
multiple
searchable
:search-attributes="[column.key]"
placeholder="Auswählen"
:ui-menu="{ width: '100%' }"
/> />
</div> </div>
</div> <!-- Scrollable content -->
<div class="flex-1 overflow-y-auto p-4 space-y-6">
<!-- Footer FIXED in card --> <div
<div v-for="column in dataType.templateColumns.filter(c => c.distinct)"
class=" :key="column.key"
flex justify-between gap-3 class="space-y-2"
px-4 py-4 border-t flex-shrink-0 >
bg-white dark:bg-gray-900 <p class="font-semibold">{{ column.label }}</p>
rounded-b-2xl
"
>
<UButton
color="error"
variant="outline"
class="flex-1"
@click="resetMobileFilters"
>
Zurücksetzen
</UButton>
<UButton <USelectMenu
color="primary" v-model="columnsToFilter[column.key]"
class="flex-1" :options="itemsMeta?.distinctValues?.[column.key]"
@click="applyMobileFilters" multiple
searchable
:search-attributes="[column.key]"
placeholder="Auswählen"
:ui-menu="{ width: '100%' }"
/>
</div>
</div>
<!-- Footer FIXED in card -->
<div
class="
flex justify-between gap-3
px-4 py-4 border-t flex-shrink-0
bg-white dark:bg-gray-900
rounded-b-2xl
"
> >
Anwenden <UButton
</UButton> color="error"
</div> variant="outline"
class="flex-1"
@click="resetMobileFilters"
>
Zurücksetzen
</UButton>
<UButton
color="primary"
class="flex-1"
@click="applyMobileFilters"
>
Anwenden
</UButton>
</div>
</template>
</USlideover> </USlideover>

View File

@@ -50,60 +50,7 @@ const addMessage = async () => {
<template #center> <template #center>
{{itemInfo.title}} {{itemInfo.title}}
</template> </template>
<template #right> <template #right />
<!-- <UButton
v-if="profileStore.currentTenant === 5"
variant="outline"
@click="closeTicket"
>
Ticket Schließen
</UButton>
<UButton
v-if="profileStore.currentTenant === 5"
variant="outline"
@click="showAddEntryModal = true"
>
+ Eintrag
</UButton>
<UModal v-model:open="showAddEntryModal">
<UCard>
<template #header>
Eintrag hinzufügen
</template>
<UFormGroup
label="Intern:"
>
<UToggle
v-model="addEntryData.internal"
/>
</UFormGroup>
<UFormGroup
label="Typ:"
>
<USelectMenu
v-model="addEntryData.type"
:options="['Nachricht','Notiz','Anruf','Externe Kommunikation']"
/>
</UFormGroup>
<UFormGroup
label="Inhalt:"
>
<UTextarea
v-model="addEntryData.content"
/>
</UFormGroup>
<template #footer>
<UButton
@click="addEntry"
>
Erstellen
</UButton>
</template>
</UCard>
</UModal>-->
</template>
</UDashboardNavbar> </UDashboardNavbar>
<UDashboardPanelContent v-if="loaded"> <UDashboardPanelContent v-if="loaded">

View File

@@ -40,20 +40,20 @@ const createTicket = async () => {
</template> </template>
</UDashboardNavbar> </UDashboardNavbar>
<UForm class="w-2/3 mx-auto mt-5"> <UForm class="w-2/3 mx-auto mt-5">
<UFormGroup <UFormField
label="Titel:" label="Titel:"
> >
<UInput <UInput
v-model="itemInfo.title" v-model="itemInfo.title"
/> />
</UFormGroup> </UFormField>
<UFormGroup <UFormField
label="Nachricht:" label="Nachricht:"
> >
<UTextarea <UTextarea
v-model="itemInfo.content" v-model="itemInfo.content"
/> />
</UFormGroup> </UFormField>
</UForm> </UForm>
</template> </template>

View File

@@ -59,13 +59,13 @@ const filteredRows = computed(() => {
/> />
<USelectMenu <USelectMenu
v-if="profileStore.currentTenant === 5" v-if="profileStore.currentTenant === 5"
:ui-menu="{ width: 'min-w-max' }" :content="{ width: 'min-w-max' }"
:options="tenants" :items="tenants"
option-attribute="name" label-key="name"
value-attribute="id" value-key="id"
v-model="selectedTenant" v-model="selectedTenant"
> >
<template #label> <template #default>
{{selectedTenant ? tenants.find(i => i.id === selectedTenant).name : "Nicht nach Tenant filtern"}} {{selectedTenant ? tenants.find(i => i.id === selectedTenant).name : "Nicht nach Tenant filtern"}}
</template> </template>
</USelectMenu> </USelectMenu>
@@ -73,25 +73,25 @@ const filteredRows = computed(() => {
</UDashboardToolbar> </UDashboardToolbar>
<UTable <UTable
:data="filteredRows" :data="filteredRows"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine Tickets anzuzeigen` }" :empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine Tickets anzuzeigen` }"
@select="(i) => router.push(`/support/${i.id}`)" :on-select="(i) => router.push(`/support/${i.id}`)"
:columns="normalizeTableColumns([{key:'created_at',label:'Datum'}, ...profileStore.currentTenant === 5 ? [{key:'tenant',label:'Tenant'}] : [],{key:'status',label:'Status'},{key:'title',label:'Titel'},{key:'created_by',label:'Ersteller'},{key:'ticketmessages',label:'Nachrichten'}])" :columns="normalizeTableColumns([{key:'created_at',label:'Datum'}, ...profileStore.currentTenant === 5 ? [{key:'tenant',label:'Tenant'}] : [],{key:'status',label:'Status'},{key:'title',label:'Titel'},{key:'created_by',label:'Ersteller'},{key:'ticketmessages',label:'Nachrichten'}])"
> >
<template #tenant-data="{ row }"> <template #tenant-cell="{ row }">
{{row.tenant.name}} {{row.original.tenant.name}}
</template> </template>
<template #status-data="{ row }"> <template #status-cell="{ row }">
<span v-if="row.status === 'Offen'" class="text-yellow-500">Offen</span> <span v-if="row.original.status === 'Offen'" class="text-yellow-500">Offen</span>
<span v-else-if="row.status === 'Geschlossen'" class="text-primary">Geschlossen</span> <span v-else-if="row.original.status === 'Geschlossen'" class="text-primary">Geschlossen</span>
</template> </template>
<template #created_by-data="{ row }"> <template #created_by-cell="{ row }">
{{row.created_by.fullName}} {{row.original.created_by.fullName}}
</template> </template>
<template #created_at-data="{ row }"> <template #created_at-cell="{ row }">
{{dayjs(row.created_at).format('DD.MM.YYYY HH:mm')}} {{dayjs(row.original.created_at).format('DD.MM.YYYY HH:mm')}}
</template> </template>
<template #ticketmessages-data="{ row }"> <template #ticketmessages-cell="{ row }">
{{row.ticketmessages.length}} {{row.original.ticketmessages.length}}
</template> </template>
</UTable> </UTable>
</template> </template>

View File

@@ -460,34 +460,34 @@ onMounted(async () => {
v-else-if="filteredTasks.length" v-else-if="filteredTasks.length"
:data="filteredTasks" :data="filteredTasks"
:columns="normalizedListColumns" :columns="normalizedListColumns"
@select="(task) => openTaskViaRoute(task)" :on-select="(task) => openTaskViaRoute(task)"
> >
<template #actions-data="{ row }"> <template #actions-cell="{ row }">
<UButton <UButton
v-if="normalizeStatus(row.categorie) !== 'Abgeschlossen' && canCreate" v-if="normalizeStatus(row.original.categorie) !== 'Abgeschlossen' && canCreate"
size="xs" size="xs"
variant="soft" variant="soft"
icon="i-heroicons-check" icon="i-heroicons-check"
:loading="quickCompleteLoadingId === row.id" :loading="quickCompleteLoadingId === row.original.id"
@click.stop="completeTaskQuick(row)" @click.stop="completeTaskQuick(row.original)"
> >
Erledigt Erledigt
</UButton> </UButton>
</template> </template>
<template #categorie-data="{ row }"> <template #categorie-cell="{ row }">
<UBadge variant="soft">{{ normalizeStatus(row.categorie) }}</UBadge> <UBadge variant="soft">{{ normalizeStatus(row.original.categorie) }}</UBadge>
</template> </template>
<template #assignee-data="{ row }"> <template #assignee-cell="{ row }">
{{ getAssigneeLabel(row) }} {{ getAssigneeLabel(row.original) }}
</template> </template>
<template #project-data="{ row }"> <template #project-cell="{ row }">
{{ getEntityLabel(projectOptions, row.project?.id || row.project) || "-" }} {{ getEntityLabel(projectOptions, row.original.project?.id || row.original.project) || "-" }}
</template> </template>
<template #customer-data="{ row }"> <template #customer-cell="{ row }">
{{ getEntityLabel(customerOptions, row.customer?.id || row.customer) || "-" }} {{ getEntityLabel(customerOptions, row.original.customer?.id || row.original.customer) || "-" }}
</template> </template>
<template #plant-data="{ row }"> <template #plant-cell="{ row }">
{{ getEntityLabel(plantOptions, row.plant?.id || row.plant) || "-" }} {{ getEntityLabel(plantOptions, row.original.plant?.id || row.original.plant) || "-" }}
</template> </template>
</UTable> </UTable>
<UAlert <UAlert
@@ -499,13 +499,14 @@ onMounted(async () => {
</UDashboardPanelContent> </UDashboardPanelContent>
<UModal v-model:open="isModalOpen" :prevent-close="saving || deleting"> <UModal v-model:open="isModalOpen" :prevent-close="saving || deleting">
<UCard> <template #content>
<template #header> <UCard>
<div class="flex items-center justify-between"> <template #header>
<h3 class="font-semibold">{{ modalTitle }}</h3> <div class="flex items-center justify-between">
<UBadge variant="subtle">{{ taskForm.id ? `#${taskForm.id}` : "Neu" }}</UBadge> <h3 class="font-semibold">{{ modalTitle }}</h3>
</div> <UBadge variant="subtle">{{ taskForm.id ? `#${taskForm.id}` : "Neu" }}</UBadge>
</template> </div>
</template>
<div class="space-y-3"> <div class="space-y-3">
<div> <div>
@@ -519,105 +520,106 @@ onMounted(async () => {
<USelectMenu <USelectMenu
v-model="taskForm.categorie" v-model="taskForm.categorie"
:options="STATUS_COLUMNS.map((status) => ({ label: status, value: status }))" :items="STATUS_COLUMNS.map((status) => ({ label: status, value: status }))"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
:disabled="isFormReadonly || !canCreate" :disabled="isFormReadonly || !canCreate"
> >
<template #label> <template #default>
{{ taskForm.categorie || "Status auswählen" }} {{ taskForm.categorie || "Status auswählen" }}
</template> </template>
</USelectMenu> </USelectMenu>
<USelectMenu <USelectMenu
v-model="taskForm.userId" v-model="taskForm.userId"
:options="assigneeOptions" :items="assigneeOptions"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
:disabled="isFormReadonly || !canCreate" :disabled="isFormReadonly || !canCreate"
searchable searchable
> >
<template #label> <template #default>
{{ assigneeOptions.find((option) => option.value === taskForm.userId)?.label || "Zuweisung" }} {{ assigneeOptions.find((option) => option.value === taskForm.userId)?.label || "Zuweisung" }}
</template> </template>
</USelectMenu> </USelectMenu>
<USelectMenu <USelectMenu
v-model="taskForm.project" v-model="taskForm.project"
:options="projectOptions" :items="projectOptions"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
:disabled="isFormReadonly || !canCreate" :disabled="isFormReadonly || !canCreate"
searchable searchable
clear-search-on-close clear-search-on-close
> >
<template #label> <template #default>
{{ getEntityLabel(projectOptions, taskForm.project) || "Projekt" }} {{ getEntityLabel(projectOptions, taskForm.project) || "Projekt" }}
</template> </template>
</USelectMenu> </USelectMenu>
<USelectMenu <USelectMenu
v-model="taskForm.customer" v-model="taskForm.customer"
:options="customerOptions" :items="customerOptions"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
:disabled="isFormReadonly || !canCreate" :disabled="isFormReadonly || !canCreate"
searchable searchable
clear-search-on-close clear-search-on-close
> >
<template #label> <template #default>
{{ getEntityLabel(customerOptions, taskForm.customer) || "Kunde" }} {{ getEntityLabel(customerOptions, taskForm.customer) || "Kunde" }}
</template> </template>
</USelectMenu> </USelectMenu>
<USelectMenu <USelectMenu
v-model="taskForm.plant" v-model="taskForm.plant"
:options="plantOptions" :items="plantOptions"
value-attribute="value" value-key="value"
option-attribute="label" label-key="label"
:disabled="isFormReadonly || !canCreate" :disabled="isFormReadonly || !canCreate"
searchable searchable
clear-search-on-close clear-search-on-close
> >
<template #label> <template #default>
{{ getEntityLabel(plantOptions, taskForm.plant) || "Objekt" }} {{ getEntityLabel(plantOptions, taskForm.plant) || "Objekt" }}
</template> </template>
</USelectMenu> </USelectMenu>
</div> </div>
<template #footer> <template #footer>
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<UButton <UButton
v-if="taskForm.id && canCreate" v-if="taskForm.id && canCreate"
variant="soft" variant="soft"
:loading="deleting" :loading="deleting"
@click="archiveTask" @click="archiveTask"
> >
Archivieren Archivieren
</UButton> </UButton>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<UButton variant="ghost" @click="closeModal">Schließen</UButton> <UButton variant="ghost" @click="closeModal">Schließen</UButton>
<UButton <UButton
v-if="modalMode === 'show' && canCreate" v-if="modalMode === 'show' && canCreate"
variant="soft" variant="soft"
@click="modalMode = 'edit'" @click="modalMode = 'edit'"
> >
Bearbeiten Bearbeiten
</UButton> </UButton>
<UButton <UButton
v-if="modalMode !== 'show' && canCreate" v-if="modalMode !== 'show' && canCreate"
:loading="saving" :loading="saving"
@click="saveTask" @click="saveTask"
> >
Speichern Speichern
</UButton> </UButton>
</div>
</div> </div>
</div> </template>
</template> </UCard>
</UCard> </template>
</UModal> </UModal>
</template> </template>

View File

@@ -92,33 +92,35 @@
</main> </main>
<UModal v-model:open="isModalOpen"> <UModal v-model:open="isModalOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }"> <template #content>
<template #header> <UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<div class="flex items-center justify-between"> <template #header>
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">{{ modalTitle }}</h3> <div class="flex items-center justify-between">
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isModalOpen = false" /> <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">{{ modalTitle }}</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isModalOpen = false" />
</div>
</template>
<div v-if="modalState.type === 'create'" class="space-y-4">
<UFormField label="Titel" name="title">
<UInput v-model="modalState.inputValue" autofocus placeholder="z.B. Meeting Notes" @keyup.enter="handleModalConfirm" />
</UFormField>
</div> </div>
</template> <div v-else-if="modalState.type === 'delete'">
<div v-if="modalState.type === 'create'" class="space-y-4"> <p class="text-sm text-gray-500 dark:text-gray-300">
<UFormGroup label="Titel" name="title"> Möchtest du <strong>"{{ modalState.targetItem?.title }}"</strong> wirklich löschen?
<UInput v-model="modalState.inputValue" autofocus placeholder="z.B. Meeting Notes" @keyup.enter="handleModalConfirm" /> <br><span v-if="modalState.targetItem?.isFolder" class="text-red-500 font-medium">Alle Unterseiten werden ebenfalls gelöscht.</span>
</UFormGroup> </p>
</div>
<div v-else-if="modalState.type === 'delete'">
<p class="text-sm text-gray-500 dark:text-gray-300">
Möchtest du <strong>"{{ modalState.targetItem?.title }}"</strong> wirklich löschen?
<br><span v-if="modalState.targetItem?.isFolder" class="text-red-500 font-medium">Alle Unterseiten werden ebenfalls gelöscht.</span>
</p>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<UButton color="gray" variant="ghost" @click="isModalOpen = false">Abbrechen</UButton>
<UButton :color="modalState.type === 'delete' ? 'red' : 'primary'" :loading="modalLoading" @click="handleModalConfirm">
{{ modalState.type === 'delete' ? 'Löschen' : 'Erstellen' }}
</UButton>
</div> </div>
</template> <template #footer>
</UCard> <div class="flex justify-end gap-2">
<UButton color="gray" variant="ghost" @click="isModalOpen = false">Abbrechen</UButton>
<UButton :color="modalState.type === 'delete' ? 'red' : 'primary'" :loading="modalLoading" @click="handleModalConfirm">
{{ modalState.type === 'delete' ? 'Löschen' : 'Erstellen' }}
</UButton>
</div>
</template>
</UCard>
</template>
</UModal> </UModal>
</div> </div>