Merge branch 'beta'
# Conflicts: # components/DocumentDisplay.vue # components/MainNav.vue # composables/useRole.js # composables/useSupabase.js # pages/createDocument/edit/[[id]].vue # pages/files/index.vue # pages/inventoryitems/index.vue # pages/services/[mode]/[[id]].vue # pages/vendors/[mode]/[[id]].vue # stores/data.js
This commit is contained in:
@@ -18,6 +18,7 @@ const {color,variant} = props
|
||||
const showModal = ref(false)
|
||||
|
||||
const emitConfirm = () => {
|
||||
showModal.value = false
|
||||
emit('confirmed')
|
||||
}
|
||||
</script>
|
||||
@@ -38,19 +39,22 @@ const emitConfirm = () => {
|
||||
<slot/>
|
||||
<template #footer>
|
||||
<div class="text-right">
|
||||
<UButton
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@click="showModal = false"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="emitConfirm"
|
||||
class="ml-2"
|
||||
>
|
||||
Bestätigen
|
||||
</UButton>
|
||||
<UButtonGroup>
|
||||
<UButton
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@click="showModal = false"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="emitConfirm"
|
||||
class="ml-2"
|
||||
>
|
||||
Bestätigen
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<script setup>
|
||||
|
||||
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
||||
|
||||
const toast = useToast()
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const modal = useModal()
|
||||
const profileStore = useProfileStore()
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
documentData: {
|
||||
@@ -19,129 +23,26 @@ const props = defineProps({
|
||||
|
||||
})
|
||||
|
||||
let {documentData, openShowModal:openShowModalProp, returnEmit } = props;
|
||||
const tags = dataStore.getDocumentTags
|
||||
const openShowModal = ref(false)
|
||||
|
||||
//Functions
|
||||
const openDocument = async () => {
|
||||
//selectedDocument.value = doc
|
||||
openShowModal.value = true
|
||||
console.log("open")
|
||||
}
|
||||
|
||||
const updateDocument = async () => {
|
||||
console.log("Update")
|
||||
|
||||
const {url, ...objData} = documentData
|
||||
delete objData.url
|
||||
let {documentData, returnEmit } = props;
|
||||
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("documents")
|
||||
.update(objData)
|
||||
.eq('id',objData.id)
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
toast.add({title: "Dokument aktualisiert"})
|
||||
dataStore.fetchDocuments()
|
||||
//openShowModal.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const createVendorInvoice = async () => {
|
||||
const {data:vendorInvoiceData,error:vendorInvoiceError} = await supabase
|
||||
.from("incominginvoices")
|
||||
.insert([{
|
||||
document: documentData.id,
|
||||
tenant: dataStore.currentTenant
|
||||
}])
|
||||
.select()
|
||||
if(vendorInvoiceError) {
|
||||
console.log(vendorInvoiceError)
|
||||
} else if(vendorInvoiceData) {
|
||||
|
||||
const {data:documentUpdateData,error:documentError} = await supabase
|
||||
.from("documents")
|
||||
.update({
|
||||
vendorInvoice: vendorInvoiceData[0].id
|
||||
})
|
||||
.eq('id',documentData.id)
|
||||
.select()
|
||||
|
||||
if(documentError) {
|
||||
console.log(documentError)
|
||||
} else {
|
||||
toast.add({title: "Dokument aktualisiert"})
|
||||
dataStore.fetchDocuments()
|
||||
openShowModal.value = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
dataStore.fetchIncomingInvoices()
|
||||
await router.push("/receipts")
|
||||
}
|
||||
|
||||
const showFile = (file) => {
|
||||
console.log(file)
|
||||
modal.open(DocumentDisplayModal,{
|
||||
documentData: file
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const archiveDocument = () => {
|
||||
documentData.tags.push("Archiviert")
|
||||
updateDocument()
|
||||
}
|
||||
|
||||
const resourceOptions = ref([
|
||||
{label: 'Projekt', value: 'project', optionAttr: "name"},
|
||||
{label: 'Kunde', value: 'customer', optionAttr: "name"},
|
||||
{label: 'Lieferant', value: 'vendor', optionAttr: "name"},
|
||||
{label: 'Fahrzeug', value: 'vehicle', optionAttr: "licensePlate"},
|
||||
{label: 'Objekt', value: 'plant', optionAttr: "name"},
|
||||
{label: 'Vertrag', value: 'contract', optionAttr: "name"},
|
||||
{label: 'Produkt', value: 'product', optionAttr: "name"}
|
||||
])
|
||||
const resourceToAssign = ref("project")
|
||||
const itemOptions = ref([])
|
||||
const idToAssign = ref(null)
|
||||
const getItemsBySelectedResource = () => {
|
||||
if(resourceToAssign.value === "project") {
|
||||
itemOptions.value = dataStore.projects
|
||||
} else if(resourceToAssign.value === "customer") {
|
||||
itemOptions.value = dataStore.customers
|
||||
} else if(resourceToAssign.value === "vendor") {
|
||||
itemOptions.value = dataStore.vendors
|
||||
} else if(resourceToAssign.value === "vehicle") {
|
||||
itemOptions.value = dataStore.vehicles
|
||||
} else if(resourceToAssign.value === "product") {
|
||||
itemOptions.value = dataStore.products
|
||||
} else if(resourceToAssign.value === "plant") {
|
||||
itemOptions.value = dataStore.plants
|
||||
} else if(resourceToAssign.value === "contract") {
|
||||
itemOptions.value = dataStore.contracts
|
||||
} else {
|
||||
itemOptions.value = []
|
||||
}
|
||||
}
|
||||
getItemsBySelectedResource()
|
||||
|
||||
const updateDocumentAssignment = async () => {
|
||||
documentData[resourceToAssign.value] = idToAssign.value
|
||||
await updateDocument()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="documentListItem" @click="returnEmit ? $emit('clicked', documentData.id) : openShowModal = true">
|
||||
<div :id="`docDisplay-${documentData.id}`" class="documentListItem" @click="returnEmit ? $emit('clicked', documentData.id) : showFile(documentData)">
|
||||
<iframe
|
||||
:src="`${documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
|
||||
class="previewEmbed"
|
||||
v-if="!documentData.tags.includes('Bild')"
|
||||
v-if="documentData.path.includes('pdf')"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
@@ -150,195 +51,17 @@ const updateDocumentAssignment = async () => {
|
||||
:src="documentData.url"
|
||||
/>
|
||||
<!-- TODO: Remove Scrollbar -->
|
||||
<UTooltip class="w-full" :text="documentData.path.split('_')[documentData.path.split('_').length -1]">
|
||||
<p class="truncate my-3">{{documentData.path.split("_")[documentData.path.split("_").length -1]}}</p>
|
||||
<UTooltip class="w-full" :text="documentData.path.split('/')[documentData.path.split('/').length -1]">
|
||||
<p class="truncate my-3">{{documentData.path.split("/")[documentData.path.split("/").length -1]}}</p>
|
||||
</UTooltip>
|
||||
|
||||
<InputGroup class="mt-3 flex-wrap">
|
||||
<UBadge
|
||||
v-for="tag in documentData.tags"
|
||||
v-for="tag in documentData.filetags"
|
||||
|
||||
><span class="text-nowrap">{{ tag }}</span></UBadge>
|
||||
><span class="text-nowrap">{{ tag.name }}</span></UBadge>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
<!-- <UButton
|
||||
@click="openDocument"
|
||||
class="mt-3"
|
||||
icon=""
|
||||
>
|
||||
<UIcon name="i-heroicons-eye-solid" />
|
||||
</UButton>-->
|
||||
<!-- <UToggle
|
||||
v-model="documentData.selected"
|
||||
class="ml-2"
|
||||
/>-->
|
||||
<!-- <UBadge
|
||||
v-if="documentData.vendorInvoice"
|
||||
>{{dataStore.incominginvoices.find(item => item.id === documentData.vendorInvoice) ? dataStore.incominginvoices.find(item => item.id === documentData.vendorInvoice).reference : ''}}</UBadge>
|
||||
<UBadge
|
||||
v-if="documentData.inDatev"
|
||||
>DATEV</UBadge>-->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<USlideover
|
||||
v-model="openShowModal"
|
||||
fullscreen
|
||||
>
|
||||
<UCard class="flex flex-col flex-1" :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge
|
||||
v-for="tag in documentData.tags"
|
||||
>
|
||||
{{tag}}
|
||||
</UBadge>
|
||||
</div>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="openShowModal = false" />
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<UContainer class="h-full" :ui="{padding: 'px-1 sm:px-1 lg:px-1'}">
|
||||
<object
|
||||
class="bigPreview"
|
||||
:data="`${documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
|
||||
type="application/pdf"
|
||||
v-if="!documentData.tags.includes('Bild')"
|
||||
|
||||
/>
|
||||
<img
|
||||
class=" w-full"
|
||||
:src="documentData.url"
|
||||
alt=""
|
||||
v-else
|
||||
/>
|
||||
</UContainer>
|
||||
|
||||
<template #footer>
|
||||
|
||||
<UButtonGroup>
|
||||
<UButton
|
||||
@click="archiveDocument"
|
||||
>
|
||||
Archivieren
|
||||
</UButton>
|
||||
<!-- <UButton
|
||||
v-if="documentData.tags.includes('Eingangsrechnung')"
|
||||
@click="createVendorInvoice"
|
||||
>
|
||||
Eingangsrechnung erstellen
|
||||
</UButton>-->
|
||||
</UButtonGroup>
|
||||
<br>
|
||||
<a
|
||||
:href="documentData.url"
|
||||
target="_blank"
|
||||
>In neuen Tab anzeigen</a>
|
||||
|
||||
<UFormGroup
|
||||
label="Tags ändern:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="tags"
|
||||
v-model="documentData.tags"
|
||||
@change="updateDocument"
|
||||
multiple
|
||||
>
|
||||
<template #label>
|
||||
{{documentData.tags.length}} ausgewählt
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<p>Dokument zuweisen:</p>
|
||||
<UFormGroup
|
||||
label="Resource auswählen"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="resourceOptions"
|
||||
v-model="resourceToAssign"
|
||||
value-attribute="value"
|
||||
option-attribute="label"
|
||||
@change="getItemsBySelectedResource"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Eintrag auswählen:"
|
||||
>
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
<USelectMenu
|
||||
:options="itemOptions"
|
||||
v-model="idToAssign"
|
||||
:option-attribute="resourceOptions.find(i => i.value === resourceToAssign)? resourceOptions.find(i => i.value === resourceToAssign).optionAttr : 'name'"
|
||||
value-attribute="id"
|
||||
@change="updateDocumentAssignment"
|
||||
></USelectMenu>
|
||||
|
||||
|
||||
<!-- <UFormGroup
|
||||
label="Projekt zuweisen:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.projects"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
v-model="documentData.project"
|
||||
@change="updateDocument"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.projects.find(item => item.id === documentData.project) ? dataStore.projects.find(item => item.id === documentData.project).name : "Kein Projekt ausgewählt" }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Kunde zuweisen:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.customers"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
v-model="documentData.customer"
|
||||
@change="updateDocument"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.customers.find(item => item.id === documentData.customer) ? dataStore.customers.find(item => item.id === documentData.customer).name : "Kein Kunde ausgewählt" }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>-->
|
||||
|
||||
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
|
||||
<!-- <UCard class="h-full">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</UCard>-->
|
||||
</USlideover>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -370,9 +93,4 @@ const updateDocumentAssignment = async () => {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bigPreview {
|
||||
width: 100%;
|
||||
aspect-ratio: 1/ 1.414;
|
||||
}
|
||||
|
||||
</style>
|
||||
368
components/DocumentDisplayModal.vue
Normal file
368
components/DocumentDisplayModal.vue
Normal file
@@ -0,0 +1,368 @@
|
||||
<script setup>
|
||||
|
||||
const toast = useToast()
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const modal = useModal()
|
||||
const props = defineProps({
|
||||
documentData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
openShowModal: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
returnEmit: {
|
||||
type: Boolean
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
const emit = defineEmits(["updateNeeded"])
|
||||
|
||||
const folders = ref([])
|
||||
const setup = async () => {
|
||||
const {data} = await supabase.from("folders").select().eq("tenant",useProfileStore().currentTenant)
|
||||
|
||||
data.forEach(folder => {
|
||||
let name = folder.name
|
||||
|
||||
const addParent = (item) => {
|
||||
name = `${item.name} > ${name}`
|
||||
|
||||
if(item.parent){
|
||||
addParent(data.find(i => i.id === item.parent))
|
||||
} else {
|
||||
folders.value.push({
|
||||
id: folder.id,
|
||||
name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if(folder.parent) {
|
||||
addParent(data.find(i => i.id === folder.parent))
|
||||
} else {
|
||||
folders.value.push({
|
||||
id: folder.id,
|
||||
name: folder.name,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
|
||||
//Functions
|
||||
const openDocument = async () => {
|
||||
//selectedDocument.value = doc
|
||||
openShowModal.value = true
|
||||
console.log("open")
|
||||
}
|
||||
|
||||
const updateDocument = async () => {
|
||||
const {url, ...objData} = props.documentData
|
||||
delete objData.url
|
||||
delete objData.filetags
|
||||
|
||||
if(objData.project) objData.project = objData.project.id
|
||||
if(objData.customer) objData.customer = objData.customer.id
|
||||
if(objData.contract) objData.contract = objData.contract.id
|
||||
if(objData.vendor) objData.vendor = objData.vendor.id
|
||||
if(objData.plant) objData.plant = objData.plant.id
|
||||
if(objData.createddocument) objData.createddocument = objData.createddocument.id
|
||||
if(objData.vehicle) objData.vehicle = objData.vehicle.id
|
||||
if(objData.product) objData.product = objData.product.id
|
||||
if(objData.profile) objData.profile = objData.profile.id
|
||||
if(objData.check) objData.check = objData.check.id
|
||||
if(objData.inventoryitem) objData.inventoryitem = objData.inventoryitem.id
|
||||
if(objData.incominginvoice) objData.incominginvoice = objData.incominginvoice.id
|
||||
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("files")
|
||||
.update(objData)
|
||||
.eq('id',objData.id)
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
} else {
|
||||
toast.add({title: "Datei aktualisiert"})
|
||||
modal.close()
|
||||
emit("updateNeeded")
|
||||
//openShowModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const archiveDocument = async () => {
|
||||
props.documentData.archived = true
|
||||
await updateDocument()
|
||||
|
||||
const {data,error} = await supabase.from("historyitems").insert({
|
||||
createdBy: useProfileStore().activeProfile.id,
|
||||
tenant: useProfileStore().currentTenant,
|
||||
text: "Datei archiviert",
|
||||
file: props.documentData.id
|
||||
})
|
||||
|
||||
modal.close()
|
||||
emit("update")
|
||||
}
|
||||
|
||||
const resourceOptions = ref([
|
||||
{label: 'Projekt', value: 'project', optionAttr: "name"},
|
||||
{label: 'Kunde', value: 'customer', optionAttr: "name"},
|
||||
{label: 'Lieferant', value: 'vendor', optionAttr: "name"},
|
||||
{label: 'Fahrzeug', value: 'vehicle', optionAttr: "licensePlate"},
|
||||
{label: 'Objekt', value: 'plant', optionAttr: "name"},
|
||||
{label: 'Vertrag', value: 'contract', optionAttr: "name"},
|
||||
{label: 'Produkt', value: 'product', optionAttr: "name"}
|
||||
])
|
||||
const resourceToAssign = ref("project")
|
||||
const itemOptions = ref([])
|
||||
const idToAssign = ref(null)
|
||||
const getItemsBySelectedResource = () => {
|
||||
if(resourceToAssign.value === "project") {
|
||||
itemOptions.value = dataStore.projects
|
||||
} else if(resourceToAssign.value === "customer") {
|
||||
itemOptions.value = dataStore.customers
|
||||
} else if(resourceToAssign.value === "vendor") {
|
||||
itemOptions.value = dataStore.vendors
|
||||
} else if(resourceToAssign.value === "vehicle") {
|
||||
itemOptions.value = dataStore.vehicles
|
||||
} else if(resourceToAssign.value === "product") {
|
||||
itemOptions.value = dataStore.products
|
||||
} else if(resourceToAssign.value === "plant") {
|
||||
itemOptions.value = dataStore.plants
|
||||
} else if(resourceToAssign.value === "contract") {
|
||||
itemOptions.value = dataStore.contracts
|
||||
} else {
|
||||
itemOptions.value = []
|
||||
}
|
||||
}
|
||||
getItemsBySelectedResource()
|
||||
|
||||
const updateDocumentAssignment = async () => {
|
||||
props.documentData[resourceToAssign.value] = idToAssign.value
|
||||
await updateDocument()
|
||||
}
|
||||
|
||||
const folderToMoveTo = ref(null)
|
||||
const moveFile = async () => {
|
||||
console.log(folderToMoveTo.value)
|
||||
const {data,error} = await supabase
|
||||
.from("files")
|
||||
.update({folder: folderToMoveTo.value})
|
||||
.eq("id",props.documentData.id)
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
toast.add({title: "Fehler beim verschieben", color:"rose"})
|
||||
} else {
|
||||
toast.add({title: "Datei verschoben"})
|
||||
console.log(data)
|
||||
}
|
||||
modal.close()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal fullscreen >
|
||||
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<UBadge
|
||||
v-for="tag in props.documentData.filetags"
|
||||
>
|
||||
{{tag.name}}
|
||||
</UBadge>
|
||||
</div>
|
||||
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="modal.close()" />
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<div class="flex flex-row">
|
||||
<div class="w-1/3">
|
||||
<object
|
||||
class="bigPreview"
|
||||
:data="`${props.documentData.url}#toolbar=0&navpanes=0&scrollbar=0`"
|
||||
type="application/pdf"
|
||||
v-if="props.documentData.path.includes('pdf')"
|
||||
|
||||
/>
|
||||
<img
|
||||
class=" w-full"
|
||||
:src="props.documentData.url"
|
||||
alt=""
|
||||
v-else
|
||||
/>
|
||||
</div>
|
||||
<div class="w-2/3 p-5">
|
||||
<UButtonGroup>
|
||||
<ButtonWithConfirm
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@confirmed="archiveDocument"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie die Datei wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
|
||||
<UButton
|
||||
:to="props.documentData.url"
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-top-right-on-square"
|
||||
target="_blank"
|
||||
>
|
||||
Öffnen
|
||||
</UButton>
|
||||
</UButtonGroup>
|
||||
|
||||
<UDivider>Zuweisungen</UDivider>
|
||||
|
||||
<table class="w-full">
|
||||
<tr v-if="props.documentData.project">
|
||||
<td>Projekt</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/projects/show/${props.documentData.project.id}`">{{props.documentData.project.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.customer">
|
||||
<td>Kunde</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/customers/show/${props.documentData.customer.id}`">{{props.documentData.customer.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.vendor">
|
||||
<td>Lieferant</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/vendors/show/${props.documentData.vendor.id}`">{{props.documentData.vendor.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.createddocument">
|
||||
<td>Ausgangsbeleg</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/createDocument/show/${props.documentData.createddocument.id}`">{{props.documentData.createddocument.documentNumber}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.plant">
|
||||
<td>Objekt</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/plants/show/${props.documentData.plant.id}`">{{props.documentData.plant.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.contract">
|
||||
<td>Vertrag</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/contracts/show/${props.documentData.contract.id}`">{{props.documentData.contract.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.vehicle">
|
||||
<td>Fahrzeug</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/vehicles/show/${props.documentData.vehicle.id}`">{{props.documentData.vehicle.licensePlate}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.product">
|
||||
<td>Artikel</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/products/show/${props.documentData.product.id}`">{{props.documentData.product.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.inventoryitem">
|
||||
<td>Inventarartikel</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/inventoryitem/show/${props.documentData.inventoryitem.id}`">{{props.documentData.inventoryitem.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.check">
|
||||
<td>Überprüfung</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/standardEntity/checks/show/${props.documentData.check.id}`">{{props.documentData.check.name}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.profile">
|
||||
<td>Mitarbeiter</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/profiles/show/${props.documentData.profile.id}`">{{props.documentData.profile.fullName}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="props.documentData.incominginvoice">
|
||||
<td>Eingangsrechnung</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/incomingInvoices/show/${props.documentData.incominginvoice.id}`">{{props.documentData.incominginvoice.reference}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<UDivider class="my-3">Datei zuweisen</UDivider>
|
||||
|
||||
<UFormGroup
|
||||
label="Resource auswählen"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="resourceOptions"
|
||||
v-model="resourceToAssign"
|
||||
value-attribute="value"
|
||||
option-attribute="label"
|
||||
@change="getItemsBySelectedResource"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Eintrag auswählen:"
|
||||
>
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
<USelectMenu
|
||||
:options="itemOptions"
|
||||
v-model="idToAssign"
|
||||
:option-attribute="resourceOptions.find(i => i.value === resourceToAssign)? resourceOptions.find(i => i.value === resourceToAssign).optionAttr : 'name'"
|
||||
value-attribute="id"
|
||||
@change="updateDocumentAssignment"
|
||||
></USelectMenu>
|
||||
|
||||
<UDivider class="my-5">Datei verschieben</UDivider>
|
||||
|
||||
<InputGroup class="w-full">
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
v-model="folderToMoveTo"
|
||||
value-attribute="id"
|
||||
option-attribute="name"
|
||||
:options="folders"
|
||||
/>
|
||||
<UButton
|
||||
@click="moveFile"
|
||||
variant="outline"
|
||||
>Verschieben</UButton>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.bigPreview {
|
||||
width: 100%;
|
||||
aspect-ratio: 1/ 1.414;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -10,6 +10,7 @@ const props = defineProps({
|
||||
})
|
||||
const dataStore = useDataStore()
|
||||
|
||||
const emit = defineEmits(["updateNeeded"])
|
||||
|
||||
</script>
|
||||
|
||||
@@ -21,6 +22,7 @@ const dataStore = useDataStore()
|
||||
:key="item.id"
|
||||
@clicked="(info) => $emit('selectDocument', info)"
|
||||
:return-emit="returnDocumentId"
|
||||
@updatedNeeded="emit('updatedNeeded')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,16 +11,25 @@ const props = defineProps({
|
||||
|
||||
const {type, elementId} = props
|
||||
|
||||
const emit = defineEmits(["uploadFinished"])
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const tags = dataStore.getDocumentTags
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const uploadModalOpen = ref(false)
|
||||
const uploadInProgress = ref(false)
|
||||
const fileUploadFormData = ref({
|
||||
tags: ["Dokument"],
|
||||
project: null,
|
||||
tenant: dataStore.currentTenant
|
||||
tenant: profileStore.currentTenant
|
||||
})
|
||||
const availableTags = ref([])
|
||||
const selectedTags = ref([])
|
||||
const setup = async () => {
|
||||
availableTags.value = await useSupabaseSelect("filetags")
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
|
||||
const openModal = () => {
|
||||
uploadModalOpen.value = true
|
||||
@@ -32,10 +41,11 @@ const uploadFiles = async () => {
|
||||
let fileData = fileUploadFormData.value
|
||||
fileData[type] = elementId
|
||||
|
||||
await dataStore.uploadFiles(fileData, document.getElementById("fileUploadInput").files,true)
|
||||
await useFiles().uploadFiles(fileData, document.getElementById("fileUploadInput").files,selectedTags.value,true)
|
||||
|
||||
uploadModalOpen.value = false;
|
||||
uploadInProgress.value = false;
|
||||
emit("uploadFinished")
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -43,7 +53,7 @@ const uploadFiles = async () => {
|
||||
<USlideover
|
||||
v-model="uploadModalOpen"
|
||||
>
|
||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }" class="h-full">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
@@ -67,6 +77,7 @@ const uploadFiles = async () => {
|
||||
type="file"
|
||||
id="fileUploadInput"
|
||||
multiple
|
||||
accept="image/jpeg, image/png, image/gif, application/pdf"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
@@ -75,13 +86,15 @@ const uploadFiles = async () => {
|
||||
>
|
||||
<USelectMenu
|
||||
multiple
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
searchable-placeholder="Suchen..."
|
||||
:options="tags"
|
||||
v-model="fileUploadFormData.tags"
|
||||
:options="availableTags"
|
||||
v-model="selectedTags"
|
||||
>
|
||||
<template #label>
|
||||
<span v-if="fileUploadFormData.tags.length > 0">{{fileUploadFormData.tags.join(", ")}}</span>
|
||||
<span v-if="selectedTags.length > 0">{{selectedTags.map(i => availableTags.find(x => x.id === i).name).join(", ")}}</span>
|
||||
<span v-else>Keine Tags ausgewählt</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
99
components/DocumentUploadModal.vue
Normal file
99
components/DocumentUploadModal.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup >
|
||||
|
||||
const props = defineProps({
|
||||
fileData: {
|
||||
type: Object,
|
||||
default: {
|
||||
type: null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(["uploadFinished"])
|
||||
|
||||
const modal = useModal()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const uploadInProgress = ref(false)
|
||||
const availableFiletypes = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
availableFiletypes.value = await useSupabaseSelect("filetags")
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const uploadFiles = async () => {
|
||||
uploadInProgress.value = true;
|
||||
|
||||
|
||||
await useFiles().uploadFiles(props.fileData, document.getElementById("fileUploadInput").files,[],true)
|
||||
|
||||
uploadInProgress.value = false;
|
||||
emit("uploadFinished")
|
||||
modal.close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal>
|
||||
<UCard :ui="{ body: { base: 'flex-1' }, ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||
Datei hochladen
|
||||
</h3>
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
icon="i-heroicons-x-mark-20-solid"
|
||||
class="-my-1"
|
||||
@click="modal.close()"
|
||||
:disabled="uploadInProgress"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<UFormGroup
|
||||
label="Datei:"
|
||||
>
|
||||
<UInput
|
||||
type="file"
|
||||
id="fileUploadInput"
|
||||
multiple
|
||||
accept="image/jpeg, image/png, image/gif, application/pdf"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Tags:"
|
||||
class="mt-3"
|
||||
>
|
||||
<USelectMenu
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
searchable-placeholder="Suchen..."
|
||||
:options="availableFiletypes"
|
||||
v-model="props.fileData.type"
|
||||
>
|
||||
<template #label>
|
||||
<span v-if="props.fileData.type">{{availableFiletypes.find(x => x.id === props.fileData.type).name}}</span>
|
||||
<span v-else>Keine Typ ausgewählt</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
@click="uploadFiles"
|
||||
:loading="uploadInProgress"
|
||||
:disabled="uploadInProgress"
|
||||
>Hochladen</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
667
components/EntityEdit.vue
Normal file
667
components/EntityEdit.vue
Normal file
@@ -0,0 +1,667 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
import MaterialComposing from "~/components/materialComposing.vue";
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
item: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const {type} = props
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push(`/${type}`)
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < 3) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
const openTab = ref(0)
|
||||
|
||||
const item = ref(JSON.parse(props.item))
|
||||
console.log(item.value)
|
||||
|
||||
const oldItem = ref(null)
|
||||
const generateOldItemData = () => {
|
||||
oldItem.value = JSON.parse(props.item)
|
||||
}
|
||||
generateOldItemData()
|
||||
|
||||
|
||||
const setupCreate = () => {
|
||||
dataType.templateColumns.forEach(datapoint => {
|
||||
if(datapoint.key.includes(".")){
|
||||
!item.value[datapoint.key.split(".")[0]] ? item.value[datapoint.key.split(".")[0]] = {} : null
|
||||
}
|
||||
|
||||
if(datapoint.inputType === "editor") {
|
||||
if(datapoint.key.includes(".")){
|
||||
item.value[datapoint.key.split(".")[0]][datapoint.key.split(".")[1]] = {}
|
||||
} else {
|
||||
item.value[datapoint.key] = {}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
setupCreate()
|
||||
|
||||
const setupQuery = () => {
|
||||
if(route.query) {
|
||||
|
||||
Object.keys(route.query).forEach(key => {
|
||||
if(["customer","contract","plant","contact"].includes(key)){
|
||||
item.value[key] = Number(route.query[key])
|
||||
} else {
|
||||
item.value[key] = route.query[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
setupQuery()
|
||||
|
||||
const loadedOptions = ref({})
|
||||
const loadOptions = async () => {
|
||||
let optionsToLoad = dataType.templateColumns.filter(i => i.selectDataType).map(i => {
|
||||
return {
|
||||
option: i.selectDataType,
|
||||
key: i.key
|
||||
}
|
||||
})
|
||||
|
||||
for await(const option of optionsToLoad) {
|
||||
if(option.option === "countrys") {
|
||||
loadedOptions.value[option.option] = (await supabase.from("countrys").select()).data
|
||||
} else if(option.option === "units") {
|
||||
loadedOptions.value[option.option] = (await supabase.from("units").select()).data
|
||||
} else {
|
||||
loadedOptions.value[option.option] = (await useSupabaseSelect(option.option))
|
||||
|
||||
if(dataType.templateColumns.find(x => x.key === option.key).selectDataTypeFilter){
|
||||
loadedOptions.value[option.option] = loadedOptions.value[option.option].filter(i => dataType.templateColumns.find(x => x.key === option.key).selectDataTypeFilter(i, item))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadOptions()
|
||||
|
||||
const contentChanged = (content, datapoint) => {
|
||||
if(datapoint.key.includes(".")){
|
||||
item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]].html = content.html
|
||||
item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]].text = content.text
|
||||
item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]].json = content.json
|
||||
} else {
|
||||
item[datapoint.key].html = content.html
|
||||
item[datapoint.key].text = content.text
|
||||
item[datapoint.key].json = content.json
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
|
||||
>
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/standardEntity/${type}`)"
|
||||
>
|
||||
{{dataType.label}}
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="item"
|
||||
:class="['text-xl','font-medium']"
|
||||
>{{item.id ? `${dataType.labelSingle} bearbeiten` : `${dataType.labelSingle} erstellen` }}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@confirmed="dataStore.updateItem(type,{...item,archived: true}, oldItem)"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black dark:text-white font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie das {{dataType.labelSingle}} {{item[dataType.templateColumns.find(i => i.title).key]}} wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
<UButton
|
||||
v-if="item.id"
|
||||
@click="dataStore.updateItem(type,item, oldItem)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else
|
||||
@click="dataStore.createNewItem(type,item)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="router.push(item.id ? `/standardEntity/${type}/show/${item.id}` : `/standardEntity/${type}`)"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<UForm
|
||||
class="p-5"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<div
|
||||
v-for="(columnName,index) in dataType.inputColumns"
|
||||
:class="['w-1/2', ... index < dataType.inputColumns.length -1 ? ['mr-5'] : []]"
|
||||
>
|
||||
<UDivider>{{columnName}}</UDivider>
|
||||
|
||||
<!--
|
||||
Die Form Group darf nur in der ersten bearbeitet werden und muss dann runterkopiert werden
|
||||
-->
|
||||
|
||||
<UFormGroup
|
||||
v-for="datapoint in dataType.templateColumns.filter(i => i.inputType && i.inputColumn === columnName)"
|
||||
:label="datapoint.label"
|
||||
>
|
||||
<template #help>
|
||||
<component
|
||||
v-if="datapoint.helpComponent"
|
||||
:is="datapoint.helpComponent"
|
||||
:item="item"
|
||||
/>
|
||||
</template>
|
||||
<InputGroup class="w-full" v-if="datapoint.key.includes('.')">
|
||||
<UInput
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-if="['text','number'].includes(datapoint.inputType)"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:type="datapoint.inputType"
|
||||
:placeholder="datapoint.inputIsNumberRange ? 'Leer lassen für automatisch generierte Nummer' : ''"
|
||||
>
|
||||
<template #trailing v-if="datapoint.inputTrailing">
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">{{datapoint.inputTrailing}}</span>
|
||||
</template>
|
||||
</UInput>
|
||||
<UToggle
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'bool'"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'select'"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:option-attribute="datapoint.selectOptionAttribute"
|
||||
:value-attribute="datapoint.selectValueAttribute || 'id'"
|
||||
:options="datapoint.selectManualOptions || loadedOptions[datapoint.selectDataType]"
|
||||
:searchable="datapoint.selectSearchAttributes"
|
||||
:search-attributes="datapoint.selectSearchAttributes"
|
||||
:multiple="datapoint.selectMultiple"
|
||||
>
|
||||
<template #empty>
|
||||
Keine Optionen verfügbar
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<UTextarea
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'textarea'"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
rows="4"
|
||||
/>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'date'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]] ? dayjs(item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]" @close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'datetime'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]] ? dayjs(item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]).format('DD.MM.YY HH:mm') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]" @close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
mode="datetime"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<!-- TODO: DISABLED FOR TIPTAP -->
|
||||
<Tiptap
|
||||
v-else-if="datapoint.inputType === 'editor'"
|
||||
@updateContent="(i) => contentChanged(i,datapoint)"
|
||||
:preloadedContent="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]].html"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)"
|
||||
@click="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]] = null"
|
||||
variant="outline"
|
||||
color="white"
|
||||
icon="i-heroicons-x-mark"
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup class="w-full" v-else>
|
||||
<UInput
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-if="['text','number'].includes(datapoint.inputType)"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:type="datapoint.inputType"
|
||||
:placeholder="datapoint.inputIsNumberRange ? 'Leer lassen für automatisch generierte Nummer' : ''"
|
||||
>
|
||||
<template #trailing v-if="datapoint.inputTrailing">
|
||||
{{datapoint.inputTrailing}}
|
||||
</template>
|
||||
</UInput>
|
||||
<UToggle
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'bool'"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'select'"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:option-attribute="datapoint.selectOptionAttribute"
|
||||
:value-attribute="datapoint.selectValueAttribute || 'id'"
|
||||
:options="datapoint.selectManualOptions || loadedOptions[datapoint.selectDataType]"
|
||||
:searchable="datapoint.selectSearchAttributes"
|
||||
:search-attributes="datapoint.selectSearchAttributes"
|
||||
:multiple="datapoint.selectMultiple"
|
||||
searchable-placeholder="Suche..."
|
||||
>
|
||||
<template #empty>
|
||||
Keine Optionen verfügbar
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<UTextarea
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'textarea'"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
rows="4"
|
||||
/>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'date'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key] ? dayjs(item[datapoint.key]).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key]" @close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'datetime'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key] ? dayjs(item[datapoint.key]).format('DD.MM.YY HH:mm') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key]"
|
||||
@close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
mode="datetime"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<Tiptap
|
||||
v-else-if="datapoint.inputType === 'editor'"
|
||||
@updateContent="(i) => contentChanged(i,datapoint)"
|
||||
:preloadedContent="item[datapoint.key].html"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)"
|
||||
@click="item[datapoint.key] = null"
|
||||
variant="outline"
|
||||
color="white"
|
||||
icon="i-heroicons-x-mark"
|
||||
/>
|
||||
</InputGroup>
|
||||
<div
|
||||
v-if="profileStore.ownTenant.ownFields"
|
||||
>
|
||||
<UDivider
|
||||
class="mt-3"
|
||||
>Eigene Felder</UDivider>
|
||||
|
||||
<UFormGroup
|
||||
v-for="field in profileStore.ownTenant.ownFields.contracts"
|
||||
:key="field.key"
|
||||
:label="field.label"
|
||||
>
|
||||
<UInput
|
||||
v-if="field.type === 'text'"
|
||||
v-model="item.ownFields[field.key]"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-else-if="field.type === 'select'"
|
||||
:options="field.options"
|
||||
v-model="item.ownFields[field.key]"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
<UFormGroup
|
||||
v-for="datapoint in dataType.templateColumns.filter(i => i.inputType && !i.inputColumn)"
|
||||
:label="datapoint.label"
|
||||
>
|
||||
<template #help>
|
||||
<component
|
||||
v-if="datapoint.helpComponent"
|
||||
:is="datapoint.helpComponent"
|
||||
:item="item"
|
||||
/>
|
||||
</template>
|
||||
<InputGroup class="w-full" v-if="datapoint.key.includes('.')">
|
||||
<UInput
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-if="['text','number'].includes(datapoint.inputType)"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:type="datapoint.inputType"
|
||||
:placeholder="datapoint.inputIsNumberRange ? 'Leer lassen für automatisch generierte Nummer' : ''"
|
||||
>
|
||||
<template #trailing v-if="datapoint.inputTrailing">
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">{{datapoint.inputTrailing}}</span>
|
||||
</template>
|
||||
</UInput>
|
||||
<UToggle
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'bool'"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'select'"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:option-attribute="datapoint.selectOptionAttribute"
|
||||
:value-attribute="datapoint.selectValueAttribute || 'id'"
|
||||
:options="datapoint.selectManualOptions || loadedOptions[datapoint.selectDataType]"
|
||||
:searchable="datapoint.selectSearchAttributes"
|
||||
:search-attributes="datapoint.selectSearchAttributes"
|
||||
:multiple="datapoint.selectMultiple"
|
||||
>
|
||||
<template #empty>
|
||||
Keine Optionen verfügbar
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<UTextarea
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'textarea'"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
rows="4"
|
||||
/>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'date'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]] ? dayjs(item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]" @close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'datetime'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]] ? dayjs(item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]).format('DD.MM.YY HH:mm') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]" @close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
mode="datetime"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<!-- TODO: DISABLED FOR TIPTAP -->
|
||||
<Tiptap
|
||||
v-else-if="datapoint.inputType === 'editor'"
|
||||
@updateContent="(i) => contentChanged(i,datapoint)"
|
||||
:preloadedContent="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]].html"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)"
|
||||
@click="item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]] = null"
|
||||
variant="outline"
|
||||
color="white"
|
||||
icon="i-heroicons-x-mark"
|
||||
/>
|
||||
</InputGroup>
|
||||
<InputGroup class="w-full" v-else>
|
||||
<UInput
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-if="['text','number'].includes(datapoint.inputType)"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:type="datapoint.inputType"
|
||||
:placeholder="datapoint.inputIsNumberRange ? 'Leer lassen für automatisch generierte Nummer' : ''"
|
||||
>
|
||||
<template #trailing v-if="datapoint.inputTrailing">
|
||||
{{datapoint.inputTrailing}}
|
||||
</template>
|
||||
</UInput>
|
||||
<UToggle
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'bool'"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
<USelectMenu
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'select'"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
:option-attribute="datapoint.selectOptionAttribute"
|
||||
:value-attribute="datapoint.selectValueAttribute || 'id'"
|
||||
:options="datapoint.selectManualOptions || loadedOptions[datapoint.selectDataType]"
|
||||
:searchable="datapoint.selectSearchAttributes"
|
||||
:search-attributes="datapoint.selectSearchAttributes"
|
||||
:multiple="datapoint.selectMultiple"
|
||||
searchable-placeholder="Suche..."
|
||||
>
|
||||
<template #empty>
|
||||
Keine Optionen verfügbar
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<UTextarea
|
||||
class="flex-auto"
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-else-if="datapoint.inputType === 'textarea'"
|
||||
v-model="item[datapoint.key]"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
rows="4"
|
||||
/>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'date'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key] ? dayjs(item[datapoint.key]).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key]" @close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }" v-else-if="datapoint.inputType === 'datetime'">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="item[datapoint.key] ? dayjs(item[datapoint.key]).format('DD.MM.YY HH:mm') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
@change="datapoint.inputChangeFunction ? datapoint.inputChangeFunction(item,loadedOptions) : null"
|
||||
v-model="item[datapoint.key]"
|
||||
@close="close"
|
||||
:disabled="datapoint.disabledFunction ? datapoint.disabledFunction(item) : false"
|
||||
mode="datetime"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
<Tiptap
|
||||
v-else-if="datapoint.inputType === 'editor'"
|
||||
@updateContent="(i) => contentChanged(i,datapoint)"
|
||||
:preloadedContent="item[datapoint.key].html"
|
||||
/>
|
||||
|
||||
|
||||
<MaterialComposing
|
||||
v-else-if="datapoint.inputType === 'materialComposing'"
|
||||
:item="item"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
v-if="['text','number','select','date','datetime','textarea'].includes(datapoint.inputType)"
|
||||
@click="item[datapoint.key] = null"
|
||||
variant="outline"
|
||||
color="white"
|
||||
icon="i-heroicons-x-mark"
|
||||
/>
|
||||
</InputGroup>
|
||||
<!-- <div
|
||||
v-if="profileStore.ownTenant.ownFields"
|
||||
>
|
||||
<UDivider
|
||||
class="mt-3"
|
||||
>Eigene Felder</UDivider>
|
||||
|
||||
<UFormGroup
|
||||
v-for="field in profileStore.ownTenant.ownFields.contracts"
|
||||
:key="field.key"
|
||||
:label="field.label"
|
||||
>
|
||||
<UInput
|
||||
v-if="field.type === 'text'"
|
||||
v-model="item.ownFields[field.key]"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-else-if="field.type === 'select'"
|
||||
:options="field.options"
|
||||
v-model="item.ownFields[field.key]"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>-->
|
||||
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
vertical-align: top;
|
||||
padding-bottom: 0.15em;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
</style>
|
||||
@@ -19,12 +19,12 @@ defineShortcuts({
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push(`/${type}/create`)
|
||||
router.push(`/standardEntity/${type}/create`)
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/${type}/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
router.push(`/standardEntity/${type}/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
@@ -46,6 +46,7 @@ defineShortcuts({
|
||||
|
||||
const router = useRouter()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
|
||||
@@ -54,15 +55,13 @@ const dataType = dataStore.dataTypes[type]
|
||||
const selectedItem = ref(0)
|
||||
|
||||
|
||||
const selectedColumns = ref(dataType.templateColumns)
|
||||
const columns = computed(() => dataType.templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
const selectedColumns = ref(dataType.templateColumns.filter(i => !i.disabledInTable))
|
||||
const columns = computed(() => dataType.templateColumns.filter((column) => !column.disabledInTable && selectedColumns.value.includes(column)))
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
const selectableFilters = ref(dataType.filters.map(i => i.name))
|
||||
const selectedFilters = ref(dataType.filters.filter(i => i.default).map(i => i.name) || [])
|
||||
console.log(selectableFilters)
|
||||
console.log(selectedFilters)
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
|
||||
@@ -71,12 +70,26 @@ const filteredRows = computed(() => {
|
||||
if(selectedFilters.value.length > 0) {
|
||||
selectedFilters.value.forEach(filterName => {
|
||||
let filter = dataType.filters.find(i => i.name === filterName)
|
||||
|
||||
tempItems = tempItems.filter(filter.filterFunction)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
if(!useRole().generalAvailableRights.value[type].showToAllUsers) {
|
||||
if(useRole().checkRight(`${type}-viewAll`)){
|
||||
console.log("Right to Show All")
|
||||
} else if(useRole().checkRight(type)){
|
||||
console.log("Only Righty to show Own")
|
||||
console.log(tempItems)
|
||||
tempItems = tempItems.filter(item => item.profiles.includes(profileStore.activeProfile.id))
|
||||
} else {
|
||||
console.log("No Right to Show")
|
||||
tempItems = []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return useSearch(searchString.value, tempItems)
|
||||
})
|
||||
@@ -99,7 +112,7 @@ const filteredRows = computed(() => {
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton v-if="useRole().checkRight(`${type}-create`)" @click="router.push(`/${type}/create`)">+ {{dataType.labelSingle}}</UButton>
|
||||
<UButton v-if="useRole().checkRight(`${type}-create`)" @click="router.push(`/standardEntity/${type}/create`)">+ {{dataType.labelSingle}}</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
@@ -112,20 +125,25 @@ const filteredRows = computed(() => {
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="dataType.templateColumns"
|
||||
:options="dataType.templateColumns.filter(i => !i.disabledInTable)"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
:color="selectedColumns.length !== dataType.templateColumns.filter(i => !i.disabledInTable).length ? 'primary' : 'white'"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<USelectMenu
|
||||
v-if="selectableFilters.length > 0"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
multiple
|
||||
v-model="selectedFilters"
|
||||
:options="selectableFilters"
|
||||
:color="selectedFilters.length > 0 ? 'primary' : 'white'"
|
||||
:ui-menu="{ width: 'min-w-max' }"
|
||||
>
|
||||
<template #label>
|
||||
Filter
|
||||
@@ -133,44 +151,52 @@ const filteredRows = computed(() => {
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/projects/show/${i.id}`) "
|
||||
@select="(i) => router.push(`/standardEntity/${type}/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: `Keine ${dataType.label} anzuzeigen` }"
|
||||
>
|
||||
<template
|
||||
v-for="column in dataType.templateColumns"
|
||||
v-for="column in dataType.templateColumns.filter(i => !i.disabledInTable)"
|
||||
v-slot:[`${column.key}-header`]="{row}">
|
||||
<span class="text-nowrap">{{column.label}}</span>
|
||||
</template>
|
||||
<template #name-data="{row}">
|
||||
<span
|
||||
v-if="row.id === filteredRows[selectedItem].id"
|
||||
class="text-primary-500 font-bold">{{row.name}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{row.name}}
|
||||
</span>
|
||||
</template>
|
||||
<template #fullName-data="{row}">
|
||||
<span
|
||||
v-if="row.id === filteredRows[selectedItem].id"
|
||||
class="text-primary-500 font-bold">{{row.fullName}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{row.fullName}}
|
||||
</span>
|
||||
</template>
|
||||
<template #licensePlate-data="{row}">
|
||||
<span
|
||||
v-if="row.id === filteredRows[selectedItem].id"
|
||||
class="text-primary-500 font-bold">{{row.licensePlate}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{row.licensePlate}}
|
||||
</span>
|
||||
</template>
|
||||
<template
|
||||
v-for="column in dataType.templateColumns.filter(i => i.key !== 'name' && i.key !== 'fullName' && i.key !== 'licensePlate' && !i.disabledInTable)"
|
||||
v-slot:[`${column.key}-data`]="{row}">
|
||||
<component v-if="column.component" :is="column.component" :row="row"></component>
|
||||
<span v-else>{{row[column.key]}}</span>
|
||||
<span v-else>{{row[column.key] ? `${row[column.key]} ${column.unit ? column.unit : ''}`: ''}}</span>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <template #name-data="{row}">
|
||||
<span class="text-primary-500 font-bold" v-if="row === filteredRows[selectedItem]">{{row.name}}</span>
|
||||
<span v-else>{{row.name}}</span>
|
||||
</template>
|
||||
<template #projecttype-data="{row}">
|
||||
{{row.projecttype ? row.projecttype.name : ""}}
|
||||
</template>
|
||||
<template #phase-data="{row}">
|
||||
{{getActivePhaseLabel(row)}}
|
||||
</template>
|
||||
<template #customer-data="{row}">
|
||||
{{row.customer ? row.customer.name : ""}}
|
||||
</template>
|
||||
<template #plant-data="{row}">
|
||||
{{row.plant ? row.plant.name : ""}}
|
||||
</template>
|
||||
<template #users-data="{row}">
|
||||
{{row.users.map(i => dataStore.getProfileById(i).fullName).join(", ")}}
|
||||
</template>-->
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
|
||||
504
components/EntityShow.vue
Normal file
504
components/EntityShow.vue
Normal file
@@ -0,0 +1,504 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
item: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const {type} = props
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push(`/standardEntity/${type}`)
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < dataType.showTabs.length - 1) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(["updateNeeded"])
|
||||
|
||||
const router = useRouter()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const files = useFiles()
|
||||
|
||||
const dataType = dataStore.dataTypes[type]
|
||||
|
||||
const availableFiles = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
if(props.item.files) {
|
||||
availableFiles.value = await files.selectSomeDocuments(props.item.files.map(i => i.id)) || []
|
||||
}
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
|
||||
const openTab = ref(0)
|
||||
|
||||
const renderedPhases = computed(() => {
|
||||
console.log(props.item.phases)
|
||||
if(type === "projects" && props.item.phases) {
|
||||
return props.item.phases.map((phase,index,array) => {
|
||||
|
||||
let isAvailable = false
|
||||
|
||||
if(phase.active) {
|
||||
isAvailable = true
|
||||
} else if(index > 0 && array[index-1].active ){
|
||||
isAvailable = true
|
||||
} else if(index > 1 && array[index-1].optional && array[index-2].active){
|
||||
isAvailable = true
|
||||
} else if(array.findIndex(i => i.active) > index) {
|
||||
isAvailable = true
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
...phase,
|
||||
label: phase.optional ? `${phase.label}(optional)`: phase.label,
|
||||
disabled: !isAvailable,
|
||||
defaultOpen: phase.active ? true : false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
const changeActivePhase = async (key) => {
|
||||
let item = await useSupabaseSelectSingle("projects",props.item.id,'*')
|
||||
|
||||
let phaseLabel = ""
|
||||
|
||||
item.phases = item.phases.map(p => {
|
||||
if(p.active) p.active = false
|
||||
|
||||
if(p.key === key) {
|
||||
p.active = true
|
||||
p.activated_at = dayjs().format()
|
||||
p.activated_by = profileStore.activeProfile.id
|
||||
phaseLabel = p.label
|
||||
}
|
||||
|
||||
return p
|
||||
})
|
||||
|
||||
const {error:updateError} = await supabase.from("projects").update({phases: item.phases}).eq("id",item.id)
|
||||
|
||||
console.log(updateError)
|
||||
|
||||
|
||||
const {error} = await supabase.from("historyitems").insert({
|
||||
createdBy: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant,
|
||||
text: `Aktive Phase zu "${phaseLabel}" gewechselt`,
|
||||
project: item.id
|
||||
})
|
||||
|
||||
emit("updateNeeded")
|
||||
|
||||
}
|
||||
|
||||
const invoiceDeliveryNotes = () => {
|
||||
router.push(`/createDocument/edit?type=invoices&linkedDocuments=[${props.item.createddocuments.filter(i => i.type === "deliveryNotes").map(i => i.id)}]`)
|
||||
}
|
||||
|
||||
const getAvailableQueryStringData = () => {
|
||||
let returnString =""
|
||||
|
||||
if(props.item.customer) {
|
||||
returnString += `&customer=${props.item.customer.id}`
|
||||
} else if(type === "customers") {
|
||||
returnString += `&customer=${props.item.id}`
|
||||
}
|
||||
|
||||
if(props.item.project) {
|
||||
returnString += `&project=${props.item.project.id}`
|
||||
} else if(type === "projects") {
|
||||
returnString += `&project=${props.item.id}`
|
||||
}
|
||||
|
||||
return returnString
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
|
||||
>
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/standardEntity/${type}`)"
|
||||
>
|
||||
{{dataType.label}}
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="item"
|
||||
:class="['text-xl','font-medium']"
|
||||
>{{item ? `${dataType.labelSingle}${props.item[dataType.templateColumns.find(i => i.title).key] ? ': ' + props.item[dataType.templateColumns.find(i => i.title).key] : ''}`: '' }}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="router.push(`/standardEntity/${type}/edit/${item.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UTabs
|
||||
:items="dataType.showTabs"
|
||||
v-if="props.item.id"
|
||||
class="p-5"
|
||||
v-model="openTab"
|
||||
>
|
||||
<template #item="{item:tab}">
|
||||
<div class="scroll">
|
||||
<div v-if="tab.label === 'Informationen'" class="flex flex-row mt-5">
|
||||
<UCard class="w-1/2 mr-5">
|
||||
<UAlert
|
||||
v-if="item.archived"
|
||||
color="rose"
|
||||
variant="outline"
|
||||
:title="`${dataType.labelSingle} archiviert`"
|
||||
icon="i-heroicons-archive-box"
|
||||
class="mb-5"
|
||||
/>
|
||||
<div class="text-wrap">
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="datapoint in dataType.templateColumns"
|
||||
>
|
||||
<td>{{datapoint.label}}:</td>
|
||||
<td>
|
||||
<component v-if="datapoint.component" :is="datapoint.component" :row="props.item" :in-show="true"></component>
|
||||
<div v-else>
|
||||
<span v-if="datapoint.key.includes('.')">{{props.item[datapoint.key.split('.')[0]][datapoint.key.split('.')[1]]}}{{datapoint.unit}}</span>
|
||||
<span v-else>{{props.item[datapoint.key]}} {{datapoint.unit}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</UCard>
|
||||
<UCard class="w-1/2">
|
||||
<HistoryDisplay
|
||||
:type="type.substring(0,type.length-1)"
|
||||
v-if="props.item.id"
|
||||
:element-id="props.item.id"
|
||||
render-headline
|
||||
/>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Dateien'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<DocumentUpload
|
||||
:type="type.substring(0,type.length-1)"
|
||||
:element-id="item.id"
|
||||
@uploadFinished="emit('updateNeeded')"
|
||||
/>
|
||||
</Toolbar>
|
||||
|
||||
<DocumentList
|
||||
:key="props.item.files.length"
|
||||
:documents="availableFiles"
|
||||
v-if="availableFiles.length > 0"
|
||||
/>
|
||||
<UAlert
|
||||
v-else
|
||||
icon="i-heroicons-x-mark"
|
||||
title="Keine Dateien verfügbar"
|
||||
/>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Projekte'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/standardEntity/projects/create?${type.substring(0,type.length-1)}=${props.item.id}`)"
|
||||
>
|
||||
+ Projekt
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="props.item.projects"
|
||||
@select="(row) => router.push(`/standardEntity/projects/show/${row.id}`)"
|
||||
:columns="[{label: 'Name', key: 'name'},{label: 'Phase', key: 'phase'}]"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Projekte' }"
|
||||
|
||||
>
|
||||
<template #phase-data="{row}">
|
||||
{{row.phases ? row.phases.find(i => i.active).label : ""}}
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Ansprechpartner'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/standardEntity/contacts/create?${type.substring(0,type.length-1)}=${props.item.id}`)"
|
||||
>
|
||||
+ Ansprechpartner
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="props.item.contacts"
|
||||
@select="(row) => router.push(`/standardEntity/contacts/show/${row.id}`)"
|
||||
:columns="[{label: 'Name', key: 'fullName'},{label: 'Rolle', key: 'role'}]"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Ansprechpartner' }"
|
||||
|
||||
>
|
||||
|
||||
</UTable>
|
||||
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Objekte'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/standardEntity/plants/create?${type.substring(0,type.length-1)}=${props.item.id}`)"
|
||||
>
|
||||
+ Objekt
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="type === 'customers'"
|
||||
@click="router.push(`/standardEntity/plants/create?${type.substring(0,type.length-1)}=${props.item.id}&name=${encodeURIComponent(`${props.item.infoData.street}, ${props.item.infoData.zip} ${props.item.infoData.city}`)}`)"
|
||||
>
|
||||
+ Kundenadresse als Objekt
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="props.item.plants"
|
||||
@select="(row) => router.push(`/standardEntity/plants/show/${row.id}`)"
|
||||
:columns="[{label: 'Name', key: 'name'}]"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Objekte' }"
|
||||
|
||||
>
|
||||
|
||||
</UTable>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Aufgaben'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/standardEntity/tasks/create?${type.substring(0,type.length-1)}=${props.item.id}`)"
|
||||
>
|
||||
+ Aufgabe
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="props.item.tasks"
|
||||
@select="(row) => router.push(`/standardEntity/tasks/show/${row.id}`)"
|
||||
:columns="[{label: 'Name', key: 'name'}]"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Aufgaben' }"
|
||||
/>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Verträge'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/standardEntity/contracts/create?${type.substring(0,type.length-1)}=${props.item.id}`)"
|
||||
>
|
||||
+ Vertrag
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="props.item.contracts"
|
||||
@select="(row) => router.push(`/standardEntity/contracts/show/${row.id}`)"
|
||||
:columns="[{label: 'Name', key: 'name'},{label: 'Aktiv', key: 'active'}]"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Verträge' }"
|
||||
|
||||
></UTable>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Überprüfungen'">
|
||||
<UCard class="mt-5">
|
||||
<UTable
|
||||
:rows="props.item.checks"
|
||||
:columns="[{key:'name',label: 'Name'},{key:'rhythm',label: 'Rhythmus'},{key:'description',label: 'Beschreibung'}]"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/checks/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Überprüfungen anzuzeigen' }"
|
||||
>
|
||||
<template #rhythm-data="{row}">
|
||||
{{row.distance}}
|
||||
<span v-if="row.distanceUnit === 'dayjs'">Tage</span>
|
||||
<span v-if="row.distanceUnit === 'years'">Jahre</span>
|
||||
</template>
|
||||
</UTable>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Phasen'">
|
||||
<UCard class="mt-5">
|
||||
<UAccordion
|
||||
:items="renderedPhases"
|
||||
>
|
||||
<template #default="{item,index,open}">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
:color="item.active ? 'primary' : 'white'"
|
||||
class="mb-1"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #leading>
|
||||
<div class="w-6 h-6 flex items-center justify-center -my-1">
|
||||
<UIcon :name="item.icon" class="w-4 h-4 " />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<span class="truncate"> {{item.label}}</span>
|
||||
|
||||
<template #trailing>
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="w-5 h-5 ms-auto transform transition-transform duration-200"
|
||||
:class="[open && 'rotate-90']"
|
||||
/>
|
||||
</template>
|
||||
|
||||
</UButton>
|
||||
</template>
|
||||
<template #item="{item, index}">
|
||||
<UCard class="mx-5">
|
||||
<template #header>
|
||||
<span class="dark:text-white text-black">{{item.label}}</span>
|
||||
</template>
|
||||
<InputGroup>
|
||||
<!-- TODO: Reactive Change Phase -->
|
||||
<UButton
|
||||
v-if="!item.activated_at && index !== 0 "
|
||||
@click="changeActivePhase(item.key)"
|
||||
>
|
||||
Phase aktivieren
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="item.active"
|
||||
v-for="button in item.quickactions"
|
||||
@click="router.push(`${button.link}&customer=${itemInfo.customer.id}&project=${itemInfo.id}`)"
|
||||
>
|
||||
{{button.label}}
|
||||
</UButton>
|
||||
</InputGroup>
|
||||
|
||||
<div>
|
||||
<p v-if="item.activated_at" class="dark:text-white text-black">Aktiviert am: {{dayjs(item.activated_at).format("DD.MM.YY HH:mm")}} Uhr</p>
|
||||
<p v-if="item.activated_by" class="dark:text-white text-black">Aktiviert durch: {{profileStore.getProfileById(item.activated_by).fullName}}</p>
|
||||
<p v-if="item.description" class="dark:text-white text-black">Beschreibung: {{item.description}}</p>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
|
||||
</template>
|
||||
</UAccordion>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="tab.label === 'Ausgangsbelege'">
|
||||
<UCard class="mt-5">
|
||||
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/createDocument/edit/?type=quotes${getAvailableQueryStringData()}`)"
|
||||
>
|
||||
+ Angebot
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="router.push(`/createDocument/edit/?type=confirmationOrders${getAvailableQueryStringData()}`)"
|
||||
>
|
||||
+ Auftragsbestätigung
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="router.push(`/createDocument/edit/?type=deliveryNotes${getAvailableQueryStringData()}`)"
|
||||
>
|
||||
+ Lieferschein
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="router.push(`/createDocument/edit/?type=advanceInvoices${getAvailableQueryStringData()}`)"
|
||||
>
|
||||
+ Abschlagsrechnung
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="router.push(`/createDocument/edit/?type=invoices${getAvailableQueryStringData()}`)"
|
||||
>
|
||||
+ Rechnung
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="invoiceDeliveryNotes"
|
||||
v-if="type === 'projects'"
|
||||
>
|
||||
Lieferscheine abrechnen
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
|
||||
<UTable
|
||||
:rows="props.item.createddocuments"
|
||||
:columns="[{key:'state',label: 'Status'},{key:'type',label: 'Typ'},{key:'documentNumber',label: 'Nummer'},{key:'documentDate',label: 'Datum'}]"
|
||||
@select="(i) => router.push(i.state === 'Entwurf' ? `/createDocument/edit/${i.id}`:`/createDocument/show/${i.id}`)"
|
||||
>
|
||||
<template #documentDate-data="{row}">
|
||||
{{dayjs(row.documentDate).format("DD.MM.YY HH:mm")}}
|
||||
</template>
|
||||
<template #type-data="{row}">
|
||||
{{dataStore.documentTypesForCreation[row.type].labelSingle}}
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
</UTabs>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
vertical-align: top;
|
||||
padding-bottom: 0.15em;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
height: 80vh;
|
||||
overflow-y: scroll;
|
||||
padding: 0.3em;
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@ const { metaSymbol } = useShortcuts()
|
||||
|
||||
const shortcuts = ref(false)
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const query = ref('')
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
@@ -101,12 +102,12 @@ const filteredCategories = computed(() => {
|
||||
|
||||
const contactRequestData = ref({
|
||||
source: "helpSlideover",
|
||||
tenant: dataStore.currentTenant,
|
||||
tenant: profileStore.currentTenant,
|
||||
message: "",
|
||||
title: "",
|
||||
contactName: dataStore.activeProfile.fullName,
|
||||
contactTel: dataStore.activeProfile.phoneMobile || dataStore.activeProfile.phoneHome,
|
||||
contactMail: dataStore.activeProfile.email,
|
||||
contactName: profileStore.activeProfile.fullName,
|
||||
contactTel: profileStore.activeProfile.phoneMobile || profileStore.activeProfile.phoneHome,
|
||||
contactMail: profileStore.activeProfile.email,
|
||||
contactType: "Hilfe",
|
||||
currentPath: router.currentRoute
|
||||
})
|
||||
@@ -125,12 +126,12 @@ const addContactRequest = async () => {
|
||||
const resetContactRequest = () => {
|
||||
contactRequestData.value = {
|
||||
source: "helpSlideover",
|
||||
tenant: dataStore.currentTenant,
|
||||
tenant: profileStore.currentTenant,
|
||||
message: "",
|
||||
title: "",
|
||||
contactName: dataStore.activeProfile.fullName,
|
||||
contactTel: dataStore.activeProfile.phoneMobile || dataStore.activeProfile.phoneHome,
|
||||
contactMail: dataStore.activeProfile.email,
|
||||
contactName: profileStore.activeProfile.fullName,
|
||||
contactTel: profileStore.activeProfile.phoneMobile || profileStore.activeProfile.phoneHome,
|
||||
contactMail: profileStore.activeProfile.email,
|
||||
contactType: "Hilfe"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
import dayjs from "dayjs"
|
||||
const props = defineProps({
|
||||
type: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
elementId: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
renderHeadline: {
|
||||
@@ -14,7 +12,7 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
const { metaSymbol } = useShortcuts()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const user = useSupabaseUser()
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
@@ -26,43 +24,12 @@ const items = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
|
||||
items.value = (await supabase.from("historyitems").select().eq(type,elementId).order("created_at",{ascending: true})).data || []
|
||||
if(type && elementId){
|
||||
items.value = (await supabase.from("historyitems").select().eq(type,elementId).order("created_at",{ascending: true})).data || []
|
||||
} else {
|
||||
items.value = (await supabase.from("historyitems").select().order("created_at",{ascending: true})).data || []
|
||||
|
||||
/*if(type === "customer") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("customer",elementId)).data || []
|
||||
} else if(type === "vendor") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("vendor",elementId)).data || []
|
||||
} else if(type === "project") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("project",elementId)).data || []
|
||||
} else if(type === "plant") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("plant",elementId)).data || []
|
||||
} else if(type === "incomingInvoice") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("incomingInvoice",elementId)).data || []
|
||||
} else if(type === "document") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("document",elementId)).data || []
|
||||
} else if(type === "contact") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("contact",elementId)).data || []
|
||||
} else if(type === "contract") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("contract",elementId)).data || []
|
||||
} else if(type === "inventoryitem") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("inventoryitem",elementId)).data || []
|
||||
} else if(type === "product") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("product",elementId)).data || []
|
||||
} else if(type === "profile") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("profile",elementId)).data || []
|
||||
} else if(type === "absencerequest") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("absenceRequest",elementId)).data || []
|
||||
} else if(type === "event") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("event",elementId)).data || []
|
||||
} else if(type === "task") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("task",elementId)).data || []
|
||||
} else if(type === "vehicle") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("vehicle",elementId)).data || []
|
||||
} else if(type === "space") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("space",elementId)).data || []
|
||||
} else if(type === "trackingtrip") {
|
||||
items.value = (await supabase.from("historyitems").select().eq("trackingtrip",elementId)).data || []
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
setup()
|
||||
@@ -79,13 +46,13 @@ const addHistoryItemData = ref({
|
||||
|
||||
const addHistoryItem = async () => {
|
||||
console.log(addHistoryItemData.value)
|
||||
addHistoryItemData.value.createdBy = dataStore.activeProfile.id
|
||||
addHistoryItemData.value.createdBy = profileStore.activeProfile.id
|
||||
|
||||
addHistoryItemData.value[type] = elementId
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("historyitems")
|
||||
.insert([{...addHistoryItemData.value, tenant: dataStore.currentTenant}])
|
||||
.insert([{...addHistoryItemData.value, tenant: profileStore.currentTenant}])
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
@@ -100,9 +67,9 @@ const addHistoryItem = async () => {
|
||||
let rawUsername = i[1]
|
||||
|
||||
return {
|
||||
tenant: dataStore.currentTenant,
|
||||
tenant: profileStore.currentTenant,
|
||||
profile: profiles.find(x => x.username === rawUsername).id,
|
||||
initiatingProfile: dataStore.activeProfile.id,
|
||||
initiatingProfile: profileStore.activeProfile.id,
|
||||
title: "Sie wurden im Logbuch erwähnt",
|
||||
link: `/${type}s/show/${elementId}`,
|
||||
message: addHistoryItemData.value.text
|
||||
@@ -162,7 +129,7 @@ const renderText = (text) => {
|
||||
</UCard>
|
||||
</UModal>
|
||||
<Toolbar
|
||||
v-if="!renderHeadline"
|
||||
v-if="!renderHeadline && elementId && type"
|
||||
>
|
||||
<UButton
|
||||
@click="showAddHistoryItemModal = true"
|
||||
@@ -170,7 +137,7 @@ const renderText = (text) => {
|
||||
+ Eintrag
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<div v-else>
|
||||
<div v-else-if="renderHeadline && elementId && type">
|
||||
<div :class="`flex justify-between`">
|
||||
<p class=""><span class="text-xl">Logbuch</span> <UBadge variant="outline">{{items.length}}</UBadge></p>
|
||||
<UButton
|
||||
@@ -198,11 +165,11 @@ const renderText = (text) => {
|
||||
:src="colorMode.value === 'light' ? '/Logo.png' : '/Logo_Dark.png' "
|
||||
/>
|
||||
<UAvatar
|
||||
:alt="dataStore.getProfileById(item.createdBy).fullName"
|
||||
:alt="profileStore.getProfileById(item.createdBy).fullName"
|
||||
v-else
|
||||
/>
|
||||
<div>
|
||||
<h3 v-if="item.createdBy">{{dataStore.getProfileById(item.createdBy) ? dataStore.getProfileById(item.createdBy).fullName : ""}}</h3>
|
||||
<h3 v-if="item.createdBy">{{profileStore.getProfileById(item.createdBy) ? profileStore.getProfileById(item.createdBy).fullName : ""}}</h3>
|
||||
<h3 v-else>FEDEO Bot</h3>
|
||||
<span v-html="renderText(item.text)"/><br>
|
||||
<span class="text-gray-500">{{dayjs(item.created_at).format("DD.MM.YY HH:mm")}}</span>
|
||||
|
||||
@@ -1,43 +1,57 @@
|
||||
<script setup>
|
||||
const dataStore = useDataStore()
|
||||
import {useRole} from "~/composables/useRole.js";
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
|
||||
const role = useRole()
|
||||
const links = computed(() => {
|
||||
return [
|
||||
... profileStore.currentTenant === 21 ? [{
|
||||
label: "Lieferschein Formular",
|
||||
to: "https://loar.ma-agrarservice.de/erfassung",
|
||||
icon: "i-heroicons-rectangle-stack",
|
||||
target: "_blank",
|
||||
}] : [],
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: "Dashboard",
|
||||
to: "/",
|
||||
icon: "i-heroicons-home"
|
||||
}, {
|
||||
id: 'historyitems',
|
||||
label: "Logbuch",
|
||||
to: "/historyitems",
|
||||
icon: "i-heroicons-book-open"
|
||||
},
|
||||
{
|
||||
label: "Organisation",
|
||||
icon: "i-heroicons-rectangle-stack",
|
||||
defaultOpen: false,
|
||||
children: [
|
||||
{
|
||||
... role.checkRight("tasks") ? [{
|
||||
label: "Aufgaben",
|
||||
to: "/tasks",
|
||||
to: "/standardEntity/tasks",
|
||||
icon: "i-heroicons-rectangle-stack"
|
||||
},
|
||||
... dataStore.ownTenant.features.planningBoard ? [{
|
||||
}] : [],
|
||||
... profileStore.ownTenant.features.planningBoard ? [{
|
||||
label: "Plantafel",
|
||||
to: "/calendar/timeline",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.calendar ? [{
|
||||
... profileStore.ownTenant.features.calendar ? [{
|
||||
label: "Kalender",
|
||||
to: "/calendar/grid",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.calendar ? [{
|
||||
... profileStore.ownTenant.features.calendar ? [{
|
||||
label: "Termine",
|
||||
to: "/events",
|
||||
to: "/standardEntity/events",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
{
|
||||
label: "Dokumente",
|
||||
to: "/documents",
|
||||
label: "Dateien",
|
||||
to: "/files",
|
||||
icon: "i-heroicons-document"
|
||||
},
|
||||
]
|
||||
@@ -62,31 +76,26 @@ const links = computed(() => {
|
||||
}
|
||||
]
|
||||
},
|
||||
/*{
|
||||
label: "E-Mail",
|
||||
to: "/email",
|
||||
icon: "i-heroicons-envelope"
|
||||
},*/
|
||||
... dataStore.ownTenant.features.contacts ? [{
|
||||
... (role.checkRight("customers") || role.checkRight("vendors") || role.checkRight("contacts")) ? [{
|
||||
label: "Kontakte",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-user-group",
|
||||
children: [
|
||||
{
|
||||
... role.checkRight("customers") ? [{
|
||||
label: "Kunden",
|
||||
to: "/customers",
|
||||
to: "/standardEntity/customers",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
{
|
||||
}] : [],
|
||||
... role.checkRight("vendors") ? [{
|
||||
label: "Lieferanten",
|
||||
to: "/vendors",
|
||||
to: "/standardEntity/vendors",
|
||||
icon: "i-heroicons-truck"
|
||||
},
|
||||
{
|
||||
}] : [],
|
||||
... role.checkRight("contacts") ? [{
|
||||
label: "Ansprechpartner",
|
||||
to: "/contacts",
|
||||
to: "/standardEntity/contacts",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
}] : [],
|
||||
]
|
||||
},] : [],
|
||||
{
|
||||
@@ -94,24 +103,29 @@ const links = computed(() => {
|
||||
defaultOpen:false,
|
||||
icon: "i-heroicons-user-group",
|
||||
children: [
|
||||
... dataStore.ownTenant.features.timeTracking ? [{
|
||||
... profileStore.ownTenant.features.timeTracking ? [{
|
||||
label: "Zeiterfassung",
|
||||
to: "/employees/timetracking",
|
||||
icon: "i-heroicons-clock"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.workingTimeTracking ? [{
|
||||
... profileStore.ownTenant.features.workingTimeTracking ? [{
|
||||
label: "Anwesenheiten",
|
||||
to: "/workingtimes",
|
||||
icon: "i-heroicons-clock"
|
||||
}] : [],
|
||||
{
|
||||
... role.checkRight("absencerequests") ? [{
|
||||
label: "Abwesenheiten",
|
||||
to: "/absenceRequests",
|
||||
to: "/standardEntity/absencerequests",
|
||||
icon: "i-heroicons-document-text"
|
||||
}] : [],
|
||||
{
|
||||
label: "Fahrten",
|
||||
to: "/trackingTrips",
|
||||
icon: "i-heroicons-map"
|
||||
},
|
||||
]
|
||||
},
|
||||
... dataStore.ownTenant.features.accounting ? [{
|
||||
... profileStore.ownTenant.features.accounting ? [{
|
||||
label: "Buchhaltung",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-chart-bar-square",
|
||||
@@ -136,7 +150,7 @@ const links = computed(() => {
|
||||
},
|
||||
]
|
||||
},] : [],
|
||||
... dataStore.ownTenant.features.inventory ? [{
|
||||
... role.checkRight("inventory") ? [{
|
||||
label: "Lager",
|
||||
icon: "i-heroicons-puzzle-piece",
|
||||
defaultOpen: false,
|
||||
@@ -145,37 +159,16 @@ const links = computed(() => {
|
||||
label: "Vorgänge",
|
||||
to: "/inventory",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
},{
|
||||
},/*{
|
||||
label: "Bestände",
|
||||
to: "/inventory/stocks",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
},
|
||||
{
|
||||
},*/
|
||||
... role.checkRight("spaces") ? [{
|
||||
label: "Lagerplätze",
|
||||
to: "/spaces",
|
||||
to: "/standardEntity/spaces",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
},
|
||||
{
|
||||
label: "Inventar",
|
||||
to: "/inventoryitems",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},
|
||||
]
|
||||
},] : [],
|
||||
... dataStore.ownTenant.features.vehicles ? [{
|
||||
label: "Fuhrpark",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-truck",
|
||||
children: [
|
||||
{
|
||||
label: "Fahrzeuge",
|
||||
to: "/vehicles",
|
||||
icon: "i-heroicons-truck"
|
||||
},{
|
||||
label: "Fahrten",
|
||||
to: "/trackingTrips",
|
||||
icon: "i-heroicons-map"
|
||||
},
|
||||
}] : [],
|
||||
]
|
||||
},] : [],
|
||||
{
|
||||
@@ -183,58 +176,61 @@ const links = computed(() => {
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-clipboard-document",
|
||||
children: [
|
||||
{
|
||||
... role.checkRight("products") ? [{
|
||||
label: "Artikel",
|
||||
to: "/products",
|
||||
to: "/standardEntity/products",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},{
|
||||
}] : [],
|
||||
... role.checkRight("productcategories") ? [{
|
||||
label: "Artikelkategorien",
|
||||
to: "/productcategories",
|
||||
to: "/standardEntity/productcategories",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},{
|
||||
}] : [],
|
||||
... role.checkRight("services") ? [{
|
||||
label: "Leistungen",
|
||||
to: "/services",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},{
|
||||
to: "/standardEntity/services",
|
||||
icon: "i-heroicons-wrench-screwdriver"
|
||||
}] : [],
|
||||
... role.checkRight("servicecategories") ? [{
|
||||
label: "Leistungskategorien",
|
||||
to: "/servicecategories",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
to: "/standardEntity/servicecategories",
|
||||
icon: "i-heroicons-wrench-screwdriver"
|
||||
}] : [],
|
||||
{
|
||||
label: "Mitarbeiter",
|
||||
to: "/profiles",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
... role.checkRight("vehicles") ? [{
|
||||
label: "Fahrzeuge",
|
||||
to: "/standardEntity/vehicles",
|
||||
icon: "i-heroicons-truck"
|
||||
}] : [],
|
||||
... role.checkRight("inventoryitems") ? [{
|
||||
label: "Inventar",
|
||||
to: "/standardEntity/inventoryitems",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
}] : [],
|
||||
]
|
||||
},
|
||||
/*... dataStore.ownTenant.features.projects ? [{
|
||||
label: "Projekte",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-clipboard-document-check",
|
||||
children: [
|
||||
...dataStore.ownTenant.projecttypes.map(i => {
|
||||
return {
|
||||
label: i.label,
|
||||
to: `/projects?type=${i.label}`,
|
||||
icon: i.icon
|
||||
}
|
||||
})
|
||||
|
||||
]
|
||||
},] : [],*/
|
||||
{
|
||||
... role.checkRight("checks") ? [{
|
||||
label: "Überprüfungen",
|
||||
to: "/checks",
|
||||
to: "/standardEntity/checks",
|
||||
icon: "i-heroicons-magnifying-glass"
|
||||
},
|
||||
... dataStore.ownTenant.features.projects ? [{
|
||||
},] : [],
|
||||
... role.checkRight("projects") ? [{
|
||||
label: "Projekte",
|
||||
to: "/projects",
|
||||
to: "/standardEntity/projects",
|
||||
icon: "i-heroicons-clipboard-document-check"
|
||||
},] : [],
|
||||
... dataStore.ownTenant.features.contracts ? [{
|
||||
... role.checkRight("contracts") ? [{
|
||||
label: "Verträge",
|
||||
to: "/contracts",
|
||||
to: "/standardEntity/contracts",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.objects ? [{
|
||||
... role.checkRight("plants") ? [{
|
||||
label: "Objekte",
|
||||
to: "/plants",
|
||||
to: "/standardEntity/plants",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
},] : [],
|
||||
|
||||
@@ -244,21 +240,26 @@ const links = computed(() => {
|
||||
icon: "i-heroicons-cog-8-tooth",
|
||||
children: [
|
||||
{
|
||||
label: "Abrechnung",
|
||||
to: "https://billing.stripe.com/p/login/cN29Eb32Vdx0gOk288",
|
||||
icon: "i-heroicons-document-currency-euro",
|
||||
target: "_blank"
|
||||
},{
|
||||
label: "Nummernkreise",
|
||||
to: "/settings/numberRanges",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
label: "Mitarbeiter",
|
||||
to: "/profiles",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
label: "Rollen",
|
||||
to: "/roles",
|
||||
icon: "i-heroicons-key"
|
||||
},{
|
||||
label: "E-Mail Konten",
|
||||
to: "/settings/emailAccounts",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
icon: "i-heroicons-envelope"
|
||||
},{
|
||||
label: "Bankkonten",
|
||||
to: "/settings/banking",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
icon: "i-heroicons-currency-euro"
|
||||
},{
|
||||
label: "Text Vorlagen",
|
||||
to: "/settings/texttemplates",
|
||||
@@ -270,68 +271,20 @@ const links = computed(() => {
|
||||
},*/{
|
||||
label: "Firmeneinstellungen",
|
||||
to: "/settings/tenant",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
icon: "i-heroicons-building-office"
|
||||
},{
|
||||
label: "Projekttypen",
|
||||
to: "/projecttypes",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
}/*,{
|
||||
label: "Integrationen",
|
||||
to: "/settings/integrations",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
}*/,{
|
||||
label: "Labels",
|
||||
to: "/settings/labels/",
|
||||
icon: "i-heroicons-bars-3-solid"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
/*const setOpen = (index,open) => {
|
||||
console.log(!open)
|
||||
}*/
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!--
|
||||
<UAccordion :items="links" :ui="{ wrapper: 'flex flex-col w-full' }">
|
||||
<template #default="{ item, index, open }">
|
||||
<UButton color="gray" variant="ghost" class="border-b border-gray-200 dark:border-gray-700" :ui="{ rounded: 'rounded-none', padding: { sm: 'p-3' } }">
|
||||
<template #leading>
|
||||
<!– <div class="w-6 h-6 rounded-full bg-primary-500 dark:bg-primary-400 flex items-center justify-center -my-1">
|
||||
<UIcon :name="item.icon" class="w-4 h-4 text-white dark:text-gray-900" />
|
||||
</div>–>
|
||||
<UIcon :name="item.icon" class="w-4 h-4 dark:text-white text-gray-900" />
|
||||
</template>
|
||||
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<template #trailing>
|
||||
<UIcon
|
||||
name="i-heroicons-chevron-right-20-solid"
|
||||
class="w-5 h-5 ms-auto transform transition-transform duration-200"
|
||||
:class="[open && 'rotate-90']"
|
||||
/>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.children" class="flex flex-col">
|
||||
<UButton
|
||||
v-for="child in item.children"
|
||||
variant="ghost"
|
||||
color="gray"
|
||||
>
|
||||
{{child.label}}
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UAccordion>
|
||||
-->
|
||||
<div
|
||||
v-for="item in links"
|
||||
>
|
||||
@@ -365,6 +318,7 @@ const links = computed(() => {
|
||||
v-for="child in item.children"
|
||||
class="ml-4"
|
||||
:to="child.to"
|
||||
:target="child.target"
|
||||
>
|
||||
{{child.label}}
|
||||
</UButton>
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
<script setup>
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
//const tenants = ref(dataStore.getOwnProfile ? dataStore.getOwnProfile.tenants : [])
|
||||
//const tenant = ref(dataStore.currentTenant)
|
||||
|
||||
const selectedProfile = ref(dataStore.activeProfile.id)
|
||||
const selectedProfile = ref(profileStore.activeProfile.id)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
:options="dataStore.ownProfiles"
|
||||
:options="profileStore.ownProfiles"
|
||||
value-attribute="id"
|
||||
class="w-40"
|
||||
@change="dataStore.changeProfile(selectedProfile)"
|
||||
@change="profileStore.changeProfile(selectedProfile)"
|
||||
v-model="selectedProfile"
|
||||
>
|
||||
<UButton color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" class="w-full">
|
||||
<UAvatar :alt="dataStore.tenants.find(i => dataStore.getProfileById(selectedProfile).tenant === i.id).name" size="md" />
|
||||
<UAvatar :alt="profileStore.tenants.find(i => profileStore.profiles.find(i => i.id === selectedProfile).tenant === i.id).name" size="md" />
|
||||
|
||||
<span class="truncate text-gray-900 dark:text-white font-semibold">{{dataStore.tenants.find(i => dataStore.getProfileById(selectedProfile).tenant === i.id).name}}</span>
|
||||
<span class="truncate text-gray-900 dark:text-white font-semibold">{{profileStore.tenants.find(i => profileStore.profiles.find(i => i.id === selectedProfile).tenant === i.id).name}}</span>
|
||||
</UButton>
|
||||
|
||||
<template #option="{option}">
|
||||
{{dataStore.tenants.find(i => i.id === option.tenant).name}}
|
||||
{{profileStore.tenants.find(i => i.id === option.tenant).name}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ const dataStore = useDataStore()
|
||||
<template>
|
||||
<div class="w-1/2 mx-auto">
|
||||
<p class="text-2xl mb-5 text-center">Bitte wähle dein gewünschtes Profil</p>
|
||||
<div v-for="profile in dataStore.ownProfiles" class="flex justify-between my-3">
|
||||
{{dataStore.tenants.find(i => i.id === profile.tenant).name}}
|
||||
<div v-for="profile in profileStore.ownProfiles" class="flex justify-between my-3">
|
||||
{{profileStore.tenants.find(i => i.id === profile.tenant).name}}
|
||||
<UButton
|
||||
variant="outline"
|
||||
@click="dataStore.changeProfile(profile.id)"
|
||||
@click="profileStore.changeProfile(profile.id)"
|
||||
>Auswählen</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
<script setup>
|
||||
const { isHelpSlideoverOpen } = useDashboard()
|
||||
const { isDashboardSearchModalOpen } = useUIState()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
const user = useSupabaseUser()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -15,13 +16,13 @@ const items = computed(() => [
|
||||
}], [{
|
||||
label: 'Mein Profil',
|
||||
icon: 'i-heroicons-user',
|
||||
to: `/profiles/show/${dataStore.activeProfile.id}`
|
||||
to: `/profiles/show/${profileStore.activeProfile.id}`
|
||||
},{
|
||||
label: 'Abmelden',
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle',
|
||||
click: async () => {
|
||||
await supabase.auth.signOut()
|
||||
await dataStore.clearStore()
|
||||
//await dataStore.clearStore()
|
||||
await router.push('/login')
|
||||
|
||||
}
|
||||
@@ -32,9 +33,9 @@ const items = computed(() => [
|
||||
<template>
|
||||
<UDropdown mode="hover" :items="items" :ui="{ width: 'w-full', item: { disabled: 'cursor-text select-text' } }" :popper="{ strategy: 'absolute', placement: 'top' }" class="w-full">
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" class="w-full" :label="dataStore.activeProfile.fullName" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<UButton color="gray" variant="ghost" class="w-full" :label="profileStore.activeProfile.fullName" :class="[open && 'bg-gray-50 dark:bg-gray-800']">
|
||||
<template #leading>
|
||||
<UAvatar :alt="dataStore.activeProfile ? dataStore.activeProfile.fullName : ''" size="xs" />
|
||||
<UAvatar :alt="profileStore.activeProfile ? profileStore.activeProfile.fullName : ''" size="xs" />
|
||||
</template>
|
||||
|
||||
<template #trailing>
|
||||
@@ -49,7 +50,7 @@ const items = computed(() => [
|
||||
Angemeldet als
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{dataStore.activeProfile.email}}
|
||||
{{profileStore.activeProfile.email}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
14
components/columnRenderings/active.vue
Normal file
14
components/columnRenderings/active.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.active" class="text-primary">Ja</span>
|
||||
<span v-else class="text-rose-600">Nein</span>
|
||||
</template>
|
||||
17
components/columnRenderings/address.vue
Normal file
17
components/columnRenderings/address.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.infoData.street">{{props.row.infoData.street}},</span>
|
||||
<span v-if="props.row.infoData.special">{{props.row.infoData.special}},</span>
|
||||
<span v-if="props.row.infoData.zip">{{props.row.infoData.zip}},</span>
|
||||
<span v-if="props.row.infoData.city">{{props.row.infoData.city}}</span>
|
||||
|
||||
</template>
|
||||
19
components/columnRenderings/contact.vue
Normal file
19
components/columnRenderings/contact.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.row.contact">
|
||||
<nuxt-link v-if="props.inShow" :to="`/standardEntity/contacts/show/${props.row.contact.id}`">{{props.row.contact ? props.row.contact.name : ''}}</nuxt-link>
|
||||
<span v-else>{{props.row.contact ? props.row.contact.name : ''}}</span>
|
||||
</div></template>
|
||||
15
components/columnRenderings/created_at.vue
Normal file
15
components/columnRenderings/created_at.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.created_at">{{dayjs(props.row.created_at).format("DD.MM.YYYY HH:mm")}}</span>
|
||||
</template>
|
||||
@@ -4,10 +4,17 @@ const props = defineProps({
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.row.customer ? props.row.customer.name : ''}}</span>
|
||||
<div v-if="props.row.customer">
|
||||
<nuxt-link v-if="props.inShow" :to="`/standardEntity/customers/show/${props.row.customer.id}`">{{props.row.customer ? props.row.customer.name : ''}}</nuxt-link>
|
||||
<span v-else>{{props.row.customer ? props.row.customer.name : ''}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
13
components/columnRenderings/description.vue
Normal file
13
components/columnRenderings/description.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.row.description" v-html="props.row.description.html"/>
|
||||
</template>
|
||||
15
components/columnRenderings/endDate.vue
Normal file
15
components/columnRenderings/endDate.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.endDate">{{dayjs(props.row.endDate).format("DD.MM.YYYY")}}</span>
|
||||
</template>
|
||||
13
components/columnRenderings/isCompany.vue
Normal file
13
components/columnRenderings/isCompany.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.row.isCompany ? 'Unternehmen' : 'Privat'}}</span>
|
||||
</template>
|
||||
@@ -4,10 +4,17 @@ const props = defineProps({
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.row.plant ? props.row.plant.name : ''}}</span>
|
||||
<div v-if="props.row.plant">
|
||||
<nuxt-link v-if="props.inShow " :to="`/standardEntity/plants/show/${props.row.plant.id}`">{{props.row.plant ? props.row.plant.name : ''}}</nuxt-link>
|
||||
<span v-else>{{props.row.plant ? props.row.plant.name : ''}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
16
components/columnRenderings/profile.vue
Normal file
16
components/columnRenderings/profile.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.profile">{{props.row.profile.id ? profileStore.getProfileById(props.row.profile.id).fullName : profileStore.getProfileById(props.row.profile).fullName}}</span>
|
||||
</template>
|
||||
27
components/columnRenderings/profiles.vue
Normal file
27
components/columnRenderings/profiles.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const profiles = computed(() => props.row.profiles.map(id => profileStore.getProfileById(id).fullName).join(', '))
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.row.profiles">
|
||||
<div v-if="props.inShow">
|
||||
<nuxt-link v-for="(profileId, index) in props.row.profiles" :to="`/profiles/show/${profileId}`">{{profileStore.getProfileById(profileId).fullName}}{{index < props.row.profiles.length - 1 ? "," : ""}}</nuxt-link>
|
||||
</div>
|
||||
<span v-else>{{props.row.profiles ? profiles : ''}}</span>
|
||||
</div>
|
||||
</template>
|
||||
19
components/columnRenderings/project.vue
Normal file
19
components/columnRenderings/project.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.row.project">
|
||||
<nuxt-link v-if="props.inShow" :to="`/standardEntity/projects/show/${props.row.project.id}`">{{props.row.project ? props.row.project.name : ''}}</nuxt-link>
|
||||
<span v-else>{{props.row.project ? props.row.project.name : ''}}</span>
|
||||
</div></template>
|
||||
13
components/columnRenderings/purchasePrice.vue
Normal file
13
components/columnRenderings/purchasePrice.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.row.purchasePrice ? useCurrency(props.row.purchasePrice) : ''}}</span>
|
||||
</template>
|
||||
14
components/columnRenderings/recurring.vue
Normal file
14
components/columnRenderings/recurring.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.recurring">Ja</span>
|
||||
<span v-else>Nein</span>
|
||||
</template>
|
||||
13
components/columnRenderings/sellingPrice.vue
Normal file
13
components/columnRenderings/sellingPrice.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.row.sellingPrice ? useCurrency(props.row.sellingPrice) : ''}}</span>
|
||||
</template>
|
||||
13
components/columnRenderings/sellingPriceComposedMaterial.vue
Normal file
13
components/columnRenderings/sellingPriceComposedMaterial.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.sellingPriceComposed">{{ useCurrency(props.row.sellingPriceComposed.material)}}</span>
|
||||
</template>
|
||||
13
components/columnRenderings/sellingPriceComposedTotal.vue
Normal file
13
components/columnRenderings/sellingPriceComposedTotal.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.sellingPriceComposed">{{ useCurrency(props.row.sellingPriceComposed.total) }}</span>
|
||||
</template>
|
||||
13
components/columnRenderings/sellingPriceComposedWorker.vue
Normal file
13
components/columnRenderings/sellingPriceComposedWorker.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.sellingPriceComposed">{{ useCurrency(props.row.sellingPriceComposed.worker) }}</span>
|
||||
</template>
|
||||
14
components/columnRenderings/sepa.vue
Normal file
14
components/columnRenderings/sepa.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.hasSEPA">Ja</span>
|
||||
<span v-else>Nein</span>
|
||||
</template>
|
||||
21
components/columnRenderings/serviceCategories.vue
Normal file
21
components/columnRenderings/serviceCategories.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
|
||||
const servicecategories = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
servicecategories.value = await useSupabaseSelect("servicecategories")
|
||||
}
|
||||
|
||||
setup()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.servicecategories && servicecategories.length > 0">{{props.row.servicecategories.map(i => servicecategories.find(x => x.id === i).name).join(", ")}}</span>
|
||||
</template>
|
||||
15
components/columnRenderings/startDate.vue
Normal file
15
components/columnRenderings/startDate.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="props.row.startDate">{{dayjs(props.row.startDate).format("DD.MM.YYYY")}}</span>
|
||||
</template>
|
||||
13
components/columnRenderings/unit.vue
Normal file
13
components/columnRenderings/unit.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.row.unit ? props.row.unit.name : ''}}</span>
|
||||
</template>
|
||||
13
components/columnRenderings/usePlanning.vue
Normal file
13
components/columnRenderings/usePlanning.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.row.usePlanning ? 'Ja' : 'Nein'}}</span>
|
||||
</template>
|
||||
22
components/columnRenderings/vehicle.vue
Normal file
22
components/columnRenderings/vehicle.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.row.vehicle">
|
||||
<nuxt-link v-if="props.inShow" :to="`/standardEntity/vehicles/show/${props.row.vehicle.id}`">
|
||||
{{ props.row.vehicle ? props.row.vehicle.name : '' }}
|
||||
</nuxt-link>
|
||||
<span v-else>{{ props.row.vehicle ? props.row.vehicle.name : '' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
20
components/columnRenderings/vendor.vue
Normal file
20
components/columnRenderings/vendor.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
row: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
},
|
||||
inShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.row.vendor">
|
||||
<nuxt-link v-if="props.inShow" :to="`/standardEntity/vendors/show/${props.row.vendor.id}`">{{props.row.vendor ? props.row.vendor.name : ''}}</nuxt-link>
|
||||
<span v-else>{{props.row.vendor ? props.row.vendor.name : ''}}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -6,13 +6,14 @@ dayjs.extend(customParseFormat)
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
let incomeData = ref({})
|
||||
let expenseData = ref({})
|
||||
|
||||
const setup = async () => {
|
||||
let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",dataStore.currentTenant).in('type',['invoices'])).data
|
||||
let expenseRawData =(await supabase.from("incominginvoices").select().eq("tenant",dataStore.currentTenant)).data
|
||||
let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",profileStore.currentTenant).in('type',['invoices'])).data
|
||||
let expenseRawData =(await supabase.from("incominginvoices").select().eq("tenant",profileStore.currentTenant)).data
|
||||
|
||||
expenseRawData = expenseRawData.filter(i => i.date).map(i => {
|
||||
let amount = 0
|
||||
@@ -76,6 +77,7 @@ const setup = async () => {
|
||||
|
||||
amount = Number(amount.toFixed(2))
|
||||
|
||||
console.log(amount)
|
||||
return {
|
||||
id: i.id,
|
||||
date: dayjs(i.documentDate).format("DD-MM-YY"),
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
<script setup>
|
||||
|
||||
let unpaidCreatedDocumentsSum = ref(0)
|
||||
let unpaidInvoicesSum = ref(0)
|
||||
let unpaidInvoicesCount = ref(0)
|
||||
let draftInvoicesSum = ref(0)
|
||||
let draftInvoicesCount = ref(0)
|
||||
let unallocatedStatements = ref(0)
|
||||
const setupPage = async () => {
|
||||
let documents = (await useSupabaseSelect("createddocuments","*, statementallocations(*), customer(id,name)")).filter(i => i.type === "invoices" ||i.type === "advanceInvoices")
|
||||
let documents = (await useSupabaseSelect("createddocuments","*, statementallocations(*), customer(id,name)")).filter(i => i.type === "invoices" ||i.type === "advanceInvoices").filter(i => !i.archived)
|
||||
|
||||
documents = documents.filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== getDocumentSum(i).toFixed(2))
|
||||
let draftDocuments = documents.filter(i => i.state === "Entwurf")
|
||||
let finalizedDocuments = documents.filter(i => i.state === "Gebucht")
|
||||
|
||||
documents.forEach(i => {
|
||||
unpaidCreatedDocumentsSum.value += getDocumentSum(i) - i.statementallocations.reduce((n,{amount}) => n + amount, 0)
|
||||
console.log(finalizedDocuments)
|
||||
|
||||
|
||||
finalizedDocuments = finalizedDocuments.filter(i => i.statementallocations.reduce((n,{amount}) => n + amount, 0).toFixed(2) !== getDocumentSum(i).toFixed(2))
|
||||
|
||||
finalizedDocuments.forEach(i => {
|
||||
console.log(getDocumentSum(i))
|
||||
unpaidInvoicesSum.value += getDocumentSum(i) - i.statementallocations.reduce((n,{amount}) => n + amount, 0)
|
||||
})
|
||||
unpaidInvoicesCount.value = finalizedDocuments.length
|
||||
|
||||
draftDocuments.forEach(i => {
|
||||
draftInvoicesSum.value += getDocumentSum(i) - i.statementallocations.reduce((n,{amount}) => n + amount, 0)
|
||||
})
|
||||
draftInvoicesCount.value = draftDocuments.length
|
||||
|
||||
let bankstatements = await useSupabaseSelect("bankstatements","*, statementallocations(*)","date",true)
|
||||
unallocatedStatements.value = bankstatements.filter(i => calculateOpenSum(i) !== 0).length
|
||||
@@ -40,23 +56,25 @@ const calculateOpenSum = (statement) => {
|
||||
|
||||
return startingAmount.toFixed(2)
|
||||
}
|
||||
|
||||
const displayCurrency = (value, currency = "€") => {
|
||||
return `${Number(value).toFixed(2).replace(".",",")} ${currency}`
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Offene Ausgangsbelege:</td>
|
||||
<td>Offene Rechnungen:</td>
|
||||
<td
|
||||
v-if="unpaidCreatedDocumentsSum > 0"
|
||||
v-if="unpaidInvoicesSum > 0"
|
||||
class="text-rose-600 font-bold text-nowrap"
|
||||
>{{displayCurrency(unpaidCreatedDocumentsSum)}}</td>
|
||||
<td v-else class="text-primary-500 font-bold text-no-wrap">0,00€</td>
|
||||
>{{unpaidInvoicesCount}} Stk / {{useCurrency(unpaidInvoicesSum)}}</td>
|
||||
<td v-else class="text-primary-500 font-bold text-no-wrap">0 Stk / 0,00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Angelegte Rechnungsentwürfe:</td>
|
||||
<td
|
||||
v-if="draftInvoicesSum > 0"
|
||||
class="text-rose-600 font-bold text-nowrap"
|
||||
>{{draftInvoicesCount}} Stk / {{useCurrency(draftInvoicesSum)}}</td>
|
||||
<td v-else class="text-primary-500 font-bold text-no-wrap">0 Stk / 0,00€</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nicht zugewiesene Bankbuchungen:</td>
|
||||
|
||||
101
components/displayRunningTime.vue
Normal file
101
components/displayRunningTime.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const profileStore = useProfileStore();
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
|
||||
const runningTimeInfo = ref({})
|
||||
|
||||
const setupPage = async () => {
|
||||
runningTimeInfo.value = (await supabase.from("workingtimes").select().eq("profile", profileStore.activeProfile.id).is("endDate", null).single()).data || {}
|
||||
console.log(runningTimeInfo.value)
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
/*if(dataStore.workingtimes.find(time => time.profile === profileStore.activeProfile.id && !time.endDate)) {
|
||||
runningTimeInfo.value = dataStore.workingtimes.find(time => time.profile === profileStore.activeProfile.id && !time.end)
|
||||
}*/
|
||||
|
||||
const startTime = async () => {
|
||||
console.log("started")
|
||||
runningTimeInfo.value = {
|
||||
profile: profileStore.activeProfile.id,
|
||||
startDate: dayjs(),
|
||||
tenant: profileStore.currentTenant,
|
||||
state: "Im Web gestartet",
|
||||
source: "Dashboard"
|
||||
}
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("workingtimes")
|
||||
.insert([runningTimeInfo.value])
|
||||
.select()
|
||||
if(error) {
|
||||
console.log(error)
|
||||
toast.add({title: "Fehler beim starten der Zeit",color:"rose"})
|
||||
} else if(data) {
|
||||
toast.add({title: "Zeit erfolgreich gestartet"})
|
||||
runningTimeInfo.value = data[0]
|
||||
console.log(runningTimeInfo.value)
|
||||
}
|
||||
}
|
||||
|
||||
const stopStartedTime = async () => {
|
||||
runningTimeInfo.value.endDate = dayjs()
|
||||
runningTimeInfo.value.state = "Im Web gestoppt"
|
||||
|
||||
const {error,status} = await supabase
|
||||
.from("workingtimes")
|
||||
.update(runningTimeInfo.value)
|
||||
.eq('id',runningTimeInfo.value.id)
|
||||
|
||||
if(error) {
|
||||
console.log(error)
|
||||
let errorId = await useError().logError(`${status} - ${JSON.stringify(error)}`)
|
||||
toast.add({title: errorId ? `Fehler beim stoppen der Zeit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Zeit`,color:"rose"})
|
||||
|
||||
|
||||
} else {
|
||||
toast.add({title: "Zeit erfolgreich gestoppt"})
|
||||
runningTimeInfo.value = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="runningTimeInfo.startDate">
|
||||
<p>Start: {{dayjs(runningTimeInfo.startDate).format("HH:mm")}}</p>
|
||||
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') + ' min' }}</p>
|
||||
|
||||
<UFormGroup
|
||||
class="mt-2"
|
||||
label="Notizen:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="runningTimeInfo.notes"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UButton
|
||||
class="mt-3"
|
||||
@click="stopStartedTime"
|
||||
:disabled="!runningTimeInfo.id"
|
||||
>
|
||||
Stop
|
||||
</UButton>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>Keine Anwesenheit gestartet</p>
|
||||
<UButton
|
||||
class="mt-3"
|
||||
@click="startTime"
|
||||
>Starten</UButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
13
components/helpRenderings/quantity.vue
Normal file
13
components/helpRenderings/quantity.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>{{props.item.serialNumber ? 'Menge deaktiviert durch Eingabe der Seriennummer' : 'Für Einzelartikel Menge gleich 0'}}</span>
|
||||
</template>
|
||||
132
components/materialComposing.vue
Normal file
132
components/materialComposing.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<script setup>
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const products = ref([])
|
||||
const units = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
products.value = await useSupabaseSelect("products")
|
||||
units.value = (await supabase.from("units").select()).data
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const addProductToMaterialComposition = () => {
|
||||
|
||||
if(!props.item.materialComposition) props.item.materialComposition = []
|
||||
|
||||
props.item.materialComposition.push({
|
||||
id: uuidv4(),
|
||||
unit: 1,
|
||||
quantity: 1,
|
||||
price: 0,
|
||||
})
|
||||
calculateTotalMaterialPrice()
|
||||
}
|
||||
|
||||
const removeProductFromMaterialComposition = (id) => {
|
||||
props.item.materialComposition = props.item.materialComposition.filter((item) => item.id !== id)
|
||||
calculateTotalMaterialPrice()
|
||||
}
|
||||
|
||||
const calculateTotalMaterialPrice = () => {
|
||||
let total = 0
|
||||
|
||||
props.item.materialComposition.forEach((item) => {
|
||||
let rowSum = item.quantity * item.price
|
||||
total += rowSum
|
||||
})
|
||||
|
||||
props.item.sellingPriceComposed.material = Number(total.toFixed(2))
|
||||
props.item.sellingPriceComposed.total = Number((props.item.sellingPriceComposed.worker + props.item.sellingPriceComposed.material).toFixed(2))
|
||||
}
|
||||
|
||||
const setRowData = (row) => {
|
||||
let product = products.value.find(i => i.id === row.product)
|
||||
|
||||
row.unit = product.unit
|
||||
row.price = product.sellingPrice
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="w-full">
|
||||
<UButton
|
||||
@click="addProductToMaterialComposition"
|
||||
>
|
||||
+ Artikel
|
||||
</UButton>
|
||||
|
||||
<table class="w-full mt-3">
|
||||
<tr
|
||||
v-for="product in props.item.materialComposition"
|
||||
>
|
||||
<td>
|
||||
<USelectMenu
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
:options="products"
|
||||
value-attribute="id"
|
||||
option-attribute="name"
|
||||
v-model="product.product"
|
||||
:color="product.product ? 'primary' : 'rose'"
|
||||
@change="setRowData(product)"
|
||||
>
|
||||
<template #label>
|
||||
{{products.find(i => i.id === product.product) ? products.find(i => i.id === product.product).name : 'Kein Artikel ausgewählt'}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</td>
|
||||
<td>
|
||||
<UInput
|
||||
type="number"
|
||||
v-model="product.quantity"
|
||||
:step="units.find(i => i.id === product.unit) ? units.find(i => i.id === product.unit).step : 1"
|
||||
@change="calculateTotalMaterialPrice"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<USelectMenu
|
||||
:options="units"
|
||||
value-attribute="id"
|
||||
option-attribute="name"
|
||||
v-model="product.unit"
|
||||
></USelectMenu>
|
||||
</td>
|
||||
<td>
|
||||
<UInput
|
||||
type="number"
|
||||
v-model="product.price"
|
||||
step="0.01"
|
||||
@change="calculateTotalMaterialPrice"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<UButton
|
||||
icon="i-heroicons-x-mark"
|
||||
@click="removeProductFromMaterialComposition(product.id)"
|
||||
variant="outline"
|
||||
color="rose"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
</style>
|
||||
24
components/noAutoLoad/displayPresentProfiles.vue
Normal file
24
components/noAutoLoad/displayPresentProfiles.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
const supabase = useSupabaseClient()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const workingtimes = ref([])
|
||||
const setupPage = async () => {
|
||||
workingtimes.value = (await supabase.from("workingtimes").select().eq("tenant",profileStore.currentTenant).is("endDate",null)).data
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="workingtimes.length > 0">
|
||||
<p v-for="time in workingtimes"><UIcon name="i-heroicons-check"/> {{profileStore.getProfileById(time.profile).fullName}}</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>Keine Mitarbeiter anwesend</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user