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:
BIN
RechteDoku.xlsx
Normal file
BIN
RechteDoku.xlsx
Normal file
Binary file not shown.
5
app.vue
5
app.vue
@@ -6,8 +6,6 @@ const tenants = (await supabase.from("tenants").select()).data
|
||||
const dataStore = useDataStore()
|
||||
const viewport = useViewport()
|
||||
|
||||
console.log("1.")
|
||||
|
||||
/*watch(viewport.breakpoint, (newBreakpoint, oldBreakpoint) => {
|
||||
console.log('Breakpoint updated:', oldBreakpoint, '->', newBreakpoint)
|
||||
})*/
|
||||
@@ -52,6 +50,8 @@ useSeoMeta({
|
||||
</NuxtLayout>
|
||||
|
||||
<UNotifications/>
|
||||
<USlideovers />
|
||||
<UModals />
|
||||
<VitePwaManifest/>
|
||||
|
||||
|
||||
@@ -98,7 +98,6 @@ useSeoMeta({
|
||||
.scrollList {
|
||||
overflow-y: scroll;
|
||||
height: 85vh;
|
||||
//margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
3
composables/useCurrency.js
Normal file
3
composables/useCurrency.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export const useCurrency = (value,currencyString = " €") => {
|
||||
return `${Number(value).toFixed(2).replace(".",",")} ${currencyString}`
|
||||
}
|
||||
25
composables/useError.js
Normal file
25
composables/useError.js
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
export const useError = (resourceType) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const logError = async (error) => {
|
||||
let errorData = {
|
||||
message: error,
|
||||
tenant: profileStore.currentTenant,
|
||||
profile: profileStore.activeProfile.id
|
||||
}
|
||||
|
||||
const {data:supabaseData,error:supabaseError} = await supabase.from("errors").insert(errorData).select().single()
|
||||
|
||||
if(supabaseError) {
|
||||
console.error(supabaseError)
|
||||
} else if(supabaseData) {
|
||||
return supabaseData.id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return { logError}
|
||||
}
|
||||
214
composables/useFiles.js
Normal file
214
composables/useFiles.js
Normal file
@@ -0,0 +1,214 @@
|
||||
import index from "v-calendar";
|
||||
|
||||
export const useFiles = () => {
|
||||
const supabase = useSupabaseClient()
|
||||
const toast = useToast()
|
||||
|
||||
let bucket = "filesdev"
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const uploadFiles = async (formData, files,tags, upsert) => {
|
||||
const uploadSingleFile = async (file) => {
|
||||
//Create File Entry to Get ID for Folder
|
||||
const {data:createdFileData,error:createdFileError} = await supabase
|
||||
.from("files")
|
||||
.insert({
|
||||
tenant: profileStore.currentTenant,
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
|
||||
if(createdFileError){
|
||||
console.log(createdFileError)
|
||||
toast.add({title: "Hochladen fehlgeschlagen", icon: "i-heroicons-x-circle", color: "rose", timeout: 10000})
|
||||
} else if(createdFileData) {
|
||||
//Upload File to ID Folder
|
||||
const {data:uploadData, error: uploadError} = await supabase
|
||||
.storage
|
||||
.from(bucket)
|
||||
.upload(`${profileStore.currentTenant}/filesbyid/${createdFileData.id}/${file.name}`, file, {upsert: upsert})
|
||||
|
||||
if(uploadError) {
|
||||
console.log(uploadError)
|
||||
console.log(uploadError.statusCode)
|
||||
|
||||
if(uploadError.statusCode === '400') {
|
||||
console.log("is 400")
|
||||
toast.add({title: "Hochladen fehlgeschlagen", description: "Die Datei enthält ungültige Zeichen", icon: "i-heroicons-x-circle", color: "rose", timeout: 10000})
|
||||
} else if(uploadError.statusCode === '409') {
|
||||
console.log("is 409")
|
||||
toast.add({title: "Hochladen fehlgeschlagen", description: "Es existiert bereits eine Datei mit diesem Namen", icon: "i-heroicons-x-circle", color: "rose", timeout: 10000})
|
||||
} else {
|
||||
toast.add({title: "Hochladen fehlgeschlagen", icon: "i-heroicons-x-circle", color: "rose", timeout: 10000})
|
||||
|
||||
}
|
||||
} else if(uploadData) {
|
||||
//Update File with Corresponding Path
|
||||
const {data:updateFileData, error:updateFileError} = await supabase
|
||||
.from("files")
|
||||
.update({
|
||||
...formData,
|
||||
path: uploadData.path,
|
||||
})
|
||||
.eq("id", createdFileData.id)
|
||||
|
||||
if(updateFileError) {
|
||||
console.log(updateFileError)
|
||||
toast.add({title: "Hochladen fehlgeschlagen", icon: "i-heroicons-x-circle", color: "rose", timeout: 10000})
|
||||
} else {
|
||||
const {data:tagData, error:tagError} = await supabase
|
||||
.from("filetagmembers")
|
||||
.insert(tags.map(tag => {
|
||||
return {
|
||||
file_id: createdFileData.id,
|
||||
tag_id: tag
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
|
||||
toast.add({title: "Hochladen erfolgreich"})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(files.length === 1) {
|
||||
await uploadSingleFile(files[0])
|
||||
} else if( files.length > 1) {
|
||||
|
||||
for(let i = 0; i < files.length; i++){
|
||||
await uploadSingleFile(files[i])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const selectDocuments = async (sortColumn = null, folder = null) => {
|
||||
let data = []
|
||||
|
||||
if(sortColumn !== null ) {
|
||||
data = (await supabase
|
||||
.from("files")
|
||||
.select('*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)')
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.not("path","is",null)
|
||||
.not("archived","is",true)
|
||||
.order(sortColumn, {ascending: true})).data
|
||||
} else {
|
||||
data = (await supabase
|
||||
.from("files")
|
||||
.select('*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)')
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.not("path","is",null)
|
||||
.not("archived","is",true)).data
|
||||
|
||||
}
|
||||
|
||||
if(data.length > 0){
|
||||
let paths = []
|
||||
data.forEach(doc => {
|
||||
paths.push(doc.path)
|
||||
})
|
||||
|
||||
const {data: supabaseData,error} = await supabase.storage.from(bucket).createSignedUrls(paths,3600)
|
||||
|
||||
data = data.map((doc,index) => {
|
||||
|
||||
return {
|
||||
...doc,
|
||||
url: supabaseData[index].signedUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//console.log(data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const selectSomeDocuments = async (documentIds, sortColumn = null, folder = null) => {
|
||||
let data = null
|
||||
|
||||
if(sortColumn !== null ) {
|
||||
data = (await supabase
|
||||
.from("files")
|
||||
.select('*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)')
|
||||
.in("id",documentIds)
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.not("path","is",null)
|
||||
.not("archived","is",true)
|
||||
.order(sortColumn, {ascending: true})).data
|
||||
} else {
|
||||
data = (await supabase
|
||||
.from("files")
|
||||
.select('*, incominginvoice(*), project(*), vendor(*), customer(*), contract(*), plant(*), createddocument(*, customer(*)), vehicle(*), product(*), profile(*), check(*), inventoryitem(*)')
|
||||
.in("id",documentIds)
|
||||
.not("path","is",null)
|
||||
.not("archived","is",true)
|
||||
.eq("tenant", profileStore.currentTenant)).data
|
||||
|
||||
}
|
||||
|
||||
if(data.length > 0){
|
||||
let paths = []
|
||||
data.forEach(doc => {
|
||||
paths.push(doc.path)
|
||||
})
|
||||
|
||||
const {data: supabaseData,error} = await supabase.storage.from(bucket).createSignedUrls(paths,3600)
|
||||
|
||||
data = data.map((doc,index) => {
|
||||
|
||||
return {
|
||||
...doc,
|
||||
url: supabaseData[index].signedUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//console.log(data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const selectDocument = async (id) => {
|
||||
const {data,error} = await supabase
|
||||
.from("files")
|
||||
.select('*')
|
||||
.eq("id",id)
|
||||
.single()
|
||||
|
||||
const {data: supabaseData,error:supabaseError} = await supabase.storage.from(bucket).createSignedUrl(data.path,3600)
|
||||
|
||||
return {
|
||||
...data,
|
||||
url: supabaseData.signedUrl
|
||||
}
|
||||
/*
|
||||
if(data.length > 0){
|
||||
let paths = []
|
||||
data.forEach(doc => {
|
||||
paths.push(doc.path)
|
||||
})
|
||||
|
||||
const {data: supabaseData,error} = await supabase.storage.from(bucket).createSignedUrls(paths,3600)
|
||||
|
||||
data = data.map((doc,index) => {
|
||||
|
||||
return {
|
||||
...doc,
|
||||
url: supabaseData[index].signedUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//console.log(data)
|
||||
|
||||
return data[0]*/
|
||||
}
|
||||
|
||||
return {uploadFiles, selectDocuments, selectSomeDocuments, selectDocument}
|
||||
}
|
||||
42
composables/useFunctions.js
Normal file
42
composables/useFunctions.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import axios from "axios";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const baseURL = "https://functions.fedeo.io"
|
||||
|
||||
export const useFunctions = () => {
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const getWorkingTimesEvaluationData = async (profileId, startDate, endDate) => {
|
||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||
|
||||
return (await axios({
|
||||
method: "POST",
|
||||
url: `${baseURL}/functions/workingtimeevaluation`,
|
||||
data: {
|
||||
profile: profileId,
|
||||
startDate: dayjs(startDate).format("YYYY-MM-DD"),
|
||||
endDate: dayjs(endDate).format("YYYY-MM-DD"),
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
}
|
||||
})).data
|
||||
}
|
||||
|
||||
const useNextNumber = async (numberRange) => {
|
||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
||||
|
||||
return (await axios({
|
||||
method: "POST",
|
||||
url: `${baseURL}/functions/usenextnumber`,
|
||||
data: {
|
||||
numberRange: numberRange,
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`
|
||||
}
|
||||
})).data.usedNumber
|
||||
}
|
||||
|
||||
return {getWorkingTimesEvaluationData, useNextNumber}
|
||||
}
|
||||
@@ -3,8 +3,9 @@ export const useNumberRange = (resourceType) => {
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const numberRanges = dataStore.ownTenant.numberRanges
|
||||
const numberRanges = profileStore.ownTenant.numberRanges
|
||||
|
||||
const numberRange = numberRanges[resourceType]
|
||||
|
||||
@@ -19,13 +20,12 @@ export const useNumberRange = (resourceType) => {
|
||||
const {data,error} = await supabase
|
||||
.from("tenants")
|
||||
.update({numberRanges: newNumberRanges})
|
||||
.eq('id',dataStore.currentTenant)
|
||||
.eq('id',profileStore.currentTenant)
|
||||
|
||||
|
||||
dataStore.fetchOwnTenant()
|
||||
await profileStore.fetchOwnTenant()
|
||||
|
||||
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
|
||||
|
||||
}
|
||||
|
||||
return { useNextNumber}
|
||||
|
||||
@@ -4,9 +4,10 @@ import Handlebars from "handlebars";
|
||||
export const usePrintLabel = async (printServerId,printerName , rawZPL ) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
await supabase.from("printJobs").insert({
|
||||
tenant: dataStore.currentTenant,
|
||||
tenant: profileStore.currentTenant,
|
||||
rawContent: rawZPL,
|
||||
printerName: printerName,
|
||||
printServer: printServerId
|
||||
|
||||
250
composables/useRole.js
Normal file
250
composables/useRole.js
Normal file
@@ -0,0 +1,250 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const useRole = () => {
|
||||
const profileStore = useProfileStore()
|
||||
console.log(profileStore.currentTenant)
|
||||
|
||||
|
||||
const generalAvailableRights = ref({
|
||||
projects: {
|
||||
label: "Projekte",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"projects-viewAll": {
|
||||
label: "Alle Projekte einsehen",
|
||||
parent: "projects"
|
||||
},
|
||||
"projects-create": {
|
||||
label: "Projekte erstellen",
|
||||
parent: "projects"
|
||||
},
|
||||
contracts: {
|
||||
label: "Verträge",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"contracts-viewAll": {
|
||||
label: "Alle Verträge einsehen",
|
||||
parent: "contracts"
|
||||
},
|
||||
"contracts-create": {
|
||||
label: "Verträge erstellen",
|
||||
parent: "contracts"
|
||||
},
|
||||
plants: {
|
||||
label: "Objekte",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"plants-viewAll": {
|
||||
label: "Alle Objekte einsehen",
|
||||
parent: "plants"
|
||||
},
|
||||
"plants-create": {
|
||||
label: "Objekte erstellen",
|
||||
parent: "plants"
|
||||
},
|
||||
products: {
|
||||
label: "Artikel",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"products-create": {
|
||||
label: "Artikel erstellen",
|
||||
parent: "products"
|
||||
},
|
||||
productcategories: {
|
||||
label: "Artikelkategorie",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"productcategories-create": {
|
||||
label: "Artikelkategorie erstellen",
|
||||
parent: "productcategories"
|
||||
},
|
||||
services: {
|
||||
label: "Leistungen",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"services-create": {
|
||||
label: "Leistungen erstellen",
|
||||
parent: "services"
|
||||
},
|
||||
servicecategories: {
|
||||
label: "Leistungskategorien",
|
||||
showToAllUsers: true
|
||||
},
|
||||
"servicecategories-create": {
|
||||
label: "Leistungskategorien erstellen",
|
||||
parent: "servicecategories"
|
||||
},
|
||||
customers: {
|
||||
label: "Kunden",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"customers-viewAll": {
|
||||
label: "Alle Kunden einsehen",
|
||||
parent: "customers"
|
||||
},
|
||||
"customers-create": {
|
||||
label: "Kunden erstellen",
|
||||
parent: "customers"
|
||||
},
|
||||
contacts: {
|
||||
label: "Kontakte",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"contacts-viewAll": {
|
||||
label: "Alle Kontakte einsehen",
|
||||
parent: "contacts"
|
||||
},
|
||||
"contacts-create": {
|
||||
label: "Kontakte erstellen",
|
||||
parent: "contacts"
|
||||
},
|
||||
vendors: {
|
||||
label: "Lieferanten",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"vendors-viewAll": {
|
||||
label: "Alle Lieferanten einsehen",
|
||||
parent: "vendors"
|
||||
},
|
||||
"vendors-create": {
|
||||
label: "Lieferanten erstellen",
|
||||
parent: "vendors"
|
||||
},
|
||||
checks: {
|
||||
label: "Überprüfungen",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"checks-viewAll": {
|
||||
label: "Alle Überprüfungen einsehen",
|
||||
parent: "checks"
|
||||
},
|
||||
"checks-create": {
|
||||
label: "Überprüfungen erstellen",
|
||||
parent: "checks"
|
||||
},
|
||||
vehicles: {
|
||||
label: "Fahrzeuge",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"vehicles-viewAll": {
|
||||
label: "Alle Fahrzeuge einsehen",
|
||||
parent: "vehicles"
|
||||
},
|
||||
"vehicles-create": {
|
||||
label: "Fahrzeuge erstellen",
|
||||
parent: "vehicles"
|
||||
},
|
||||
inventoryitems: {
|
||||
label: "Inventarartikel",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"inventoryitems-viewAll": {
|
||||
label: "Alle Inventarartikel einsehen",
|
||||
parent: "inventoryitems"
|
||||
},
|
||||
"inventoryitems-create": {
|
||||
label: "Inventarartikel erstellen",
|
||||
parent: "inventoryitems"
|
||||
},
|
||||
absencerequests: {
|
||||
label: "Abwesenheiten",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"absencerequests-viewAll": {
|
||||
label: "Alle Abwesenheiten einsehen",
|
||||
parent: "absencerequests"
|
||||
},
|
||||
"absencerequests-create": {
|
||||
label: "Abwesenheiten erstellen",
|
||||
parent: "absencerequests"
|
||||
},
|
||||
events: {
|
||||
label: "Termine",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"events-viewAll": {
|
||||
label: "Alle Termine einsehen",
|
||||
parent: "events"
|
||||
},
|
||||
"events-create": {
|
||||
label: "Termine erstellen",
|
||||
parent: "events"
|
||||
},
|
||||
spaces: {
|
||||
label: "Lagerplätze",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"spaces-viewAll": {
|
||||
label: "Alle Lagerplätze einsehen",
|
||||
parent: "spaces"
|
||||
},
|
||||
"spaces-create": {
|
||||
label: "Lagerplätze erstellen",
|
||||
parent: "spaces"
|
||||
},
|
||||
roles: {
|
||||
label: "Rollen",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"roles-viewAll": {
|
||||
label: "Alle Rollen einsehen",
|
||||
parent: "roles"
|
||||
},
|
||||
"roles-create": {
|
||||
label: "Rollen erstellen",
|
||||
parent: "roles"
|
||||
},
|
||||
tasks: {
|
||||
label: "Aufgaben",
|
||||
showToAllUsers: false
|
||||
},
|
||||
"tasks-viewAll": {
|
||||
label: "Alle Aufgaben einsehen",
|
||||
parent: "tasks"
|
||||
},
|
||||
"tasks-create": {
|
||||
label: "Aufgaben erstellen",
|
||||
parent: "tasks"
|
||||
},
|
||||
"inventory": {
|
||||
label: "Lager",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
let role = profileStore.activeProfile.role
|
||||
|
||||
const checkRight = (right) => {
|
||||
let rightsToCheck = [right]
|
||||
|
||||
|
||||
//console.log(right.split("-"))
|
||||
|
||||
if(right.split("-").length > 1) {
|
||||
rightsToCheck.push(right.split("-")[0])
|
||||
}
|
||||
//console.log(rightsToCheck)
|
||||
|
||||
let hasAllNeccessaryRights = true
|
||||
|
||||
//console.log(role.rights)
|
||||
|
||||
rightsToCheck.forEach(i => {
|
||||
if(!role.rights.includes(i)){
|
||||
hasAllNeccessaryRights = false
|
||||
}
|
||||
})
|
||||
|
||||
return hasAllNeccessaryRights
|
||||
}
|
||||
|
||||
return {
|
||||
role,
|
||||
generalAvailableRights,
|
||||
checkRight
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export const useSupabaseSelect = async (relation,select = '*', sortColumn = null, ascending = true) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
let data = null
|
||||
|
||||
|
||||
@@ -10,67 +10,21 @@ export const useSupabaseSelect = async (relation,select = '*', sortColumn = null
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", dataStore.currentTenant)
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.order(sortColumn, {ascending: ascending})).data
|
||||
} else {
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", dataStore.currentTenant)).data
|
||||
.eq("tenant", profileStore.currentTenant)).data
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export const useSupabaseSelectDocuments = async (select = '*', sortColumn = null, folderPath = "_") => {
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
let data = null
|
||||
|
||||
|
||||
if(sortColumn !== null ) {
|
||||
data = (await supabase
|
||||
.from("documents")
|
||||
.select(select)
|
||||
.eq("tenant", dataStore.currentTenant)
|
||||
.eq("folderPath", folderPath)
|
||||
.order(sortColumn, {ascending: true})).data
|
||||
} else {
|
||||
data = (await supabase
|
||||
.from("documents")
|
||||
.select(select)
|
||||
.eq("tenant", dataStore.currentTenant)
|
||||
.eq("folderPath",folderPath)).data
|
||||
|
||||
}
|
||||
|
||||
if(data.length > 0){
|
||||
let paths = []
|
||||
data.forEach(doc => {
|
||||
paths.push(doc.path)
|
||||
})
|
||||
|
||||
const {data: supabaseData,error} = await supabase.storage.from('files').createSignedUrls(paths,3600)
|
||||
|
||||
data = data.map((doc,index) => {
|
||||
|
||||
return {
|
||||
...doc,
|
||||
url: supabaseData[index].signedUrl
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
//console.log(data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export const useSupabaseSelectSingle = async (relation,idToEq,select = '*' ) => {
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
let data = null
|
||||
|
||||
|
||||
@@ -78,14 +32,14 @@ export const useSupabaseSelectSingle = async (relation,idToEq,select = '*' ) =>
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", dataStore.currentTenant)
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.eq("id",idToEq)
|
||||
.single()).data
|
||||
} else {
|
||||
data = (await supabase
|
||||
.from(relation)
|
||||
.select(select)
|
||||
.eq("tenant", dataStore.currentTenant)
|
||||
.eq("tenant", profileStore.currentTenant)
|
||||
.single()).data
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
|
||||
|
||||
export const useZipCheck = async (zip) => {
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
console.log((await supabase.from("citys").select().eq("zip",Number(zip)).maybeSingle()).data)
|
||||
|
||||
const result = (await supabase.from("citys").select().eq("zip",Number(zip)).maybeSingle()).data
|
||||
|
||||
return result ? result.short : null
|
||||
|
||||
@@ -3,398 +3,20 @@
|
||||
|
||||
import MainNav from "~/components/MainNav.vue";
|
||||
import dayjs from "dayjs";
|
||||
import {useProfileStore} from "~/stores/profile.js";
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const colorMode = useColorMode()
|
||||
const { isHelpSlideoverOpen } = useDashboard()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
console.log("2.")
|
||||
|
||||
dataStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||
profileStore.initializeData((await supabase.auth.getUser()).data.user.id)
|
||||
|
||||
const month = dayjs().format("MM")
|
||||
|
||||
|
||||
/*const isLight = computed({
|
||||
get() {
|
||||
return colorMode.value !== 'dark'
|
||||
},
|
||||
set() {
|
||||
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
})
|
||||
|
||||
const navLinks = [
|
||||
{
|
||||
label: "Home",
|
||||
to: "/",
|
||||
icon: 'i-heroicons-home'
|
||||
},
|
||||
{
|
||||
label: "Kontakte",
|
||||
icon: 'i-heroicons-user-group',
|
||||
children: [
|
||||
{
|
||||
label: "Kunden",
|
||||
to: "/customers",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
{
|
||||
label: "Lieferanten",
|
||||
to: "/vendors",
|
||||
icon: "i-heroicons-truck"
|
||||
},
|
||||
{
|
||||
label: "Ansprechpartner",
|
||||
to: "/contacts",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Buchhaltung",
|
||||
icon: 'i-heroicons-document-chart-bar',
|
||||
children: [
|
||||
{
|
||||
label: "Belege",
|
||||
to: "/receipts",
|
||||
icon: "i-heroicons-document-text"
|
||||
},
|
||||
{
|
||||
label: "Bank",
|
||||
to: "/banking",
|
||||
icon: "i-heroicons-currency-euro"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Aufträge",
|
||||
icon: "i-heroicons-square-3-stack-3d",
|
||||
children: [
|
||||
{
|
||||
label: "Projekte",
|
||||
to: "/projects",
|
||||
icon: "i-heroicons-clipboard-document-check"
|
||||
},
|
||||
{
|
||||
label: "Verträge",
|
||||
to: "/contracts",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
},
|
||||
{
|
||||
label: "Objekte",
|
||||
to: "/plants",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Verwaltung",
|
||||
icon: "i-heroicons-square-3-stack-3d",
|
||||
children: [
|
||||
{
|
||||
label: "Aufgaben",
|
||||
to: "/tasks",
|
||||
icon: "i-heroicons-rectangle-stack"
|
||||
},
|
||||
{
|
||||
label: "Plantafel",
|
||||
to: "/calendar/timeline",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
},
|
||||
{
|
||||
label: "Kalender",
|
||||
to: "/calendar/grid",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
},
|
||||
{
|
||||
label: "Dokumente",
|
||||
to: "/documents",
|
||||
icon: "i-heroicons-document"
|
||||
},
|
||||
{
|
||||
label: "E-Mail",
|
||||
to: "/email",
|
||||
icon: "i-heroicons-envelope"
|
||||
},
|
||||
]
|
||||
},/!*
|
||||
{
|
||||
label: "Chat",
|
||||
to: "/chat",
|
||||
icon:'i-heroicons-chat-bubble-left-right'
|
||||
},*!/
|
||||
{
|
||||
label: "Mitarbeiter",
|
||||
icon: 'i-heroicons-user',
|
||||
children: [
|
||||
{
|
||||
label: "Zeiterfassung",
|
||||
to: "/employees/timetracking",
|
||||
icon: "i-heroicons-clock"
|
||||
},
|
||||
{
|
||||
label: "Anwesenheiten",
|
||||
to: "/workingtimes",
|
||||
icon: "i-heroicons-clock"
|
||||
},
|
||||
{
|
||||
label: "Abwesenheiten",
|
||||
to: "/absenceRequests",
|
||||
icon: "i-heroicons-document-text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Lager",
|
||||
icon: 'i-heroicons-home',
|
||||
children: [
|
||||
{
|
||||
label: "Steuerung",
|
||||
to: "/inventory",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
},
|
||||
{
|
||||
label: "Artikelstamm",
|
||||
to: "/products",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},
|
||||
{
|
||||
label: "Lagerplätze",
|
||||
to: "/spaces",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
},
|
||||
{
|
||||
label: "Fahrzeuge",
|
||||
to: "/vehicles",
|
||||
icon: "i-heroicons-truck"
|
||||
}, {
|
||||
label: "Inventar",
|
||||
to: "/inventoryitems",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},
|
||||
]
|
||||
},
|
||||
]*/
|
||||
|
||||
//const activeFeatures = dataStore.tenants.find(dataStore.currentTenant).features
|
||||
|
||||
let links = computed(() => {
|
||||
return [
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: "Dashboard",
|
||||
to: "/",
|
||||
icon: "i-heroicons-home"
|
||||
},
|
||||
{
|
||||
label: "Organisation",
|
||||
icon: "i-heroicons-rectangle-stack",
|
||||
defaultOpen: false,
|
||||
children: [
|
||||
{
|
||||
label: "Aufgaben",
|
||||
to: "/tasks",
|
||||
icon: "i-heroicons-rectangle-stack"
|
||||
},
|
||||
... dataStore.ownTenant.features.planningBoard ? [{
|
||||
label: "Plantafel",
|
||||
to: "/calendar/timeline",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.calendar ? [{
|
||||
label: "Kalender",
|
||||
to: "/calendar/grid",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.calendar ? [{
|
||||
label: "Termine",
|
||||
to: "/events",
|
||||
icon: "i-heroicons-calendar-days"
|
||||
}] : [],
|
||||
{
|
||||
label: "Dokumente",
|
||||
to: "/documents",
|
||||
icon: "i-heroicons-document"
|
||||
},
|
||||
]
|
||||
},
|
||||
/*{
|
||||
label: "E-Mail",
|
||||
to: "/email",
|
||||
icon: "i-heroicons-envelope"
|
||||
},*/
|
||||
... dataStore.ownTenant.features.contacts ? [{
|
||||
label: "Kontakte",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-user-group",
|
||||
children: [
|
||||
{
|
||||
label: "Kunden",
|
||||
to: "/customers",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
{
|
||||
label: "Lieferanten",
|
||||
to: "/vendors",
|
||||
icon: "i-heroicons-truck"
|
||||
},
|
||||
{
|
||||
label: "Ansprechpartner",
|
||||
to: "/contacts",
|
||||
icon: "i-heroicons-user-group"
|
||||
},
|
||||
]
|
||||
},] : [],
|
||||
{
|
||||
label: "Mitarbeiter",
|
||||
defaultOpen:false,
|
||||
icon: "i-heroicons-user-group",
|
||||
children: [
|
||||
... dataStore.ownTenant.features.timeTracking ? [{
|
||||
label: "Zeiterfassung",
|
||||
to: "/employees/timetracking",
|
||||
icon: "i-heroicons-clock"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.workingTimeTracking ? [{
|
||||
label: "Anwesenheiten",
|
||||
to: "/workingtimes",
|
||||
icon: "i-heroicons-clock"
|
||||
}] : [],
|
||||
{
|
||||
label: "Abwesenheiten",
|
||||
to: "/absenceRequests",
|
||||
icon: "i-heroicons-document-text"
|
||||
},
|
||||
]
|
||||
},
|
||||
... dataStore.ownTenant.features.accounting ? [{
|
||||
label: "Buchhaltung",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-chart-bar-square",
|
||||
children: [
|
||||
{
|
||||
label: "Ausgangsbelege",
|
||||
to: "/createDocument",
|
||||
icon: "i-heroicons-document-text"
|
||||
},{
|
||||
label: "Eingangsbelege",
|
||||
to: "/incomingInvoices",
|
||||
icon: "i-heroicons-document-text"
|
||||
},
|
||||
{
|
||||
label: "Bank",
|
||||
to: "/banking",
|
||||
icon: "i-heroicons-document-text"
|
||||
},
|
||||
]
|
||||
},] : [],
|
||||
... dataStore.ownTenant.features.inventory ? [{
|
||||
label: "Lager",
|
||||
icon: "i-heroicons-puzzle-piece",
|
||||
defaultOpen: false,
|
||||
children: [
|
||||
{
|
||||
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"
|
||||
},
|
||||
{
|
||||
label: "Lagerplätze",
|
||||
to: "/spaces",
|
||||
icon: "i-heroicons-square-3-stack-3d"
|
||||
},
|
||||
]
|
||||
},] : [],
|
||||
{
|
||||
label: "Inventar",
|
||||
to: "/inventoryitems",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},
|
||||
... dataStore.ownTenant.features.vehicles ? [{
|
||||
label: "Fahrzeuge",
|
||||
to: "/vehicles",
|
||||
icon: "i-heroicons-truck",
|
||||
}] : [],
|
||||
{
|
||||
label: "Stammdaten",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-clipboard-document",
|
||||
children: [
|
||||
{
|
||||
label: "Artikelstamm",
|
||||
to: "/products",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},{
|
||||
label: "Leistungsstamm",
|
||||
to: "/services",
|
||||
icon: "i-heroicons-puzzle-piece"
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
... dataStore.ownTenant.features.projects ? [{
|
||||
label: "Projekte",
|
||||
to: "/projects",
|
||||
icon: "i-heroicons-clipboard-document-check"
|
||||
},] : [],
|
||||
... dataStore.ownTenant.features.contracts ? [{
|
||||
label: "Verträge",
|
||||
to: "/contracts",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
}] : [],
|
||||
... dataStore.ownTenant.features.objects ? [{
|
||||
label: "Objekte",
|
||||
to: "/plants",
|
||||
icon: "i-heroicons-clipboard-document"
|
||||
},] : [],
|
||||
|
||||
{
|
||||
label: "Einstellungen",
|
||||
defaultOpen: false,
|
||||
icon: "i-heroicons-cog-8-tooth",
|
||||
children: [
|
||||
{
|
||||
label: "Nummernkreise",
|
||||
to: "/settings/numberRanges",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
label: "Mitarbeiter",
|
||||
to: "/profiles",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
label: "Bankkonten",
|
||||
to: "/settings/banking",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
label: "Text Vorlagen",
|
||||
to: "/settings/texttemplates",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
label: "Eigene Felder",
|
||||
to: "/settings/ownfields",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
label: "Firmeneinstellungen",
|
||||
to: "/settings/tenant",
|
||||
icon: "i-heroicons-clipboard-document-list"
|
||||
},{
|
||||
label: "Reparaturprozesse",
|
||||
to: "/repairs/prozess/",
|
||||
icon: "i-heroicons-bars-3-solid"
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const actions = [
|
||||
{
|
||||
id: 'new-customer',
|
||||
@@ -489,7 +111,7 @@ const footerLinks = [/*{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardLayout v-if="dataStore.loaded">
|
||||
<UDashboardLayout v-if="profileStore.loaded">
|
||||
|
||||
<UDashboardPanel :width="250" :resizable="{ min: 200, max: 300 }" collapsible>
|
||||
<UDashboardNavbar class="!border-transparent" :ui="{ left: 'flex-1' }">
|
||||
@@ -503,18 +125,8 @@ const footerLinks = [/*{
|
||||
<UDashboardSearchButton label="Suche..."/>
|
||||
</template>
|
||||
|
||||
<!-- <UDashboardSidebarLinks :links="links">
|
||||
|
||||
</UDashboardSidebarLinks>-->
|
||||
<MainNav/>
|
||||
|
||||
<!-- <UAccordion
|
||||
:items="links"
|
||||
>
|
||||
|
||||
</UAccordion>-->
|
||||
|
||||
|
||||
<div class="flex-1" />
|
||||
|
||||
<UDashboardSidebarLinks :links="footerLinks" />
|
||||
|
||||
@@ -28,7 +28,7 @@ export default defineNuxtConfig({
|
||||
transpile: ['@vuepic/vue-datepicker']
|
||||
},
|
||||
|
||||
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxt/content', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', '@nuxtjs/google-fonts', '@vite-pwa/nuxt', 'nuxt-viewport', 'nuxt-tiptap-editor', '@nuxtjs/leaflet'],
|
||||
modules: ['@pinia/nuxt', '@nuxt/ui', '@nuxt/content', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', '@vite-pwa/nuxt', 'nuxt-viewport', 'nuxt-tiptap-editor', '@nuxtjs/leaflet'],
|
||||
|
||||
routeRules: {
|
||||
'/printing': {ssr: false}
|
||||
@@ -54,26 +54,7 @@ export default defineNuxtConfig({
|
||||
preference: 'system'
|
||||
},
|
||||
|
||||
/*pwa: {
|
||||
manifest: {
|
||||
name: "FEDEO",
|
||||
short_name: "FEDEO",
|
||||
description: "FEDEO",
|
||||
icons: [
|
||||
{
|
||||
src: "/icon.png",
|
||||
sizes: "512x512",
|
||||
}
|
||||
|
||||
],
|
||||
display: "standalone",
|
||||
theme_color: "green"
|
||||
},
|
||||
devOptions: {
|
||||
enabled: false,
|
||||
type: "module"
|
||||
}
|
||||
},*/
|
||||
tiptap: {
|
||||
prefix: "Tiptap"
|
||||
},
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
approved: "Offen"
|
||||
})
|
||||
const states = ["Offen","Genehmigt", "Abgelehnt"]
|
||||
|
||||
const absenceReasons = [
|
||||
"Elternzeit",
|
||||
"Kind krank - Kinderbetreuung",
|
||||
"Krankheit",
|
||||
"Krankheit 1 Tag (mit Attest)",
|
||||
"Krankheit ab 2. Tag (mit Attest)",
|
||||
"Mutterschutz",
|
||||
"Sonderurlaub (bezahlt)",
|
||||
"Überstundenausgleich",
|
||||
"Unbezahlter Urlaub",
|
||||
"Urlaub"
|
||||
]
|
||||
|
||||
//Functions
|
||||
const setupPage = () => {
|
||||
if(mode.value === "show" || mode.value === "edit"){
|
||||
itemInfo.value = dataStore.getAbsenceRequestById(Number(useRoute().params.id))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const editItem = async () => {
|
||||
router.push(`/absenceRequests/edit/${itemInfo.value.id}`)
|
||||
setupPage()
|
||||
}
|
||||
|
||||
const cancelEditorCreate = () => {
|
||||
if(itemInfo.value) {
|
||||
router.push(`/absenceRequests/show/${itemInfo.value.id}`)
|
||||
} else {
|
||||
router.push(`/absenceRequests/`)
|
||||
}
|
||||
}
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Abwesenheit erstellen' : 'Abwesenheit bearbeiten')">
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('absencerequests',itemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('absencerequests',itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="cancelEditorCreate"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click="editItem"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
<template #badge v-if="itemInfo">
|
||||
<UBadge
|
||||
v-if="itemInfo.approved === 'Offen'"
|
||||
color="blue"
|
||||
>{{itemInfo.approved}}</UBadge>
|
||||
<UBadge
|
||||
v-else-if="itemInfo.approved === 'Genehmigt'"
|
||||
color="primary"
|
||||
>{{itemInfo.approved}}</UBadge>
|
||||
<UBadge
|
||||
v-else-if="itemInfo.approved === 'Abgelehnt'"
|
||||
color="rose"
|
||||
>{{itemInfo.approved}}</UBadge>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UTabs
|
||||
:items="[{label: 'Informationen'}, {label: 'Logbuch'}]"
|
||||
v-if="itemInfo && mode == 'show'"
|
||||
class="p-5"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<UCard class="mt-5">
|
||||
<div v-if="item.label === 'Informationen'">
|
||||
<div class="truncate">
|
||||
<p>Mitarbeiter: {{dataStore.profiles.find(item => item.id === itemInfo.user) ? dataStore.profiles.find(item => item.id === itemInfo.user).fullName : ""}}</p>
|
||||
<p>Start: {{dayjs(itemInfo.start).format("DD.MM.YYYY")}}</p>
|
||||
<p>Ende: {{dayjs(itemInfo.end).format("DD.MM.YYYY")}}</p>
|
||||
<p>Grund: {{itemInfo.reason}}</p>
|
||||
<p>Notizen: {{itemInfo.notes}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Logbuch'">
|
||||
<HistoryDisplay
|
||||
type="absencerequest"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
/>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
</UTabs>
|
||||
<UForm v-else-if="mode == 'edit' || mode == 'create'" class="p-5" >
|
||||
<UFormGroup
|
||||
label="Status:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.approved"
|
||||
:options="states"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Grund:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.reason"
|
||||
:options="absenceReasons"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Mitarbeiter:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.user"
|
||||
:options="dataStore.profiles"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['fullName']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getProfileById(itemInfo.user) ? dataStore.getProfileById(itemInfo.user).fullName : "Mitarbeiter auswählen"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Start:">
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.start ? dayjs(itemInfo.start).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.start" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Ende:">
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
variant="outline"
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.end ? dayjs(itemInfo.end).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.end" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
|
||||
|
||||
<UFormGroup
|
||||
label="Notizen:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.note"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,140 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Abwesenheiten" :badge="filteredRows ? filteredRows.length : null">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/absenceRequests/create`)">+ Abwesenheit</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/absenceRequests/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Abwesenheiten anzuzeigen' }"
|
||||
>
|
||||
<template #approved-data="{row}">
|
||||
<span v-if="!row.approved">
|
||||
Genehmigung offen
|
||||
</span>
|
||||
<span
|
||||
v-else-if="row.approved"
|
||||
class="text-primary"
|
||||
>
|
||||
Genemigt
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-rose"
|
||||
>
|
||||
Abgelehnt
|
||||
</span>
|
||||
</template>
|
||||
<template #user-data="{row}">
|
||||
{{dataStore.profiles.find(profile => profile.id === row.user) ? dataStore.profiles.find(profile => profile.id === row.user).fullName : ""}}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/absenceRequests/create")
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "approved",
|
||||
label: "Genehmigt",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "user",
|
||||
label: "Mitarbeiter",
|
||||
sortable: true
|
||||
},{
|
||||
key: "reason",
|
||||
label: "Grund",
|
||||
sortable: true
|
||||
},{
|
||||
key: "start",
|
||||
label: "Start",
|
||||
sortable: true
|
||||
},{
|
||||
key: "end",
|
||||
label: "Ende",
|
||||
sortable: true
|
||||
},{
|
||||
key: "note",
|
||||
label: "Notizen",
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
const searchString = ref('')
|
||||
const filteredRows = computed(() => {
|
||||
let items = dataStore.absencerequests
|
||||
|
||||
if(!searchString.value) {
|
||||
return items
|
||||
}
|
||||
|
||||
return items.filter(item => {
|
||||
return Object.values(item).some((value) => {
|
||||
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -20,6 +20,7 @@ defineShortcuts({
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const router = useRouter()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
@@ -27,7 +28,7 @@ const bankstatements = ref([])
|
||||
|
||||
|
||||
const setupPage = async () => {
|
||||
bankstatements.value = (await supabase.from("bankstatements").select("*, statementallocations(*)").eq('tenant', dataStore.currentTenant).order("date", {ascending:false})).data
|
||||
bankstatements.value = (await supabase.from("bankstatements").select("*, statementallocations(*)").eq('tenant', profileStore.currentTenant).order("date", {ascending:false})).data
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ defineShortcuts({
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const mode = ref(route.params.mode || "show")
|
||||
@@ -108,10 +109,12 @@ const calculateAllocatedSum = computed(() => {
|
||||
let startingAmount = 0
|
||||
|
||||
itemInfo.value.statementallocations.forEach(item => {
|
||||
console.log(item)
|
||||
|
||||
if(item.cd_id) {
|
||||
startingAmount = startingAmount + item.amount
|
||||
} else if(item.ii_id) {
|
||||
startingAmount = Number(startingAmount) - item.amount
|
||||
startingAmount = Number(startingAmount) + item.amount
|
||||
}
|
||||
})
|
||||
|
||||
@@ -132,7 +135,7 @@ const saveAllocations = async () => {
|
||||
const saveAllocation = async (allocation) => {
|
||||
const {data,error} = await supabase.from("statementallocations").insert({
|
||||
...allocation,
|
||||
tenant: dataStore.currentTenant
|
||||
tenant: profileStore.currentTenant
|
||||
}).select()
|
||||
|
||||
if(data) {
|
||||
@@ -251,92 +254,95 @@ setup()
|
||||
</template>
|
||||
|
||||
<table class="w-full" v-if="itemInfo.id">
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Buchungsdatum:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{dayjs(itemInfo.date).format("DD.MM.YYYY")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Wertstellungsdatum:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{dayjs(itemInfo.valueDate).format("DD.MM.YYYY")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between text-right">
|
||||
<td>
|
||||
<span class="font-semibold">Partner:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{itemInfo.amount > 0 ? itemInfo.debName : itemInfo.credName}}
|
||||
<tbody>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Buchungsdatum:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{dayjs(itemInfo.date).format("DD.MM.YYYY")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Wertstellungsdatum:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{dayjs(itemInfo.valueDate).format("DD.MM.YYYY")}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between text-right">
|
||||
<td>
|
||||
<span class="font-semibold">Partner:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{itemInfo.amount > 0 ? itemInfo.debName : itemInfo.credName}}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Partner IBAN:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{itemInfo.amount > 0 ? separateIBAN(itemInfo.debIban) : separateIBAN(itemInfo.credIban)}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Konto:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{dataStore.getBankAccountById(itemInfo.account) ? dataStore.getBankAccountById(itemInfo.account).name || separateIBAN(dataStore.getBankAccountById(itemInfo.account).iban) : ""}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td colspan="2">
|
||||
<span class="font-semibold">Verknüpfte Dokumente:</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="flex-row flex justify-between mb-3"
|
||||
v-for="item in itemInfo.statementallocations"
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Partner IBAN:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{itemInfo.amount > 0 ? separateIBAN(itemInfo.debIban) : separateIBAN(itemInfo.credIban)}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td>
|
||||
<span class="font-semibold">Konto:</span>
|
||||
</td>
|
||||
<td>
|
||||
{{dataStore.getBankAccountById(itemInfo.account) ? dataStore.getBankAccountById(itemInfo.account).name || separateIBAN(dataStore.getBankAccountById(itemInfo.account).iban) : ""}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td colspan="2">
|
||||
<span class="font-semibold">Verknüpfte Dokumente:</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="flex-row flex justify-between mb-3"
|
||||
v-for="item in itemInfo.statementallocations"
|
||||
>
|
||||
|
||||
<td>
|
||||
<!-- <span v-if="itemInfo.createdDocument"><nuxt-link :to="`/createDocument/show/${itemInfo.createdDocument}`">{{dataStore.createddocuments.find(i => i.id === itemInfo.createdDocument).documentNumber}}</nuxt-link></span>
|
||||
<span v-else-if="itemInfo.incomingInvoice"><nuxt-link :to="`/incominInvoices/show/${itemInfo.incomingInvoice}`">{{dataStore.getIncomingInvoiceById(itemInfo.incomingInvoice).reference}}</nuxt-link></span>-->
|
||||
<span v-if="item.cd_id">
|
||||
{{dataStore.getCreatedDocumentById(item.cd_id).documentNumber}}
|
||||
</span>
|
||||
<span v-else-if="item.ii_id">
|
||||
{{dataStore.getVendorById(dataStore.getIncomingInvoiceById(item.ii_id).vendor).name}} - {{dataStore.getIncomingInvoiceById(item.ii_id).reference}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<UButton
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-right-end-on-rectangle"
|
||||
@click="router.push(`/createDocument/show/${item.cd_id}`)"
|
||||
v-if="item.cd_id"
|
||||
/>
|
||||
<UButton
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-right-end-on-rectangle"
|
||||
@click="router.push(`/incominginvoices/show/${item.ii_id}`)"
|
||||
v-else-if="item.ii_id"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td colspan="2">
|
||||
<span class="font-semibold">Beschreibung:</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
{{itemInfo.text}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<td>
|
||||
<!-- <span v-if="itemInfo.createdDocument"><nuxt-link :to="`/createDocument/show/${itemInfo.createdDocument}`">{{dataStore.createddocuments.find(i => i.id === itemInfo.createdDocument).documentNumber}}</nuxt-link></span>
|
||||
<span v-else-if="itemInfo.incomingInvoice"><nuxt-link :to="`/incominInvoices/show/${itemInfo.incomingInvoice}`">{{dataStore.getIncomingInvoiceById(itemInfo.incomingInvoice).reference}}</nuxt-link></span>-->
|
||||
<span v-if="item.cd_id">
|
||||
{{dataStore.getCreatedDocumentById(item.cd_id).documentNumber}}
|
||||
</span>
|
||||
<span v-else-if="item.ii_id">
|
||||
{{dataStore.getVendorById(dataStore.getIncomingInvoiceById(item.ii_id).vendor).name}} - {{dataStore.getIncomingInvoiceById(item.ii_id).reference}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<UButton
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-right-end-on-rectangle"
|
||||
@click="router.push(`/createDocument/show/${item.cd_id}`)"
|
||||
v-if="item.cd_id"
|
||||
/>
|
||||
<UButton
|
||||
variant="outline"
|
||||
icon="i-heroicons-arrow-right-end-on-rectangle"
|
||||
@click="router.push(`/incominginvoices/show/${item.ii_id}`)"
|
||||
v-else-if="item.ii_id"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="flex-row flex justify-between">
|
||||
<td colspan="2">
|
||||
<span class="font-semibold">Beschreibung:</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
{{itemInfo.text}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</UCard>
|
||||
|
||||
@@ -6,6 +6,7 @@ import timeGridPlugin from "@fullcalendar/timegrid"
|
||||
import resourceTimelinePlugin from "@fullcalendar/resource-timeline";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import dayjs from "dayjs";
|
||||
import profiles from "~/components/columnRenderings/profiles.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
@@ -17,9 +18,10 @@ const router = useRouter()
|
||||
const mode = ref(route.params.mode || "grid")
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const resources = dataStore.getResources
|
||||
const eventTypes = dataStore.getEventTypes
|
||||
//const resources = dataStore.getResources
|
||||
//const eventTypes = dataStore.getEventTypes
|
||||
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
//Working
|
||||
const newEventData = ref({
|
||||
@@ -36,66 +38,40 @@ const showEventModal = ref(false)
|
||||
const selectedEvent = ref({})
|
||||
const selectedResources = ref([])
|
||||
|
||||
const events = ref([])
|
||||
const calendarOptionsGrid = computed(() => {
|
||||
return {
|
||||
locale: deLocale,
|
||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
||||
},
|
||||
initialView: "timeGridWeek",
|
||||
initialEvents: events.value,
|
||||
nowIndicator: true,
|
||||
height: "80vh",
|
||||
selectable: true,
|
||||
weekNumbers: true,
|
||||
select: function (info) {
|
||||
console.log(info)
|
||||
|
||||
//Functions
|
||||
const convertResourceIds = () => {
|
||||
|
||||
newEventData.value.resources = selectedResources.value.map(i => {
|
||||
/*if(i.type !== 'Mitarbeiter') {
|
||||
return {id: Number(i.id.split('-')[1]), type: i.type}
|
||||
} else {
|
||||
return {id: i.id, type: i.type}
|
||||
}*/
|
||||
|
||||
if((String(i).match(/-/g) || []).length > 1) {
|
||||
return {type: "Mitarbeiter", id: i}
|
||||
} else {
|
||||
|
||||
if(i.split('-')[0] === 'I') {
|
||||
return {id: Number(i.split('-')[1]), type: "Inventar"}
|
||||
} else if(i.split('-')[0] === 'F') {
|
||||
return {id: Number(i.split('-')[1]), type: "Fahrzeug"}
|
||||
router.push(`/standardEntity/events/create/?startDate=${encodeURIComponent(info.startStr)}&endDate=${encodeURIComponent(info.endStr)}`)
|
||||
},
|
||||
eventClick: function (info) {
|
||||
console.log(info.event)
|
||||
if(info.event.extendedProps.entrytype === "absencerequest"){
|
||||
router.push(`/standardEntity/absencerequests/show/${info.event.extendedProps.absencerequestId}`)
|
||||
} else {
|
||||
router.push(`/standardEntity/events/show/${info.event.extendedProps.eventId}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
//Calendar Config
|
||||
const calendarOptionsGrid = reactive({
|
||||
locale: deLocale,
|
||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
||||
},
|
||||
initialView: "dayGridMonth",
|
||||
initialEvents: dataStore.getEvents,
|
||||
nowIndicator: true,
|
||||
height: "80vh",
|
||||
selectable: true,
|
||||
weekNumbers: true,
|
||||
select: function(info) {
|
||||
console.log(info)
|
||||
|
||||
|
||||
router.push(`/events/edit/?start=${info.startStr}&end=${info.endStr}&source=grid`)
|
||||
|
||||
},
|
||||
eventClick: function (info){
|
||||
console.log(info)
|
||||
if(info.event.title.startsWith("Abw.:")){
|
||||
router.push(`/absencerequests/show/${info.event.id}`)
|
||||
} else {
|
||||
router.push(`/events/show/${info.event.id}`)
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const calendarOptionsTimeline = reactive({
|
||||
const calendarOptionsTimeline = ref({
|
||||
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
|
||||
locale: deLocale,
|
||||
plugins: [resourceTimelinePlugin, interactionPlugin],
|
||||
@@ -106,21 +82,26 @@ const calendarOptionsTimeline = reactive({
|
||||
center: 'title',
|
||||
right: 'resourceTimelineDay,resourceTimeline3Hours,resourceTimelineMonth'
|
||||
},
|
||||
initialEvents: dataStore.getEventsByResource,
|
||||
initialEvents: [{
|
||||
title:"Test",
|
||||
resourceId:"F-27",
|
||||
start:"2025-01-01"
|
||||
}],
|
||||
selectable: true,
|
||||
select: function (info) {
|
||||
router.push(`/events/edit/?start=${info.startStr}&end=${info.endStr}&resources=${JSON.stringify([info.resource.id])}&source=timeline`)
|
||||
router.push(`/events/edit/?startDate=${info.startStr}&endDate=${info.endStr}&resources=${JSON.stringify([info.resource.id])}&source=timeline`)
|
||||
},
|
||||
eventClick: function (info){
|
||||
if(info.event.title.startsWith("Abw.:")){
|
||||
router.push(`/absencerequests/show/${info.event.id}`)
|
||||
console.log(info.event)
|
||||
if(info.event.extendedProps.entrytype === "absencerequest"){
|
||||
router.push(`/standardEntity/absencerequests/show/${info.event.extendedProps.absencerequestId}`)
|
||||
} else {
|
||||
router.push(`/events/show/${info.event.id}`)
|
||||
router.push(`/standardEntity/events/show/${info.event.extendedProps.eventId}`)
|
||||
}
|
||||
},
|
||||
resourceGroupField: "type",
|
||||
resourceOrder: "-type",
|
||||
resources: resources,
|
||||
resources: [],
|
||||
nowIndicator:true,
|
||||
views: {
|
||||
resourceTimeline3Hours: {
|
||||
@@ -146,25 +127,280 @@ const calendarOptionsTimeline = reactive({
|
||||
height: '80vh',
|
||||
})
|
||||
|
||||
const loaded = ref(false)
|
||||
const setupPage = async () => {
|
||||
let tempData = await useSupabaseSelect("events", "*, vehicles(*), inventoryitems(*)")
|
||||
let absencerequests = await useSupabaseSelect("absencerequests", "*")
|
||||
let projects = await useSupabaseSelect("projects", "*")
|
||||
let inventoryitems = await useSupabaseSelect("inventoryitems", "*")
|
||||
let profiles = await useSupabaseSelect("profiles", "*")
|
||||
let vehicles = await useSupabaseSelect("vehicles", "*")
|
||||
|
||||
/*events.value = [
|
||||
...tempData.map(event => {
|
||||
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
|
||||
|
||||
let title = ""
|
||||
if (event.name) {
|
||||
title = event.name
|
||||
} else if (event.project) {
|
||||
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
|
||||
}
|
||||
|
||||
return {
|
||||
...event,
|
||||
start: event.startDate,
|
||||
end: event.endDate,
|
||||
title: title,
|
||||
borderColor: eventColor,
|
||||
textColor: eventColor,
|
||||
backgroundColor: "black"
|
||||
}
|
||||
}),
|
||||
...absencerequests.map(absence => {
|
||||
return {
|
||||
id: absence.id,
|
||||
resourceId: absence.user,
|
||||
resourceType: "person",
|
||||
title: `Abw.: ${absence.reason}`,
|
||||
start: dayjs(absence.start).toDate(),
|
||||
end: dayjs(absence.end).add(1, 'day').toDate(),
|
||||
allDay: true,
|
||||
}
|
||||
})
|
||||
]*/
|
||||
|
||||
calendarOptionsGrid.value.initialEvents = [
|
||||
...tempData.map(event => {
|
||||
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
|
||||
|
||||
let title = ""
|
||||
if (event.name) {
|
||||
title = event.name
|
||||
} else if (event.project) {
|
||||
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
|
||||
}
|
||||
|
||||
return {
|
||||
...event,
|
||||
start: event.startDate,
|
||||
end: event.endDate,
|
||||
title: title,
|
||||
borderColor: eventColor,
|
||||
textColor: eventColor,
|
||||
backgroundColor: "black",
|
||||
entrytype: "event",
|
||||
eventId: event.id
|
||||
}
|
||||
}),
|
||||
...absencerequests.map(absence => {
|
||||
return {
|
||||
id: absence.id,
|
||||
resourceId: absence.user,
|
||||
resourceType: "person",
|
||||
title: `${absence.reason} - ${absence.name}`,
|
||||
start: dayjs(absence.start).toDate(),
|
||||
end: dayjs(absence.end).add(1, 'day').toDate(),
|
||||
allDay: true,
|
||||
absencerequestId: absence.id,
|
||||
entrytype: "absencerequest",
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
calendarOptionsTimeline.value.resources = [
|
||||
...profiles.filter(i => i.tenant === profileStore.currentTenant).map(profile => {
|
||||
return {
|
||||
type: 'Mitarbeiter',
|
||||
title: profile.fullName,
|
||||
id: profile.id
|
||||
}
|
||||
}),
|
||||
...vehicles.map(vehicle => {
|
||||
return {
|
||||
type: 'Fahrzeug',
|
||||
title: vehicle.licensePlate,
|
||||
id: `F-${vehicle.id}`
|
||||
}
|
||||
}),
|
||||
...inventoryitems.filter(i=> i.usePlanning).map(item => {
|
||||
return {
|
||||
type: 'Inventar',
|
||||
title: item.name,
|
||||
id: `I-${item.id}`
|
||||
}
|
||||
})
|
||||
]
|
||||
console.log(calendarOptionsTimeline.value.resources)
|
||||
|
||||
/*
|
||||
calendarOptionsTimeline.value.initialEvents = [
|
||||
...events.value.map(event => {
|
||||
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
|
||||
|
||||
let title = ""
|
||||
if (event.name) {
|
||||
title = event.name
|
||||
} else if (event.project) {
|
||||
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
|
||||
}
|
||||
|
||||
let resource = ""
|
||||
let resourceType = ""
|
||||
|
||||
|
||||
|
||||
return {
|
||||
...event,
|
||||
start: event.startDate,
|
||||
end: event.endDate,
|
||||
title: title,
|
||||
borderColor: eventColor,
|
||||
textColor: eventColor,
|
||||
backgroundColor: "black"
|
||||
}
|
||||
})
|
||||
]
|
||||
*/
|
||||
|
||||
let tempEvents = []
|
||||
|
||||
tempData.forEach(event => {
|
||||
console.log(event)
|
||||
let eventColor = profileStore.ownTenant.calendarConfig.eventTypes.find(type => type.label === event.eventtype).color
|
||||
|
||||
let title = ""
|
||||
if (event.name) {
|
||||
title = event.name
|
||||
} else if (event.project) {
|
||||
projects.find(i => i.id === event.project) ? projects.find(i => i.id === event.project).name : ""
|
||||
}
|
||||
|
||||
let returnData = {
|
||||
title: title,
|
||||
borderColor: eventColor,
|
||||
textColor: eventColor,
|
||||
backgroundColor: "black",
|
||||
start: event.startDate,
|
||||
end: event.endDate,
|
||||
resourceIds: [],
|
||||
entrytype: "event",
|
||||
eventId: event.id
|
||||
}
|
||||
|
||||
|
||||
if(event.profiles.length > 0) {
|
||||
console.log("Profiles found")
|
||||
event.profiles.forEach(profile => {
|
||||
returnData.resourceIds.push(profile)
|
||||
})
|
||||
}
|
||||
|
||||
if(event.vehicles.length > 0) {
|
||||
console.log("Vehicles found")
|
||||
event.vehicles.forEach(vehicle => {
|
||||
returnData.resourceIds.push(`F-${vehicle.id}`)
|
||||
})
|
||||
}
|
||||
|
||||
if(event.inventoryitems.length > 0) {
|
||||
console.log("Inventoryitems found")
|
||||
event.inventoryitems.forEach(inventoryitem => {
|
||||
returnData.resourceIds.push(`I-${inventoryitem.id}`)
|
||||
})
|
||||
}
|
||||
|
||||
tempEvents.push(returnData)
|
||||
|
||||
})
|
||||
|
||||
absencerequests.forEach(absencerequest => {
|
||||
let returnData = {
|
||||
title: `${absencerequest.reason} - ${absencerequest.name}`,
|
||||
backgroundColor: "black",
|
||||
start: absencerequest.startDate,
|
||||
end: absencerequest.endDate,
|
||||
resourceIds: [absencerequest.profile],
|
||||
entrytype: "absencerequest",
|
||||
allDay: true,
|
||||
absencerequestId: absencerequest.id
|
||||
}
|
||||
|
||||
tempEvents.push(returnData)
|
||||
})
|
||||
|
||||
calendarOptionsTimeline.value.initialEvents = tempEvents
|
||||
console.log(calendarOptionsTimeline.value)
|
||||
|
||||
loaded.value = true
|
||||
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
|
||||
//Functions
|
||||
/*
|
||||
const convertResourceIds = () => {
|
||||
|
||||
newEventData.value.resources = selectedResources.value.map(i => {
|
||||
/!*if(i.type !== 'Mitarbeiter') {
|
||||
return {id: Number(i.id.split('-')[1]), type: i.type}
|
||||
} else {
|
||||
return {id: i.id, type: i.type}
|
||||
}*!/
|
||||
|
||||
if((String(i).match(/-/g) || []).length > 1) {
|
||||
return {type: "Mitarbeiter", id: i}
|
||||
} else {
|
||||
|
||||
if(i.split('-')[0] === 'I') {
|
||||
return {id: Number(i.split('-')[1]), type: "Inventar"}
|
||||
} else if(i.split('-')[0] === 'F') {
|
||||
return {id: Number(i.split('-')[1]), type: "Fahrzeug"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
//Calendar Config
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="currentItem ? currentItem.name : ''">
|
||||
<template #right>
|
||||
<UDashboardNavbar title="Kalender">
|
||||
<template #center>
|
||||
<h1
|
||||
:class="['text-xl','font-medium']"
|
||||
>Kalender</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<div v-if="mode === 'grid'" class="p-5">
|
||||
<FullCalendar
|
||||
:options="calendarOptionsGrid"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="mode === 'timeline'" class="p-5">
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<div v-if="mode === 'grid' && loaded" class="p-5">
|
||||
<FullCalendar
|
||||
:options="calendarOptionsGrid"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="mode === 'timeline' && loaded" class="p-5">
|
||||
<FullCalendar
|
||||
:options="calendarOptionsTimeline"
|
||||
/>
|
||||
</div>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -11,6 +11,7 @@ const supabase = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const selectedChat = ref({})
|
||||
const messageText = ref("")
|
||||
|
||||
@@ -24,8 +25,8 @@ const messageText = ref("")
|
||||
@click="selectedChat = chat"
|
||||
>
|
||||
<UAlert
|
||||
:title="chat.title ? chat.title : chat.members.map(i => { if(i !== user.id) return dataStore.getProfileById(i).fullName}).join(' ')"
|
||||
:avatar="{alt: chat.members.map(i => { if(i !== user.id) return dataStore.getProfileById(i).fullName}).join(' ')}"
|
||||
:title="chat.title ? chat.title : chat.members.map(i => { if(i !== user.id) return profileStore.getProfileById(i).fullName}).join(' ')"
|
||||
:avatar="{alt: chat.members.map(i => { if(i !== user.id) return profileStore.getProfileById(i).fullName}).join(' ')}"
|
||||
:color="selectedChat.id === chat.id ? 'primary' : 'white'"
|
||||
variant="outline"
|
||||
>
|
||||
@@ -49,13 +50,13 @@ const messageText = ref("")
|
||||
{{message.text}}
|
||||
</div>
|
||||
<UAvatar
|
||||
:alt="dataStore.getProfileById(message.origin) ? dataStore.getProfileById(message.origin).fullName : ''"
|
||||
:alt="profileStore.getProfileById(message.origin) ? profileStore.getProfileById(message.origin).fullName : ''"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-start mb-4" v-else>
|
||||
<UAvatar
|
||||
:alt="dataStore.getProfileById(message.origin) ? dataStore.getProfileById(message.origin).fullName : ''"
|
||||
:alt="profileStore.getProfileById(message.origin) ? profileStore.getProfileById(message.origin).fullName : ''"
|
||||
size="md"
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const profiles = ref([])
|
||||
@@ -10,14 +10,14 @@ const selectedProfiles = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
profiles.value = await useSupabaseSelect("profiles")
|
||||
selectedProfiles.value = [dataStore.activeProfile.id]
|
||||
selectedProfiles.value = [profileStore.activeProfile.id]
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const createChat = async () => {
|
||||
const {data,error} = await supabase.from("chats").insert({
|
||||
tenant: dataStore.currentTenant,
|
||||
tenant: profileStore.currentTenant,
|
||||
name: itemInfo.value.name
|
||||
}).select()
|
||||
|
||||
@@ -105,7 +105,7 @@ const createChat = async () => {
|
||||
:search-attributes="['fullName']"
|
||||
>
|
||||
<template #label>
|
||||
{{selectedProfiles.length > 0 ? selectedProfiles.map(i => dataStore.getProfileById(i).fullName).join(", ") : "Keine Benutzer ausgewählt"}}
|
||||
{{selectedProfiles.length > 0 ? selectedProfiles.map(i => profileStore.getProfileById(i).fullName).join(", ") : "Keine Benutzer ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
@@ -13,17 +13,17 @@ defineShortcuts({
|
||||
})
|
||||
|
||||
const itemInfo = ref({})
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const setup = async () => {
|
||||
itemInfo.value = await useSupabaseSelectSingle("chats",useRoute().params.id,"*, profiles(*), chatmessages(*)")
|
||||
|
||||
let unseenMessages = itemInfo.value.chatmessages.filter(i => !i.seenBy.includes(dataStore.activeProfile.id))
|
||||
let unseenMessages = itemInfo.value.chatmessages.filter(i => !i.seenBy.includes(profileStore.activeProfile.id))
|
||||
|
||||
|
||||
for await (const message of unseenMessages){
|
||||
await supabase.from("chatmessages").update({seenBy: [...message.seenBy, dataStore.activeProfile.id]}).eq("id",message.id)
|
||||
await supabase.from("chatmessages").update({seenBy: [...message.seenBy, profileStore.activeProfile.id]}).eq("id",message.id)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ const messageText = ref("")
|
||||
const sendMessage = async () => {
|
||||
if(messageText.value.length > 0) {
|
||||
const message = {
|
||||
origin: dataStore.activeProfile.id,
|
||||
origin: profileStore.activeProfile.id,
|
||||
destinationchat: itemInfo.value.id,
|
||||
text: messageText.value,
|
||||
tenant: dataStore.currentTenant,
|
||||
seenBy: [dataStore.activeProfile.id]
|
||||
tenant: profileStore.currentTenant,
|
||||
seenBy: [profileStore.activeProfile.id]
|
||||
}
|
||||
|
||||
const {data,error} = await supabase.from("chatmessages").insert(message)
|
||||
@@ -49,11 +49,11 @@ const sendMessage = async () => {
|
||||
//Reset
|
||||
messageText.value = ""
|
||||
//Create Notifications
|
||||
let notifications = itemInfo.value.profiles.filter(i => i.id !== dataStore.activeProfile.id).map(i => {
|
||||
let notifications = itemInfo.value.profiles.filter(i => i.id !== profileStore.activeProfile.id).map(i => {
|
||||
return {
|
||||
tenant: dataStore.currentTenant,
|
||||
tenant: profileStore.currentTenant,
|
||||
profile: i.id,
|
||||
initiatingProfile: dataStore.activeProfile.id,
|
||||
initiatingProfile: profileStore.activeProfile.id,
|
||||
title: `Sie haben eine neue Nachricht im Chat ${itemInfo.value.name}`,
|
||||
link: `/chats/show/${itemInfo.value.id}`,
|
||||
message: message.text
|
||||
@@ -88,13 +88,13 @@ const sendMessage = async () => {
|
||||
</UDashboardNavbar>
|
||||
<div class="scrollList p-5">
|
||||
<UAlert
|
||||
:color="message.seenBy.includes(dataStore.activeProfile.id) ? 'white' : 'primary'"
|
||||
:variant="message.seenBy.includes(dataStore.activeProfile.id) ? 'solid' : 'outline'"
|
||||
:color="message.seenBy.includes(profileStore.activeProfile.id) ? 'white' : 'primary'"
|
||||
:variant="message.seenBy.includes(profileStore.activeProfile.id) ? 'solid' : 'outline'"
|
||||
class="my-2"
|
||||
v-for="message in itemInfo.chatmessages"
|
||||
:description="message.text"
|
||||
:avatar="{ alt: dataStore.getProfileById(message.origin).fullName }"
|
||||
:title="`${dataStore.getProfileById(message.origin).fullName} - ${isToday(new Date(message.created_at)) ? dayjs(message.created_at).format('HH:mm') : dayjs(message.created_at).format('DD.MM.YYYY HH:mm')}`"
|
||||
:avatar="{ alt: profileStore.getProfileById(message.origin).fullName }"
|
||||
:title="`${profileStore.getProfileById(message.origin).fullName} - ${isToday(new Date(message.created_at)) ? dayjs(message.created_at).format('HH:mm') : dayjs(message.created_at).format('DD.MM.YYYY HH:mm')}`"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
<script setup>
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push("/checks")
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < 3) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
const openTab = ref(0)
|
||||
|
||||
const vehicles = ref([])
|
||||
const profiles = ref([])
|
||||
const inventoryitems = ref([])
|
||||
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
name: "",
|
||||
vehicle: null,
|
||||
inventoryitem: null,
|
||||
profile: null,
|
||||
distance: 1,
|
||||
distanceUnit: "days"
|
||||
})
|
||||
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
|
||||
profiles.value = await useSupabaseSelect("profiles")
|
||||
vehicles.value = await useSupabaseSelect("vehicles")
|
||||
inventoryitems.value = await useSupabaseSelect("inventoryitems")
|
||||
|
||||
if(mode.value === "show" ){
|
||||
itemInfo.value = await useSupabaseSelectSingle("checks", route.params.id, "*, checkexecutions(*, executed_by(fullName)), vehicle(*), profile(*), inventoryitem(*)")
|
||||
} else if (mode.value === "edit"){
|
||||
itemInfo.value = await useSupabaseSelectSingle("checks", route.params.id, "*")
|
||||
}
|
||||
|
||||
if(mode.value === "create") {
|
||||
let query = route.query
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const cancelEditorCreate = () => {
|
||||
if(itemInfo.value) {
|
||||
router.push(`/checks/show/${itemInfo.value.id}`)
|
||||
} else {
|
||||
router.push(`/checks/`)
|
||||
}
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar>
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/checks`)"
|
||||
>
|
||||
Überprüfungen
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
:class="['text-xl','font-medium']"
|
||||
>{{itemInfo ? `Überprüfung: ${itemInfo.name}` : (mode === 'create' ? 'Überprüfung erstellen' : 'Überprüfung bearbeiten')}}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('checks',itemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('checks',itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="cancelEditorCreate"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click="router.push(`/checks/edit/${itemInfo.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<UTabs
|
||||
v-if="itemInfo.id && mode === 'show'"
|
||||
:items="[{label: 'Informationen'}, {label: 'Dokumente'}, {label: 'Ausführungen'}]"
|
||||
class="p-5"
|
||||
v-model="openTab"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.label === 'Informationen'" class="flex mt-5">
|
||||
<div class="w-1/2 mr-5">
|
||||
<UCard>
|
||||
<div class="text-wrap">
|
||||
<table class="mb-3 w-full">
|
||||
<tr>
|
||||
<td>Name:</td>
|
||||
<td>{{itemInfo.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rhythmus:</td>
|
||||
<td>
|
||||
{{itemInfo.distance}}
|
||||
<span v-if="itemInfo.distanceUnit === 'dayjs'">Tage</span>
|
||||
<span v-if="itemInfo.distanceUnit === 'years'">Jahre</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="itemInfo.vehicle">
|
||||
<td>Fahrzeug:</td>
|
||||
<td>{{itemInfo.vehicle.licensePlate}}</td>
|
||||
</tr>
|
||||
<tr v-if="itemInfo.profile">
|
||||
<td>Person:</td>
|
||||
<td>{{itemInfo.profile.fullName}}</td>
|
||||
</tr>
|
||||
<tr v-if="itemInfo.inventoryitem">
|
||||
<td>Inventarartikel:</td>
|
||||
<td>{{itemInfo.inventoryitem.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Beschreibung:</td>
|
||||
<td>{{itemInfo.description}}</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UCard class="h-full">
|
||||
<HistoryDisplay
|
||||
type="check"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
:render-headline="true"
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Dokumente'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<DocumentUpload
|
||||
type="check"
|
||||
:element-id="itemInfo.id"
|
||||
/>
|
||||
</Toolbar>
|
||||
<DocumentList
|
||||
:documents="dataStore.getDocumentsByContractId(itemInfo.id)"
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Ausführungen'">
|
||||
<UCard class="mt-5">
|
||||
<UTable
|
||||
:rows="itemInfo.checkexecutions"
|
||||
:columns="[{key:'created_at',label:'Ersellt am:'},{key:'executed_at',label:'Ausgeführt am:'},{key:'executed_by',label:'Ausgeführt von:'},{key:'description',label:'Beschreibung:'}]"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Ausführungen anzuzeigen' }"
|
||||
>
|
||||
<template #created_at-data="{row}">
|
||||
{{dayjs(row.created_at).format("DD.MM.YYYY HH:mm")}}
|
||||
</template>
|
||||
<template #executed_at-data="{row}">
|
||||
{{dayjs(row.executed_at).format("DD.MM.YYYY HH:mm")}}
|
||||
</template>
|
||||
<template #executed_by-data="{row}">
|
||||
{{row.executed_by.fullName}}
|
||||
</template>
|
||||
</UTable>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</UTabs>
|
||||
<UForm v-else-if="mode === 'edit' || mode === 'create'" class="p-5" >
|
||||
<div class="mx-auto w-4/5">
|
||||
<div class="flex flex-row justify-around">
|
||||
<div class="w-1/2">
|
||||
<UDivider>Allgemeines</UDivider>
|
||||
<UFormGroup
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Typ:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.type"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Rhythmus"
|
||||
class="w-full"
|
||||
>
|
||||
<InputGroup class="w-full">
|
||||
<UInput
|
||||
v-model="itemInfo.distance"
|
||||
type="number"
|
||||
class="flex-auto"
|
||||
></UInput>
|
||||
<USelectMenu
|
||||
:options="[{key: 'days', label: 'Tage'},{key:'years',label:'Jahre'}]"
|
||||
option-attribute="label"
|
||||
value-attribute="key"
|
||||
v-model="itemInfo.distanceUnit"
|
||||
class="w-40"
|
||||
></USelectMenu>
|
||||
</InputGroup>
|
||||
</UFormGroup>
|
||||
|
||||
|
||||
</div>
|
||||
<div class=" ml-5 w-1/2">
|
||||
<UDivider>Verknüpftes Element</UDivider>
|
||||
<UFormGroup
|
||||
label="Fahrzeug:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="vehicles"
|
||||
option-attribute="licensePlate"
|
||||
value-attribute="id"
|
||||
v-model="itemInfo.vehicle"
|
||||
:disabled="itemInfo.inventoryitem || itemInfo.profile"
|
||||
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Person:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="profiles"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
v-model="itemInfo.profile"
|
||||
:disabled="itemInfo.vehicle || itemInfo.inventoryitem"
|
||||
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Inventarartikel:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="inventoryitems"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
v-model="itemInfo.inventoryitem"
|
||||
:disabled="itemInfo.vehicle || itemInfo.profile"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<UFormGroup
|
||||
label="Beschreibung:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.description"
|
||||
rows="6"
|
||||
maxrows="12"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</UForm>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
vertical-align: top;
|
||||
padding-bottom: 0.15em;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,148 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Überprüfungen" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/checks/create`)">+ Überprüfung</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
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 #name-data="{row}">
|
||||
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.name}}</span>
|
||||
<span v-else>{{row.name}}</span>
|
||||
</template>
|
||||
<template #vehicle-data="{row}">
|
||||
<span v-if="row.vehicle">{{row.vehicle.licensePlate}}</span>
|
||||
</template>
|
||||
<template #profile-data="{row}">
|
||||
<span v-if="row.profile">{{row.profile.fullName}}</span>
|
||||
</template>
|
||||
<template #inventoryitem-data="{row}">
|
||||
<span v-if="row.inventoryitem">{{row.inventoryitem.name}}</span>
|
||||
</template>
|
||||
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/checks/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/checks/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("checks","*, vehicle(licensePlate), profile(fullName), inventoryitem(name)")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "vehicle",
|
||||
label: "Fahrzeug"
|
||||
},
|
||||
{
|
||||
key: "profile",
|
||||
label: "Person"
|
||||
},
|
||||
{
|
||||
key: "inventoryitem",
|
||||
label: "Inventarartikel"
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
label: "Notizen"
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
|
||||
const searchString = ref('')
|
||||
const filteredRows = computed(() => {
|
||||
return useSearch(searchString.value, items.value)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import dayjs from "dayjs"
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
@@ -10,7 +11,7 @@ const items = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
|
||||
items.value = (await supabase.from("historyitems").select().like('text',`%@${dataStore.activeProfile.username}%`)/*.textSearch("text", `'@${dataStore.activeProfile.username}'`)*/.order("created_at")).data
|
||||
items.value = (await supabase.from("historyitems").select().like('text',`%@${profileStore.activeProfile.username}%`)/*.textSearch("text", `'@${profileStore.activeProfile.username}'`)*/.order("created_at")).data
|
||||
}
|
||||
|
||||
const navigateToHistoryItem = (item) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ defineShortcuts({
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
@@ -33,7 +34,8 @@ const openTab = ref(0)
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
active: true
|
||||
active: true,
|
||||
profiles: [profileStore.activeProfile.id]
|
||||
})
|
||||
const oldItemInfo = ref({})
|
||||
|
||||
@@ -54,21 +56,11 @@ const setupPage = async () => {
|
||||
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
||||
}
|
||||
|
||||
const cancelEditorCreate = () => {
|
||||
if(itemInfo.value) {
|
||||
router.push(`/contacts/show/${itemInfo.value.id}`)
|
||||
} else {
|
||||
router.push(`/contacts`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
:title="itemInfo ? itemInfo.fullName : (mode === 'create' ? 'Ansprechpartner erstellen' : 'Ansprechpartner bearbeiten')"
|
||||
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
|
||||
>
|
||||
<template #left>
|
||||
@@ -84,7 +76,7 @@ setupPage()
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
:class="['text-xl','font-medium', ... itemInfo.active ? ['text-primary'] : ['text-rose-500']]"
|
||||
>{{itemInfo ? `Ansprechpartner: ${itemInfo.fullName}` : (mode === 'create' ? 'Ansprechpartner erstellen' : 'Ansprechpartner bearbeiten')}}</h1>
|
||||
>{{itemInfo.id ? `Ansprechpartner: ${itemInfo.fullName}` : (mode === 'create' ? 'Ansprechpartner erstellen' : 'Ansprechpartner bearbeiten')}}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
@@ -100,7 +92,7 @@ setupPage()
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="cancelEditorCreate"
|
||||
@click="router.push(itemInfo.id ? `/contacts/show/${itemInfo.value.id}` : `/contacts/`)"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
@@ -310,6 +302,23 @@ setupPage()
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Berechtige Benutzer:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.profiles"
|
||||
:options="profileStore.profiles"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
multiple
|
||||
:search-attributes="['fullName']"
|
||||
>
|
||||
<template #label>
|
||||
{{itemInfo.profiles.length > 0 ? itemInfo.profiles.map(i => profileStore.getProfileById(i).fullName).join(", ") : "Kein Benutzer ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Notizen:"
|
||||
>
|
||||
@@ -317,6 +326,7 @@ setupPage()
|
||||
v-model="itemInfo.notes"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,182 +1,24 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Ansprechpartner" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/contacts/create`)">+ Kontakt</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/contacts/show/${i.id}`)"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Ansprechpartner anzuzeigen' }"
|
||||
>
|
||||
<template #fullName-data="{row}">
|
||||
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.fullName}}</span>
|
||||
<span v-else>{{row.fullName}}</span>
|
||||
</template>
|
||||
<template #active-data="{row}">
|
||||
<span v-if="row.active" class="text-primary-500">Aktiv</span>
|
||||
<span v-else class="text-rose">Gesperrt</span>
|
||||
</template>
|
||||
<template #customer-data="{row}">
|
||||
{{row.customer ? row.customer.name : ""}}
|
||||
</template>
|
||||
<template #vendor-data="{row}">
|
||||
{{row.vendor ? row.vendor.name : ""}}
|
||||
</template>
|
||||
</UTable>
|
||||
<EntityList
|
||||
type="contacts"
|
||||
:items="items"
|
||||
></EntityList>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {useSearch} from "~/composables/useSearch.js";
|
||||
import EntityList from "~/components/EntityList.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/contacts/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/contacts/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("contacts","*, customer(name), vendor(name)")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
|
||||
|
||||
const templateColumns = [
|
||||
|
||||
{
|
||||
key: "fullName",
|
||||
label: "Name",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "customer",
|
||||
label: "Kunde",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "vendor",
|
||||
label: "Lieferant",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "role",
|
||||
label: "Rolle",
|
||||
},
|
||||
{
|
||||
key: "email",
|
||||
label: "E-Mail",
|
||||
},
|
||||
{
|
||||
key: "phoneMobile",
|
||||
label: "Mobil",
|
||||
},
|
||||
{
|
||||
key: "phoneHome",
|
||||
label: "Festnetz",
|
||||
},
|
||||
{
|
||||
key: "active",
|
||||
label: "Aktiv",
|
||||
},
|
||||
{
|
||||
key: "birtday",
|
||||
label: "Geburtstag",
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
/*const filteredRows = computed(() => {
|
||||
if(!searchString.value) {
|
||||
return dataStore.contacts
|
||||
}
|
||||
|
||||
return dataStore.contacts.filter(item => {
|
||||
return Object.values(item).some((value) => {
|
||||
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
})*/
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
return useSearch(searchString.value, items.value)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,534 +0,0 @@
|
||||
<script setup>
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push("/contracts")
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < 3) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
const openTab = ref(0)
|
||||
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
name: "",
|
||||
customer: null,
|
||||
active: true,
|
||||
ownFields: {}
|
||||
})
|
||||
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
if(mode.value === "show" ){
|
||||
itemInfo.value = await useSupabaseSelectSingle("contracts", route.params.id, "*, customer(id,name,isCompany, customerNumber), contact(id,fullName)")
|
||||
} else if (mode.value === "edit"){
|
||||
itemInfo.value = await useSupabaseSelectSingle("contracts", route.params.id, "*")
|
||||
}
|
||||
|
||||
|
||||
if(mode.value === "create") {
|
||||
let query = route.query
|
||||
|
||||
if(query.customer) itemInfo.value.customer = Number(query.customer)
|
||||
}
|
||||
}
|
||||
|
||||
const cancelEditorCreate = () => {
|
||||
if(itemInfo.value) {
|
||||
router.push(`/contracts/show/${itemInfo.value.id}`)
|
||||
} else {
|
||||
router.push(`/contracts/`)
|
||||
}
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Vertrag erstellen' : 'Vertrag bearbeiten')">
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/contracts`)"
|
||||
>
|
||||
Verträge
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
:class="['text-xl','font-medium', ... itemInfo.active ? ['text-primary'] : ['text-rose-500']]"
|
||||
>{{itemInfo ? `Vertrag: ${itemInfo.name}` : (mode === 'create' ? 'Vertrag erstellen' : 'Vertrag bearbeiten')}}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('contracts',itemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('contracts',itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="cancelEditorCreate"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click="router.push(`/contracts/edit/${itemInfo.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<UTabs
|
||||
v-if="itemInfo.id && mode === 'show'"
|
||||
:items="[{label: 'Informationen'}, {label: 'Dokumente'}]"
|
||||
class="p-5"
|
||||
v-model="openTab"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.label === 'Informationen'" class="flex mt-5">
|
||||
<div class="w-1/2 mr-5">
|
||||
<UCard>
|
||||
<div class="text-wrap">
|
||||
<table class="mb-3">
|
||||
<tr>
|
||||
<td>Name:</td>
|
||||
<td>{{itemInfo.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aktiv:</td>
|
||||
<td>
|
||||
<span v-if="itemInfo.active" class="text-primary-500">Ja</span>
|
||||
<span v-else class="text-rose-600">Nein</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kunde:</td>
|
||||
<td>
|
||||
<nuxt-link
|
||||
:to="`/customers/show/${itemInfo.customer.id}`"
|
||||
v-if="itemInfo.customer"
|
||||
>{{itemInfo.customer.customerNumber ? `${itemInfo.customer.customerNumber} - ` : ''}}{{itemInfo.customer ? itemInfo.customer.name : ""}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="itemInfo.customer.isCompany">
|
||||
<td>Ansprechpartner:</td>
|
||||
<td>
|
||||
<nuxt-link
|
||||
v-if="itemInfo.contact"
|
||||
:to="`/contacts/show/${itemInfo.contact.id}`"
|
||||
>{{itemInfo.contact ? itemInfo.contact.fullName : ""}}</nuxt-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wiederkehrend:</td>
|
||||
<td>{{itemInfo.recurring ? "Ja" : "Nein"}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Startdatum:</td>
|
||||
<td>{{dayjs(itemInfo.startDate).format("DD.MM.YYYY")}}</td>
|
||||
</tr>
|
||||
<tr v-if="!itemInfo.active">
|
||||
<td>Enddatum:</td>
|
||||
<td>{{dayjs(itemInfo.endDate).format("DD.MM.YYYY")}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unterschrieben am:</td>
|
||||
<td>{{dayjs(itemInfo.signDate).format("DD.MM.YYYY")}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Laufzeit:</td>
|
||||
<td>{{itemInfo.duration}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rechnungsversand:</td>
|
||||
<td>{{itemInfo.invoiceDispatch}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Zahlart:</td>
|
||||
<td>{{itemInfo.paymentType}}</td>
|
||||
</tr>
|
||||
<tr v-if="itemInfo.paymentType === 'Einzug'">
|
||||
<td>SEPA Mandatsreferenz:</td>
|
||||
<td>{{itemInfo.sepaRef}}</td>
|
||||
</tr>
|
||||
<tr v-if="itemInfo.paymentType === 'Einzug'">
|
||||
<td>SEPA Unterschrieben am:</td>
|
||||
<td>{{dayjs(itemInfo.sepaDate).format("DD.MM.YYYY")}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bank:</td>
|
||||
<td>{{itemInfo.bankingName}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>BIC:</td>
|
||||
<td>{{itemInfo.bankingBIC}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IBAN:</td>
|
||||
<td>{{itemInfo.bankingIban}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kontoinhaber:</td>
|
||||
<td>{{itemInfo.bankingOwner}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Beschreibung:</td>
|
||||
<td>{{itemInfo.notes}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="font-bold">Eigene Felder:</span></td>
|
||||
</tr>
|
||||
<tr v-for="fieldKey in Object.keys(itemInfo.ownFields)">
|
||||
<td>{{dataStore.ownTenant.ownFields.contracts.find(i => i.key === fieldKey).label}}</td>
|
||||
<td>{{itemInfo.ownFields[fieldKey]}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UCard class="h-full">
|
||||
<HistoryDisplay
|
||||
type="contract"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
:render-headline="true"
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <UBadge
|
||||
v-if="itemInfo.active"
|
||||
>
|
||||
Vertrag aktiv
|
||||
</UBadge>
|
||||
<UBadge
|
||||
v-else
|
||||
color="red"
|
||||
>
|
||||
Vertrag gesperrt
|
||||
</UBadge>-->
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Dokumente'">
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<DocumentUpload
|
||||
type="contract"
|
||||
:element-id="itemInfo.id"
|
||||
/>
|
||||
</Toolbar>
|
||||
<DocumentList
|
||||
:documents="dataStore.getDocumentsByContractId(itemInfo.id)"
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</UTabs>
|
||||
<UForm v-else-if="mode == 'edit' || mode == 'create'" class="p-5" >
|
||||
<div class="mx-auto w-4/5">
|
||||
<div class="flex flex-row justify-around">
|
||||
<div class="w-1/2">
|
||||
<UDivider>Vertragsdaten</UDivider>
|
||||
<UFormGroup
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Kunde:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.customer"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
:options="dataStore.customers"
|
||||
@change="itemInfo.contact = null"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getCustomerById(itemInfo.customer) ? dataStore.getCustomerById(itemInfo.customer).name : "Kein Kunde ausgewählt" }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Ansprechpartner:"
|
||||
v-if="itemInfo.customer"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.contact"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
:options="dataStore.getContactsByCustomerId(itemInfo.customer)"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getContactById(itemInfo.contact) ? dataStore.getContactById(itemInfo.contact).fullName : "Kein Ansprechpartner ausgewählt" }}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
<InputGroup>
|
||||
<UFormGroup
|
||||
label="Vertrag aktiv:"
|
||||
>
|
||||
<UCheckbox
|
||||
v-model="itemInfo.active"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Vertrag wiederkehrend:"
|
||||
>
|
||||
<UCheckbox
|
||||
v-model="itemInfo.recurring"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
<InputGroup>
|
||||
<UFormGroup
|
||||
label="Vertragsstart:"
|
||||
class="mt-2"
|
||||
>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.startDate ? dayjs(itemInfo.startDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.startDate" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Vertragsende(voraussichtlich):"
|
||||
class="mt-2"
|
||||
>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.endDate ? dayjs(itemInfo.endDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.endDate" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
</InputGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="mindest Vertragslaufzeit:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="['12 Monate','24 Monate','36 Monate','48 Monate']"
|
||||
v-model="itemInfo.duration"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Datum der Unterzeichnung:"
|
||||
class="mt-2"
|
||||
>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.signDate ? dayjs(itemInfo.signDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.signDate" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
<div class=" ml-5 w-1/2">
|
||||
<UDivider>Abrechnung</UDivider>
|
||||
<UFormGroup
|
||||
label="Rechnungsversand:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="['E-Mail', 'Post']"
|
||||
v-model="itemInfo.invoiceDispatch"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Zahlungsart:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="['Einzug', 'Überweisung']"
|
||||
v-model="itemInfo.paymentType"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Sepa Mandat"
|
||||
>
|
||||
<InputGroup>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.sepaDate ? dayjs(itemInfo.sepaDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.sepaDate" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
<UInput
|
||||
placeholder="Mandatsreferenz"
|
||||
class="flex-auto"
|
||||
v-model="itemInfo.sepaRef"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Kontodaten:"
|
||||
>
|
||||
<InputGroup>
|
||||
<UInput
|
||||
v-model="itemInfo.bankingIban"
|
||||
placeholder="IBAN"
|
||||
class="w-1/2"
|
||||
/>
|
||||
<UInput
|
||||
v-model="itemInfo.bankingOwner"
|
||||
class="w-1/2"
|
||||
placeholder="Inhaber"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Bankdaten:"
|
||||
>
|
||||
<InputGroup>
|
||||
<UInput
|
||||
v-model="itemInfo.bankingName"
|
||||
placeholder="Name"
|
||||
class="w-1/2"
|
||||
/>
|
||||
<UInput
|
||||
v-model="itemInfo.bankingBIC"
|
||||
placeholder="BIC"
|
||||
class="w-1/2"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
<UFormGroup
|
||||
label="Notizen:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.notes"
|
||||
rows="6"
|
||||
maxrows="12"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<div
|
||||
v-if="dataStore.ownTenant.ownFields"
|
||||
>
|
||||
<UDivider
|
||||
class="mt-3"
|
||||
>Eigene Felder</UDivider>
|
||||
|
||||
<UFormGroup
|
||||
v-for="field in dataStore.ownTenant.ownFields.contracts"
|
||||
:key="field.key"
|
||||
:label="field.label"
|
||||
>
|
||||
<UInput
|
||||
v-if="field.type === 'text'"
|
||||
v-model="itemInfo.ownFields[field.key]"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-else-if="field.type === 'select'"
|
||||
:options="field.options"
|
||||
v-model="itemInfo.ownFields[field.key]"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</UForm>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
vertical-align: top;
|
||||
padding-bottom: 0.15em;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,131 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Verträge" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/contracts/create`)">+ Vertrag</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/contracts/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Verträge anzuzeigen' }"
|
||||
>
|
||||
<template #customer-data="{row}">
|
||||
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.customer ? row.customer.name : ""}}</span>
|
||||
<span v-else>{{row.customer ? row.customer.name : ""}}</span>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/contracts/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/contracts/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("contracts","*, customer(id,name)")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: 'customer',
|
||||
label: "Kunde",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
label: "Notizen"
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
|
||||
const searchString = ref('')
|
||||
const filteredRows = computed(() => {
|
||||
return useSearch(searchString.value, items.value)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #amount-data="{row}">
|
||||
{{displayCurrency(calculateDocSum(row))}}
|
||||
<span v-if="row.type !== 'deliveryNotes'">{{displayCurrency(calculateDocSum(row))}}</span>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
@@ -162,7 +162,7 @@ const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("createddocuments","*, customer(id,name), statementallocations(id,amount)","documentDate")
|
||||
items.value = (await useSupabaseSelect("createddocuments","*, customer(id,name), statementallocations(id,amount)","documentNumber")).filter(i => !i.archived)
|
||||
}
|
||||
|
||||
setupPage()
|
||||
@@ -215,6 +215,9 @@ const templateTypes = [
|
||||
{
|
||||
key: "invoices",
|
||||
label: "Rechnungen"
|
||||
},{
|
||||
key: "cancellationInvoices",
|
||||
label: "Stornorechnungen"
|
||||
},{
|
||||
key: "advanceInvoices",
|
||||
label: "Abschlagsrechnungen"
|
||||
@@ -269,7 +272,7 @@ const calculateDocSum = (row) => {
|
||||
|
||||
row.rows.forEach(row => {
|
||||
if(row.mode === "normal" || row.mode === "service" || row.mode === "free") {
|
||||
sum += row.quantity * row.price * (1 - row.discountPercent / 100) * (1 + row.taxPercent / 100)
|
||||
sum += row.quantity * row.price * (1 - row.discountPercent / 100) * (1 + (row.taxPercent || 0) / 100)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -280,10 +283,6 @@ const isPaid = (item) => {
|
||||
let amountPaid = 0
|
||||
item.statementallocations.forEach(allocation => amountPaid += allocation.amount)
|
||||
|
||||
console.log(item.documentNumber)
|
||||
console.log(amountPaid)
|
||||
console.log(calculateDocSum(item))
|
||||
|
||||
return Number(amountPaid.toFixed(2)) === Number(calculateDocSum(item))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,7 @@ defineShortcuts({
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -20,13 +21,20 @@ const linkedDocument =ref({})
|
||||
const currentTenant = ref({})
|
||||
const setupPage = async () => {
|
||||
if(route.params) {
|
||||
if(route.params.id) itemInfo.value = await useSupabaseSelectSingle("createddocuments",route.params.id,"*")
|
||||
const {data,error} = await supabase.from("documents").select("id").eq("createdDocument", route.params.id).order("id",{ascending:true})
|
||||
linkedDocument.value = data[data.length -1]
|
||||
if(route.params.id) itemInfo.value = await useSupabaseSelectSingle("createddocuments",route.params.id,"*, files(*)")
|
||||
|
||||
console.log(itemInfo.value)
|
||||
|
||||
linkedDocument.value = await useFiles().selectDocument(itemInfo.value.files[0].id)
|
||||
|
||||
|
||||
|
||||
//const {data,error} = await supabase.from("files").select("id").eq("createddocument", route.params.id).order("id",{ascending:true})
|
||||
//linkedDocument.value = data[data.length -1]
|
||||
|
||||
}
|
||||
|
||||
currentTenant.value = (await supabase.from("tenants").select().eq("id",dataStore.currentTenant).single()).data
|
||||
currentTenant.value = (await supabase.from("tenants").select().eq("id",profileStore.currentTenant).single()).data
|
||||
console.log(currentTenant.value)
|
||||
|
||||
}
|
||||
@@ -35,9 +43,9 @@ setupPage()
|
||||
|
||||
const openEmail = () => {
|
||||
if(["invoices","advanceInvoices"].includes(itemInfo.value.type)){
|
||||
router.push(`/email/new?loadDocuments=[${linkedDocument.value.id}]&bcc=${encodeURIComponent(currentTenant.value.standardEmailForInvoices)}`)
|
||||
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]&bcc=${encodeURIComponent(currentTenant.value.standardEmailForInvoices)}`)
|
||||
} else {
|
||||
router.push(`/email/new?loadDocuments=[${linkedDocument.value.id}]`)
|
||||
router.push(`/email/new?loadDocuments=["${linkedDocument.value.id}"]`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -56,10 +64,10 @@ const openEmail = () => {
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
<UButton
|
||||
<!-- <UButton
|
||||
:to="dataStore.documents.find(i => i.createdDocument === itemInfo.id) ? dataStore.documents.find(i => i.createdDocument === itemInfo.id).url : ''"
|
||||
target="_blank"
|
||||
>In neuen Tab anzeigen</UButton>
|
||||
>In neuen Tab anzeigen</UButton>-->
|
||||
<UButton
|
||||
@click="router.push(`/createDocument/edit/?linkedDocument=${itemInfo.id}`)"
|
||||
>
|
||||
@@ -71,13 +79,32 @@ const openEmail = () => {
|
||||
>
|
||||
E-Mail
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="router.push(`/createDocument/edit/?linkedDocument=${itemInfo.id}&loadMode=storno`)"
|
||||
variant="outline"
|
||||
color="rose"
|
||||
>
|
||||
Stornieren
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="itemInfo.project"
|
||||
@click="router.push(`/standardEntity/projects/show/${itemInfo.project}`)"
|
||||
icon="i-heroicons-arrow-right-end-on-rectangle"
|
||||
variant="outline"
|
||||
>
|
||||
Projekt
|
||||
</UButton>
|
||||
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<UDashboardPanelContent>
|
||||
<object
|
||||
:data="linkedDocument.url"
|
||||
class="h-full"
|
||||
/>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
|
||||
<object
|
||||
:data="dataStore.documents.find(i => i.id === linkedDocument.id) ? dataStore.documents.find(i => i.id === linkedDocument.id).url : ''"
|
||||
class="h-full"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push("/customers")
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < 3) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
|
||||
const openTab = ref(0)
|
||||
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
name: "",
|
||||
infoData: {
|
||||
country: "Deutschland"
|
||||
},
|
||||
active: true,
|
||||
isCompany: true
|
||||
})
|
||||
const oldItemInfo = ref({})
|
||||
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
if(mode.value === "show"){
|
||||
itemInfo.value = await useSupabaseSelectSingle("customers",route.params.id,"*, contacts(*)")
|
||||
} else if(mode.value === "edit") {
|
||||
itemInfo.value = await useSupabaseSelectSingle("customers",route.params.id,"*")
|
||||
}
|
||||
|
||||
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
||||
}
|
||||
|
||||
const editItem = async () => {
|
||||
await router.push(`/customers/edit/${itemInfo.value.id}`)
|
||||
}
|
||||
|
||||
const setCityByZip = async () => {
|
||||
itemInfo.value.infoData.city = await useZipCheck(itemInfo.value.infoData.zip)
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
:title="itemInfo ? `Kunde: ${itemInfo.name}` : (mode === 'create' ? 'Kunde erstellen' : 'Kunde bearbeiten')"
|
||||
:ui="{center: 'flex items-stretch gap-1.5 min-w-0'}"
|
||||
>
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/customers`)"
|
||||
>
|
||||
Kunden
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
:class="['text-xl','font-medium', ... itemInfo.active ? ['text-primary'] : ['text-rose-500']]"
|
||||
>{{itemInfo ? `Kunde: ${itemInfo.name}` : (mode === 'create' ? 'Kunde erstellen' : 'Kunde bearbeiten')}}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('customers',itemInfo,oldItemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('customers',itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="itemInfo ? router.push(`/customers/show/${itemInfo.id}`) : router.push(`/customers`)"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click="editItem"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
<template #badge v-if="itemInfo">
|
||||
<UBadge
|
||||
v-if="itemInfo.active"
|
||||
>
|
||||
Aktiv
|
||||
</UBadge>
|
||||
<UBadge
|
||||
v-else
|
||||
color="red"
|
||||
>
|
||||
Gesperrt
|
||||
</UBadge>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<UTabs
|
||||
v-if="itemInfo.id && mode == 'show'"
|
||||
:items="[{label: 'Informationen'},{label: 'Projekte'},{label: 'Objekte'},{label: 'Verträge'}]"
|
||||
class="p-5"
|
||||
v-model="openTab"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.label === 'Informationen'" class="flex mt-5">
|
||||
<div class="w-1/2 mr-5">
|
||||
<UCard >
|
||||
<div class="text-wrap">
|
||||
<p>Kundennummer: {{itemInfo.customerNumber}}</p>
|
||||
<p>Typ: {{itemInfo.isCompany ? 'Firma' : 'Privatperson'}}</p>
|
||||
<p v-if="itemInfo.infoData.street">Straße + Hausnummer: {{itemInfo.infoData.street}}</p>
|
||||
<p v-if="itemInfo.infoData.zip && itemInfo.infoData.city">PLZ + Ort: {{itemInfo.infoData.zip}} {{itemInfo.infoData.city}}</p>
|
||||
<p v-if="itemInfo.infoData.tel">Telefon: {{itemInfo.infoData.tel}}</p>
|
||||
<p v-if="itemInfo.infoData.email">E-Mail: {{itemInfo.infoData.email}}</p>
|
||||
<p v-if="itemInfo.infoData.web">Web: {{itemInfo.infoData.web}}</p>
|
||||
<p v-if="itemInfo.infoData.ustid">USt-Id: {{itemInfo.infoData.ustid}}</p>
|
||||
<p>Notizen:<br> {{itemInfo.notes}}</p>
|
||||
</div>
|
||||
|
||||
</UCard>
|
||||
<UCard class="mt-5">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/contacts/create?customer=${itemInfo.id}`)"
|
||||
>
|
||||
+ Ansprechpartner
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="dataStore.getContactsByCustomerId(itemInfo.id)"
|
||||
@select="(row) => router.push(`/contacts/show/${row.id}`)"
|
||||
:columns="[{label: 'Anrede', key: 'salutation'},{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 class="w-1/2">
|
||||
<UCard class="h-full">
|
||||
<HistoryDisplay
|
||||
type="customer"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
:render-headline="true"
|
||||
/>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<UCard class="mt-5" v-else>
|
||||
|
||||
<div v-if="item.label === 'Projekte'">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/projects/create?customer=${itemInfo.id}`)"
|
||||
>
|
||||
+ Projekt
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="dataStore.getProjectsByCustomerId(itemInfo.id)"
|
||||
@select="(row) => router.push(`/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>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Objekte'">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/plants/create?customer=${itemInfo.id}`)"
|
||||
>
|
||||
+ Objekt
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="dataStore.getPlantsByCustomerId(itemInfo.id)"
|
||||
@select="(row) => router.push(`/plants/show/${row.id}`)"
|
||||
:columns="[{label: 'Name', key: 'name'}]"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Objekte' }"
|
||||
|
||||
>
|
||||
|
||||
</UTable>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Verträge'">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/contracts/create?customer=${itemInfo.id}`)"
|
||||
>
|
||||
+ Vertrag
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="dataStore.getContractsByCustomerId(itemInfo.id)"
|
||||
@select="(row) => router.push(`/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>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
</template>
|
||||
</UTabs>
|
||||
<UForm v-else-if="mode === 'edit' || mode === 'create'" class="p-5">
|
||||
<UFormGroup
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Kundennummer:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.customerNumber"
|
||||
placeholder="Leer lassen für automatisch generierte Nummer"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UTooltip text="Ist ein Kunde nicht aktiv so wird er für neue Aufträge gesperrt">
|
||||
<UFormGroup
|
||||
label="Kunde aktiv:"
|
||||
>
|
||||
<UCheckbox
|
||||
v-model="itemInfo.active"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</UTooltip>
|
||||
<UFormGroup
|
||||
label="Firmenkunde:"
|
||||
>
|
||||
<UCheckbox
|
||||
v-model="itemInfo.isCompany"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Notizen:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.notes"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Straße + Hausnummer"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.street"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Adresszusatz"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.special"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Postleitzahl"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.zip"
|
||||
@focusout="setCityByZip"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Ort"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.city"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Land"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="['Deutschland','Niederlande','Belgien','Italien', 'Frankreich','Irland','USA','Spanien', 'Schweden']"
|
||||
v-model="itemInfo.infoData.country"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Telefon:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.tel"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="E-Mail:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.email"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Webseite:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.web"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="USt-Id:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.infoData.ustid"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,168 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Kunden" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
name="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/customers/create/`)">+ Kunde</UButton>
|
||||
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/customers/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Kunden anzuzeigen' }"
|
||||
>
|
||||
<template #name-data="{row}">
|
||||
<span v-if="row === filteredRows[selectedItem]" class="text-primary-500 font-bold">{{row.name}}</span>
|
||||
<span v-else>{{row.name}}</span>
|
||||
</template>
|
||||
<template #isCompany-data="{row}">
|
||||
<span v-if="row.isCompany">Firmenkunden</span>
|
||||
<span v-else>Privatkunde</span>
|
||||
</template>
|
||||
<template #active-data="{row}">
|
||||
<span v-if="row.active" class="text-primary-500">Aktiv</span>
|
||||
<span v-else class="text-rose-500">Gesperrt</span>
|
||||
</template>
|
||||
<template #address-data="{row}">
|
||||
{{row.infoData.street ? `${row.infoData.street}, ` : ''}}{{row.infoData.special ? `${row.infoData.special},` : ''}} {{row.infoData.zip ? row.infoData.zip : ""}} {{row.infoData.city ? `${row.infoData.city}, ` : ''}} {{row.infoData.country}}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/customers/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/customers/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("customers",null,"customerNumber")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: 'customerNumber',
|
||||
label: "Kundennr.",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "isCompany",
|
||||
label: "Typ",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
label: "Notizen",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "active",
|
||||
label: "Aktiv",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "address",
|
||||
label: "Adresse",
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
return useSearch(searchString.value, items.value)
|
||||
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,580 +0,0 @@
|
||||
<script setup>
|
||||
import axios from 'axios'
|
||||
import {sub} from 'date-fns'
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const accounts = ref([])
|
||||
|
||||
//accounts.value = dataStore.emailAccounts.map(i => { return { label: i.emailAddress, emailEngingeId: i.emailEngineId }})
|
||||
|
||||
const mailboxes = ref({})
|
||||
const messages = ref([])
|
||||
const selectedMailbox = ref("INBOX")
|
||||
const selectedAccount = ref(null)
|
||||
const selectedMessage = ref(null)
|
||||
const setup = async () => {
|
||||
accounts.value = dataStore.emailAccounts.map((i,index) => {
|
||||
let item = { label: i.emailAddress, emailEngineId: i.emailEngineId }
|
||||
if(index === 0) {
|
||||
item.defaultOpen = true
|
||||
}
|
||||
return item
|
||||
|
||||
})
|
||||
|
||||
for await (const account of accounts.value) {
|
||||
console.log(account.emailEngineId)
|
||||
const {data,error} = await axios.get(`http://157.90.231.142:3000/v1/account/${account.emailEngineId}/mailboxes`, {headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'}})
|
||||
console.log(data)
|
||||
console.log(error)
|
||||
mailboxes.value[account.emailEngineId] = data.mailboxes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const selectMailbox = async (account, mailbox) => {
|
||||
selectedMailbox.value = mailbox
|
||||
const {data,error} = await axios.get(`http://157.90.231.142:3000/v1/account/${account}/messages?path=${ mailbox.path}`, {headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'}})
|
||||
console.log(data)
|
||||
console.log(error)
|
||||
messages.value = data.messages
|
||||
selectedAccount.value = account
|
||||
}
|
||||
const messageHTML = ref(null)
|
||||
const messageText = ref(null)
|
||||
const selectMessage = async (account, message) => {
|
||||
console.log(message)
|
||||
selectedMessage.value = message
|
||||
const {data,error} = await axios.get(`http://157.90.231.142:3000/v1/account/${account}/text/${message.text.id}`, {headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'}})
|
||||
messageHTML.value = data.html
|
||||
messageText.value = data.plain
|
||||
}
|
||||
|
||||
const addFlags = async (account, message, flags) => {
|
||||
console.log(flags)
|
||||
const {data,error} = await axios({
|
||||
method: "PUT",
|
||||
url: `http://157.90.231.142:3000/v1/account/${account}/message/${message.id}`,
|
||||
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
|
||||
data: {
|
||||
flags: {
|
||||
add: flags
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log(data)
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
const removeFlags = async (account, message, flags) => {
|
||||
console.log(flags)
|
||||
const {data,error} = await axios({
|
||||
method: "PUT",
|
||||
url: `http://157.90.231.142:3000/v1/account/${account}/message/${message.id}`,
|
||||
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
|
||||
data: {
|
||||
flags: {
|
||||
delete: flags
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log(data)
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
const setSeen = async (seen,message) => {
|
||||
if(seen) {
|
||||
await addFlags(selectedAccount.value,message, ["\\Seen"])
|
||||
await selectMailbox(selectedAccount.value, selectedMailbox.value)
|
||||
} else {
|
||||
await removeFlags(selectedAccount.value,message, ["\\Seen"])
|
||||
await selectMailbox(selectedAccount.value, selectedMailbox.value)
|
||||
}
|
||||
}
|
||||
|
||||
const moveTo = async (destinationPath) => {
|
||||
const {data,error} = await axios({
|
||||
method: "PUT",
|
||||
url: `http://157.90.231.142:3000/v1/account/${selectedAccount.value}/message/${selectedMessage.value.id}/move`,
|
||||
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
|
||||
data: {
|
||||
path: destinationPath
|
||||
}
|
||||
})
|
||||
|
||||
console.log(data)
|
||||
console.log(error)
|
||||
selectedMessage.value = null
|
||||
messageHTML.value = null
|
||||
messageText.value = null
|
||||
selectMailbox(selectedAccount.value, selectedMailbox.value)
|
||||
}
|
||||
|
||||
const downloadAttachment = async (attachment) => {
|
||||
const {data,error} = await axios({
|
||||
method: "GET",
|
||||
url: `http://157.90.231.142:3000/v1/account/${selectedAccount.value}/attachment/${attachment.id}`,
|
||||
headers: { 'Authorization': 'Bearer dadb572465fba648590f31557f68028a750b47b278d87c1773e8fd09670eec59'},
|
||||
responseType: "blob"
|
||||
})
|
||||
|
||||
const downloadURL = URL.createObjectURL(new Blob([data]))
|
||||
const link = document.createElement('a')
|
||||
link.href = downloadURL
|
||||
link.setAttribute('download', attachment.filename)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.remove()
|
||||
console.log(data)
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
|
||||
setup()
|
||||
|
||||
const selectedTab = ref(0)
|
||||
|
||||
const mails = ref([{
|
||||
id: 1,
|
||||
from: {
|
||||
name: 'Alex Smith',
|
||||
email: 'alex.smith@example.com',
|
||||
avatar: {
|
||||
src: 'https://i.pravatar.cc/128?u=1'
|
||||
}
|
||||
},
|
||||
subject: 'Meeting Schedule',
|
||||
body: 'Hi there, just a quick reminder about our meeting scheduled for 10 AM tomorrow. We\'ll be discussing the new marketing strategies and I would really appreciate your input on the matter. Looking forward to a productive session.',
|
||||
date: new Date().toISOString()
|
||||
}, {
|
||||
id: 2,
|
||||
unread: true,
|
||||
from: {
|
||||
name: 'Jordan Brown',
|
||||
email: 'jordan.brown@example.com',
|
||||
avatar: {
|
||||
src: 'https://i.pravatar.cc/128?u=2'
|
||||
}
|
||||
},
|
||||
subject: 'Project Update',
|
||||
body: 'I wanted to provide you with the latest update on the project. We\'ve made significant progress on the development front and I\'ve attached a detailed report for your review. Please let me know your thoughts and any areas for improvement.',
|
||||
date: sub(new Date(), { minutes: 7 }).toISOString()
|
||||
}, {
|
||||
id: 3,
|
||||
unread: true,
|
||||
from: {
|
||||
name: 'Taylor Green',
|
||||
email: 'taylor.green@example.com',
|
||||
avatar: {
|
||||
src: 'https://i.pravatar.cc/128?u=3'
|
||||
}
|
||||
},
|
||||
subject: 'Lunch Plans',
|
||||
body: 'Hey! I was wondering if you would like to grab lunch this Friday. I know a great spot downtown that serves the best Mexican cuisine. It would be a great opportunity for us to catch up and discuss the upcoming team event.',
|
||||
date: sub(new Date(), { hours: 3 }).toISOString()
|
||||
}, {
|
||||
id: 4,
|
||||
from: {
|
||||
name: 'Morgan White',
|
||||
email: 'morgan.white@example.com',
|
||||
avatar: {
|
||||
src: 'https://i.pravatar.cc/128?u=4'
|
||||
}
|
||||
},
|
||||
subject: 'New Proposal',
|
||||
body: 'I\'ve attached the new proposal for our next project. It outlines all the objectives, timelines, and resource allocations. I\'m particularly excited about the innovative approach we\'re taking this time. Please have a look and let me know your thoughts.',
|
||||
date: sub(new Date(), { days: 1 }).toISOString()
|
||||
}, {
|
||||
id: 5,
|
||||
from: {
|
||||
name: 'Casey Gray',
|
||||
email: 'casey.gray@example.com'
|
||||
},
|
||||
subject: 'Travel Itinerary',
|
||||
body: 'Your travel itinerary for the upcoming business trip is ready. I\'ve included all flight details, hotel reservations, and meeting schedules. Please review and let me know if there are any changes you would like to make or any additional arrangements needed.',
|
||||
date: sub(new Date(), { days: 1 }).toISOString()
|
||||
}, {
|
||||
id: 6,
|
||||
from: {
|
||||
name: 'Jamie Johnson',
|
||||
email: 'jamie.johnson@example.com'
|
||||
},
|
||||
subject: 'Budget Report',
|
||||
body: 'I\'ve completed the budget report for this quarter. It includes a detailed analysis of our expenditures and revenue, along with projections for the next quarter. I believe there are some areas where we can optimize our spending. Let\'s discuss this in our next finance meeting.',
|
||||
date: sub(new Date(), { days: 2 }).toISOString()
|
||||
}, {
|
||||
id: 7,
|
||||
from: {
|
||||
name: 'Riley Davis',
|
||||
email: 'riley.davis@example.com',
|
||||
avatar: {
|
||||
src: 'https://i.pravatar.cc/128?u=7'
|
||||
}
|
||||
},
|
||||
subject: 'Training Session',
|
||||
body: 'Just a reminder about the training session scheduled for next week. We\'ll be covering new software tools that are crucial for our workflow. It\'s important that everyone attends as this will greatly enhance our team\'s efficiency. Please confirm your availability.',
|
||||
date: sub(new Date(), { days: 2 }).toISOString()
|
||||
}, {
|
||||
id: 8,
|
||||
unread: true,
|
||||
from: {
|
||||
name: 'Kelly Wilson',
|
||||
email: 'kelly.wilson@example.com',
|
||||
avatar: {
|
||||
src: 'https://i.pravatar.cc/128?u=8'
|
||||
}
|
||||
},
|
||||
subject: 'Happy Birthday!',
|
||||
body: 'Happy Birthday! Wishing you a fantastic day filled with joy and laughter. Your dedication and hard work throughout the year have been invaluable to our team. Enjoy your day to the fullest!',
|
||||
date: sub(new Date(), { days: 2 }).toISOString()
|
||||
}, {
|
||||
id: 9,
|
||||
from: {
|
||||
name: 'Drew Moore',
|
||||
email: 'drew.moore@example.com'
|
||||
},
|
||||
subject: 'Website Feedback',
|
||||
body: 'We are in the process of revamping our company website and I would greatly appreciate your feedback on the new design. Your perspective is always insightful and could help us enhance the user experience significantly. Please let me know a convenient time for you to discuss this.',
|
||||
date: sub(new Date(), { days: 5 }).toISOString()
|
||||
}, {
|
||||
id: 10,
|
||||
from: {
|
||||
name: 'Jordan Taylor',
|
||||
email: 'jordan.taylor@example.com'
|
||||
},
|
||||
subject: 'Gym Membership',
|
||||
body: 'This is a friendly reminder that your gym membership is due for renewal at the end of this month. We\'ve added several new classes and facilities that I think you\'ll really enjoy. Let me know if you would like a tour of the new facilities.',
|
||||
date: sub(new Date(), { days: 5 }).toISOString()
|
||||
}, {
|
||||
id: 11,
|
||||
unread: true,
|
||||
from: {
|
||||
name: 'Morgan Anderson',
|
||||
email: 'morgan.anderson@example.com'
|
||||
},
|
||||
subject: 'Insurance Policy',
|
||||
body: 'I\'m writing to inform you that your insurance policy details have been updated. The new document outlines the changes in coverage and premium rates. It\'s important to review these changes to ensure they meet your needs. Please don\'t hesitate to contact me if you have any questions.',
|
||||
date: sub(new Date(), { days: 12 }).toISOString()
|
||||
}, {
|
||||
id: 12,
|
||||
from: {
|
||||
name: 'Casey Thomas',
|
||||
email: 'casey.thomas@example.com'
|
||||
},
|
||||
subject: 'Book Club Meeting',
|
||||
body: 'I\'m excited to remind you about our next book club meeting scheduled for next Thursday. We\'ll be discussing \'The Great Gatsby,\' and I\'m looking forward to hearing everyone\'s perspectives. Also, we will be choosing our next book, so bring your suggestions!',
|
||||
date: sub(new Date(), { months: 1 }).toISOString()
|
||||
}, {
|
||||
id: 13,
|
||||
from: {
|
||||
name: 'Jamie Jackson',
|
||||
email: 'jamie.jackson@example.com'
|
||||
},
|
||||
subject: 'Recipe Exchange',
|
||||
body: 'Don\'t forget to send in your favorite recipe for our upcoming recipe exchange. It\'s a great opportunity to share and discover new and delicious meals. I\'m particularly excited to try out new dishes and add some variety to my cooking.',
|
||||
date: sub(new Date(), { months: 1 }).toISOString()
|
||||
}, {
|
||||
id: 14,
|
||||
from: {
|
||||
name: 'Riley White',
|
||||
email: 'riley.white@example.com'
|
||||
},
|
||||
subject: 'Yoga Class Schedule',
|
||||
body: 'The new schedule for yoga classes is now available. We\'ve added some new styles and adjusted the timings to accommodate more participants. I believe these classes are a great way to relieve stress and stay healthy. Hope to see you there!',
|
||||
date: sub(new Date(), { months: 1 }).toISOString()
|
||||
}, {
|
||||
id: 15,
|
||||
from: {
|
||||
name: 'Kelly Harris',
|
||||
email: 'kelly.harris@example.com'
|
||||
},
|
||||
subject: 'Book Launch Event',
|
||||
body: 'I\'m thrilled to invite you to my book launch event next month. It\'s been a journey writing this book and I\'m eager to share it with you. The event will include a reading session, Q&A, and a signing opportunity. Your support would mean a lot to me.',
|
||||
date: sub(new Date(), { months: 1 }).toISOString()
|
||||
}, {
|
||||
id: 16,
|
||||
from: {
|
||||
name: 'Drew Martin',
|
||||
email: 'drew.martin@example.com'
|
||||
},
|
||||
subject: 'Tech Conference',
|
||||
body: 'Join us at the upcoming tech conference where we will be discussing the latest trends and innovations in technology. This is a great opportunity to network with industry leaders and learn about cutting-edge developments. Your participation would greatly contribute to our team\'s knowledge and growth.',
|
||||
date: sub(new Date(), { months: 1, days: 4 }).toISOString()
|
||||
}, {
|
||||
id: 17,
|
||||
from: {
|
||||
name: 'Alex Thompson',
|
||||
email: 'alex.thompson@example.com'
|
||||
},
|
||||
subject: 'Art Exhibition',
|
||||
body: 'I wanted to invite you to check out the new art exhibition this weekend. It features some amazing contemporary artists and their latest works. It\'s a great opportunity to immerse yourself in the local art scene and get inspired. Let me know if you\'re interested in going together.',
|
||||
date: sub(new Date(), { months: 1, days: 15 }).toISOString()
|
||||
}, {
|
||||
id: 18,
|
||||
from: {
|
||||
name: 'Jordan Garcia',
|
||||
email: 'jordan.garcia@example.com'
|
||||
},
|
||||
subject: 'Networking Event',
|
||||
body: 'I\'m looking forward to seeing you at the networking event next week. It\'s a great chance to connect with professionals from various industries and expand our professional network. There will also be guest speakers discussing key business trends. Your presence would add great value to the discussions.',
|
||||
date: sub(new Date(), { months: 1, days: 18 }).toISOString()
|
||||
}, {
|
||||
id: 19,
|
||||
from: {
|
||||
name: 'Taylor Rodriguez',
|
||||
email: 'taylor.rodriguez@example.com'
|
||||
},
|
||||
subject: 'Volunteer Opportunity',
|
||||
body: 'We\'re looking for volunteers for the upcoming community event. It\'s a great opportunity to give back and make a positive impact. There are various roles available, so you can choose something that aligns with your interests and skills. Let me know if you\'re interested and I\'ll provide more details.',
|
||||
date: sub(new Date(), { months: 1, days: 25 }).toISOString()
|
||||
}, {
|
||||
id: 20,
|
||||
from: {
|
||||
name: 'Morgan Lopez',
|
||||
email: 'morgan.lopez@example.com'
|
||||
},
|
||||
subject: 'Car Service Reminder',
|
||||
body: 'Just a reminder that your car is due for service next week. Regular maintenance is important to ensure your vehicle\'s longevity and performance. I\'ve included the details of the service center and the recommended services in this email. Feel free to contact them directly to schedule an appointment.',
|
||||
date: sub(new Date(), { months: 2 }).toISOString()
|
||||
}])
|
||||
|
||||
const dropdownItems = [[{
|
||||
label: 'Mark as unread',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
}, {
|
||||
label: 'Mark as important',
|
||||
icon: 'i-heroicons-exclamation-circle'
|
||||
}], [{
|
||||
label: 'Star thread',
|
||||
icon: 'i-heroicons-star'
|
||||
}, {
|
||||
label: 'Mute thread',
|
||||
icon: 'i-heroicons-pause-circle'
|
||||
}]]
|
||||
|
||||
// Filter mails based on the selected tab
|
||||
const filteredMails = computed(() => {
|
||||
if (selectedTab.value === 1) {
|
||||
return mails.value.filter(mail => !!mail.unread)
|
||||
} else {
|
||||
return mails.value
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
const selectedMail = ref(null)
|
||||
|
||||
const isMailPanelOpen = computed({
|
||||
get () {
|
||||
return !!selectedMail.value
|
||||
},
|
||||
set (value) {
|
||||
if (!value) {
|
||||
selectedMail.value = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<!-- <div class="flex flex-row">
|
||||
<div id="mailboxlist">
|
||||
<UAccordion
|
||||
:items="accounts"
|
||||
>
|
||||
<template #default="{ item, index, open }">
|
||||
<UButton variant="soft" class="mt-3">
|
||||
|
||||
<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-for="mailbox in mailboxes[item.emailEngineId]"
|
||||
class="my-3"
|
||||
>
|
||||
<UButton
|
||||
@click="selectMailbox(item.emailEngineId, mailbox)"
|
||||
variant="outline"
|
||||
>
|
||||
<span v-if="mailbox.name === 'Trash'">Papierkorb</span>
|
||||
<span v-else-if="mailbox.name === 'INBOX'">Eingang</span>
|
||||
<span v-else-if="mailbox.name === 'Sent'">Gesendet</span>
|
||||
<span v-else-if="mailbox.name === 'Drafts'">Entwürfe</span>
|
||||
<span v-else-if="mailbox.name === 'spambucket'">Spam</span>
|
||||
<span v-else>{{mailbox.name}}</span>
|
||||
<UBadge v-if="mailbox.messages > 0">{{mailbox.messages}}</UBadge>
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</UAccordion>
|
||||
|
||||
</div>
|
||||
<UDivider orientation="vertical" class="maiLDivider"/>
|
||||
<div id="maillist">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
v-if="messages.length > 0"
|
||||
>
|
||||
<div
|
||||
:class="message === selectedMessage ? ['message','text-primary-500'] : ['message']"
|
||||
@click="selectMessage(selectedAccount, message),
|
||||
!message.flags.includes('\\Seen') ? setSeen(true,message) : null"
|
||||
>
|
||||
<UChip
|
||||
position="top-left"
|
||||
:show="!message.flags.includes('\\Seen')"
|
||||
>
|
||||
<h1>{{message.from.name ||message.from.address}}</h1>
|
||||
</UChip>
|
||||
|
||||
<h3>{{message.subject}}</h3>
|
||||
</div>
|
||||
<UDivider class="my-3"/>
|
||||
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
>
|
||||
Keine E-Mails in diesem Postfach
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<UDivider orientation="vertical" class="maiLDivider"/>
|
||||
|
||||
<div id="mailcontent" v-if="selectedMessage">
|
||||
<Toolbar>
|
||||
<!–<UButton
|
||||
@click="setup"
|
||||
>Setup</UButton>
|
||||
<UButton>+ Neu</UButton>
|
||||
<UButton>Sync</UButton>
|
||||
<UButton>Papierkorb</UButton>
|
||||
<UButton>Weiterleiten</UButton>
|
||||
<UButton>Antworten</UButton>–>
|
||||
<UButton
|
||||
@click="setSeen(false,selectedMessage)"
|
||||
>
|
||||
Als Ungelesen markieren
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="moveTo('INBOX.Trash')"
|
||||
icon="i-heroicons-trash"
|
||||
>
|
||||
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UAlert
|
||||
v-if="selectedMessage"
|
||||
:title="attachment.filename"
|
||||
v-for="attachment in selectedMessage.attachments"
|
||||
class="my-3"
|
||||
:actions="[{label: 'Download', click:() => {downloadAttachment(attachment)}}]"
|
||||
>
|
||||
|
||||
</UAlert>
|
||||
<iframe
|
||||
v-if="messageHTML"
|
||||
style="width: 100%; height: 100%"
|
||||
:srcdoc="messageHTML">
|
||||
</iframe>
|
||||
<pre
|
||||
class="text-wrap"
|
||||
v-else-if="messageText">
|
||||
{{messageText}}
|
||||
</pre>
|
||||
</div>
|
||||
</div>-->
|
||||
|
||||
<UDashboardPanel id="inbox" :width="400" :resizable="{ min: 300, max: 500 }">
|
||||
<UDashboardNavbar title="Inbox" >
|
||||
<template #right>
|
||||
<UTabs v-model="selectedTab" :items="[{label: 'Alle'},{label: 'Ungelesen'}]" :ui="{ wrapper: '', list: { height: 'h-9', tab: { height: 'h-7', size: 'text-[13px]' } } }" />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<!-- ~/components/inbox/InboxList.vue -->
|
||||
<InboxList v-model="selectedMail" :mails="filteredMails" />
|
||||
</UDashboardPanel>
|
||||
|
||||
<UDashboardPanel v-model="isMailPanelOpen" collapsible grow side="right">
|
||||
<template v-if="selectedMail">
|
||||
<UDashboardNavbar>
|
||||
<template #toggle>
|
||||
<UDashboardNavbarToggle icon="i-heroicons-x-mark" />
|
||||
|
||||
<UDivider orientation="vertical" class="mx-1.5 lg:hidden" />
|
||||
</template>
|
||||
|
||||
<template #left>
|
||||
<UTooltip text="Archive">
|
||||
<UButton icon="i-heroicons-archive-box" color="gray" variant="ghost" />
|
||||
</UTooltip>
|
||||
|
||||
<UTooltip text="Move to junk">
|
||||
<UButton icon="i-heroicons-archive-box-x-mark" color="gray" variant="ghost" />
|
||||
</UTooltip>
|
||||
|
||||
<!-- <UDivider orientation="vertical" class="mx-1.5" />-->
|
||||
|
||||
<!-- <UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<template #default="{ open }">
|
||||
<UTooltip text="Snooze" :prevent="open">
|
||||
<UButton icon="i-heroicons-clock" color="gray" variant="ghost" :class="[open && 'bg-gray-50 dark:bg-gray-800']" />
|
||||
</UTooltip>
|
||||
</template>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<DatePicker @close="close" />
|
||||
</template>
|
||||
</UPopover>-->
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<UTooltip text="Reply">
|
||||
<UButton icon="i-heroicons-arrow-uturn-left" color="gray" variant="ghost" />
|
||||
</UTooltip>
|
||||
|
||||
<UTooltip text="Forward">
|
||||
<UButton icon="i-heroicons-arrow-uturn-right" color="gray" variant="ghost" />
|
||||
</UTooltip>
|
||||
|
||||
<UDivider orientation="vertical" class="mx-1.5" />
|
||||
|
||||
<UDropdown :items="dropdownItems">
|
||||
<UButton icon="i-heroicons-ellipsis-vertical" color="gray" variant="ghost" />
|
||||
</UDropdown>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<!-- ~/components/inbox/InboxMail.vue -->
|
||||
<InboxMail :mail="selectedMail" />
|
||||
</template>
|
||||
<div v-else class="flex-1 hidden lg:flex items-center justify-center">
|
||||
<UIcon name="i-heroicons-inbox" class="w-32 h-32 text-gray-400 dark:text-gray-500" />
|
||||
</div>
|
||||
</UDashboardPanel>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,369 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
|
||||
import {BlobReader, BlobWriter, ZipWriter} from "@zip.js/zip.js";
|
||||
import {useSupabaseSelectDocuments} from "~/composables/useSupabase.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
const dataStore = useDataStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
const toast = useToast()
|
||||
|
||||
dataStore.fetchDocuments()
|
||||
|
||||
const uploadModalOpen = ref(false)
|
||||
const uploadInProgress = ref(false)
|
||||
const fileUploadFormData = ref({
|
||||
tags: ["Eingang"],
|
||||
path: "",
|
||||
tenant: dataStore.currentTenant
|
||||
})
|
||||
|
||||
|
||||
let tags = dataStore.getDocumentTags
|
||||
|
||||
const selectedTags = ref("Eingang")
|
||||
const documents = ref([])
|
||||
const folders = ref([])
|
||||
const selectedPath = ref("_")
|
||||
const loadingDocs = ref(false)
|
||||
const isDragTarget = ref(false)
|
||||
const setupPage = async () => {
|
||||
//documents.value = await useSupabaseSelectDocuments("*, project(id,name), customer(id,name), contract(id,name), vendor(id,name), plant(id,name), vehicle(id,licensePlate), product(id,name), profile(id,fullName) ")
|
||||
//documents.value = await useSupabaseSelectDocuments("*",null, selectedPath.value)
|
||||
//console.log(documents.value)
|
||||
|
||||
folders.value = dataStore.ownTenant.documentFolders
|
||||
|
||||
documents.value = await useSupabaseSelectDocuments("*",null,selectedPath.value)
|
||||
//await supabase.from("documents").select().eq("folderPath",selectedPath.value).eq("tenant",dataStore.currentTenant)
|
||||
|
||||
//console.log(await supabase.from("documents").select().eq("folderPath",selectedPath.value))
|
||||
|
||||
|
||||
const dropZone = document.getElementById("drop_zone")
|
||||
dropZone.ondragover = function (event) {
|
||||
console.log(event)
|
||||
isDragTarget.value = true
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
dropZone.ondragleave = function (event) {
|
||||
isDragTarget.value = false
|
||||
}
|
||||
|
||||
dropZone.ondrop = async function (event) {
|
||||
console.log("files dropped")
|
||||
event.preventDefault()
|
||||
|
||||
await uploadFiles(event.dataTransfer.files)
|
||||
isDragTarget.value = false
|
||||
setupPage()
|
||||
}
|
||||
|
||||
|
||||
loadingDocs.value = false
|
||||
|
||||
}
|
||||
setupPage()
|
||||
const currentFolders = computed(() => {
|
||||
if(folders.value.length > 0) {
|
||||
/*console.log(folders.value[0].path.split("/").filter(x => x.length > 0))
|
||||
console.log(selectedPath.value.split("/").filter(x => x.length > 0))*/
|
||||
|
||||
let tempFolders = folders.value.filter(i => (i.path.split("_").filter(x => x.length > 0) || []).length === selectedPath.value.split("_").filter(x => x.length > 0).length + 1)
|
||||
|
||||
tempFolders = tempFolders.filter(i => i.path.includes(selectedPath.value))
|
||||
|
||||
return tempFolders
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const breadcrumbLinks = computed(() => {
|
||||
return [{
|
||||
label: "Home",
|
||||
click: () => {
|
||||
changePath("_")
|
||||
},
|
||||
icon: "i-heroicons-folder"
|
||||
},...selectedPath.value.split("_").filter(x => x.length > 0).map((i,index) => {
|
||||
let re = new RegExp(".+?" + i )
|
||||
let path = selectedPath.value.match(re)[0]
|
||||
|
||||
return {
|
||||
label: folders.value.find(x => x.path === path).name ||path,
|
||||
click: () => {
|
||||
changePath(path)
|
||||
},
|
||||
icon: "i-heroicons-folder"
|
||||
}
|
||||
})]
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const filteredDocuments = computed(() => {
|
||||
|
||||
/*if(selectedTags.value !== "Archiviert") {
|
||||
return documents.value.filter(i => i.tags.find(t => selectedTags.value === t) && !i.tags.includes("Archiviert"))
|
||||
} else {
|
||||
return documents.value.filter(i => i.tags.find(t => selectedTags.value === t))
|
||||
}*/
|
||||
|
||||
return documents.value
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
const changePath = async (newPath) => {
|
||||
loadingDocs.value = true
|
||||
selectedPath.value = newPath
|
||||
setupPage()
|
||||
}
|
||||
|
||||
const uploadFiles = async (files) => {
|
||||
uploadInProgress.value = true;
|
||||
|
||||
if(files) {
|
||||
await dataStore.uploadFiles({tags: ["Ablage"],tenant: dataStore.currentTenant, folderPath: selectedPath.value}, files, true)
|
||||
|
||||
} else {
|
||||
await dataStore.uploadFiles(fileUploadFormData.value, document.getElementById("fileUploadInput").files, true)
|
||||
|
||||
}
|
||||
|
||||
|
||||
uploadModalOpen.value = false;
|
||||
uploadInProgress.value = false;
|
||||
}
|
||||
|
||||
const downloadSelected = async () => {
|
||||
const bucket = "files";
|
||||
|
||||
let files = []
|
||||
dataStore.documents.filter(doc => doc.selected).forEach(doc => files.push(doc.path))
|
||||
|
||||
console.log(files)
|
||||
|
||||
// If there are no files in the folder, throw an error
|
||||
if (!files || !files.length) {
|
||||
throw new Error("No files to download");
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
|
||||
// Download each file in the folder
|
||||
files.forEach((file) => {
|
||||
promises.push(
|
||||
supabase.storage.from(bucket).download(`${file}`)
|
||||
);
|
||||
});
|
||||
|
||||
// Wait for all the files to download
|
||||
const response = await Promise.allSettled(promises);
|
||||
|
||||
// Map the response to an array of objects containing the file name and blob
|
||||
const downloadedFiles = response.map((result, index) => {
|
||||
if (result.status === "fulfilled") {
|
||||
|
||||
console.log(files[index].split("/")[files[index].split("/").length -1])
|
||||
|
||||
return {
|
||||
name: files[index].split("/")[files[index].split("/").length -1],
|
||||
blob: result.value.data,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new zip file
|
||||
const zipFileWriter = new BlobWriter("application/zip");
|
||||
const zipWriter = new ZipWriter(zipFileWriter, { bufferedWrite: true });
|
||||
|
||||
// Add each file to the zip file
|
||||
downloadedFiles.forEach((downloadedFile) => {
|
||||
if (downloadedFile) {
|
||||
zipWriter.add(downloadedFile.name, new BlobReader(downloadedFile.blob));
|
||||
}
|
||||
});
|
||||
|
||||
// Download the zip file
|
||||
const url = URL.createObjectURL(await zipWriter.close());
|
||||
const link = document.createElement("a");
|
||||
|
||||
link.href = url;
|
||||
link.setAttribute("download", "documents.zip");
|
||||
|
||||
document.body.appendChild(link);
|
||||
|
||||
link.click();
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar
|
||||
title="Dokumente"
|
||||
>
|
||||
|
||||
</UDashboardNavbar>
|
||||
<!-- <UDashboardToolbar>
|
||||
<template #right>
|
||||
<UButton @click="uploadModalOpen = true">Hochladen</UButton>
|
||||
<!– <UButton
|
||||
@click="downloadSelected"
|
||||
:disabled="dataStore.documents.filter(doc => doc.selected).length === 0"
|
||||
>Herunterladen</UButton>–>
|
||||
</template>
|
||||
<!– <template #right>
|
||||
<USelectMenu
|
||||
:options="tags"
|
||||
v-model="selectedTags"
|
||||
class="w-40"
|
||||
>
|
||||
<template #label>
|
||||
{{selectedTags}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>–>
|
||||
|
||||
</UDashboardToolbar>-->
|
||||
<UDashboardToolbar>
|
||||
<template #left>
|
||||
<UBreadcrumb
|
||||
:links="breadcrumbLinks"
|
||||
/>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton @click="uploadModalOpen = true">Hochladen</UButton>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<div id="drop_zone" class="h-full scrollList">
|
||||
<UDashboardPanelContent v-if="!isDragTarget" >
|
||||
<div class="flex flex-row w-full flex-wrap" v-if="currentFolders.length > 0">
|
||||
<a
|
||||
class="w-1/6 folderIcon flex flex-col p-5 m-2"
|
||||
v-for="folder in currentFolders"
|
||||
@click="changePath(folder.path)"
|
||||
>
|
||||
|
||||
<UIcon
|
||||
name="i-heroicons-folder"
|
||||
class="w-20 h-20"
|
||||
/>
|
||||
<span class="text-center truncate">{{folder.name}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<UDivider class="my-5" v-if="currentFolders.length > 0">Dokumente</UDivider>
|
||||
|
||||
|
||||
<div v-if="!loadingDocs">
|
||||
<DocumentList
|
||||
v-if="filteredDocuments.length > 0"
|
||||
:documents="filteredDocuments"
|
||||
@selectDocument="(info) => console.log(info)"
|
||||
/>
|
||||
<UAlert
|
||||
v-else
|
||||
class="mt-5 w-1/2 mx-auto"
|
||||
icon="i-heroicons-light-bulb"
|
||||
title="Keine Dokumente vorhanden"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
/>
|
||||
</div>
|
||||
<UProgress
|
||||
animation="carousel"
|
||||
v-else
|
||||
class="w-2/3 my-5 mx-auto"
|
||||
/>
|
||||
|
||||
</UDashboardPanelContent>
|
||||
<UCard
|
||||
class=" m-5"
|
||||
v-else>
|
||||
<template #header>
|
||||
<p class="mx-auto">Dateien zum hochladen hierher ziehen</p>
|
||||
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<USlideover
|
||||
v-model="uploadModalOpen"
|
||||
>
|
||||
|
||||
<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>
|
||||
Datei Hochladen
|
||||
</template>
|
||||
|
||||
<div class="h-full">
|
||||
<UFormGroup
|
||||
label="Datei:"
|
||||
>
|
||||
<UInput
|
||||
type="file"
|
||||
id="fileUploadInput"
|
||||
multiple
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Tags:"
|
||||
class="mt-3"
|
||||
>
|
||||
<USelectMenu
|
||||
multiple
|
||||
searchable
|
||||
searchable-placeholder="Suchen..."
|
||||
:options="tags"
|
||||
v-model="fileUploadFormData.tags"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
v-if="!uploadInProgress"
|
||||
class="mt-3"
|
||||
@click="uploadFiles"
|
||||
>Hochladen</UButton>
|
||||
<UProgress
|
||||
v-else
|
||||
animation="carousel"
|
||||
/>
|
||||
</template>
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
.folderIcon {
|
||||
border: 1px solid lightgrey;
|
||||
border-radius: 10px;
|
||||
color: dimgrey;
|
||||
}
|
||||
|
||||
.folderIcon:hover {
|
||||
border: 1px solid #69c350;
|
||||
color: #69c350;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,11 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tiptap/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
|
||||
const selectedTab = ref(0)
|
||||
@@ -12,7 +13,7 @@ const accountData = ref(null)
|
||||
const availableAccounts = ref(null)
|
||||
const selectedAccount = ref(null)
|
||||
const setupPage = async () => {
|
||||
availableAccounts.value = (await supabase.from("emailAccounts").select("*").contains("profiles",[dataStore.activeProfile.id])).data
|
||||
availableAccounts.value = (await supabase.from("emailAccounts").select("*").contains("profiles",[profileStore.activeProfile.id])).data
|
||||
console.log(availableAccounts.value)
|
||||
|
||||
if(availableAccounts.value.length > 0) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const supabase = useSupabaseClient()
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
@@ -29,7 +30,7 @@ const setupPage = async () => {
|
||||
} else {
|
||||
emailData.value.account = emailAccounts.value[0].id
|
||||
|
||||
preloadedContent.value = `<p></p><p></p><p></p>${dataStore.activeProfile.emailSignature}`
|
||||
preloadedContent.value = `<p></p><p></p><p></p>${profileStore.activeProfile.emailSignature || ""}`
|
||||
|
||||
//Check Query
|
||||
if(route.query.to) emailData.value.to = route.query.to
|
||||
@@ -39,18 +40,28 @@ const setupPage = async () => {
|
||||
|
||||
|
||||
if(route.query.loadDocuments) {
|
||||
//console.log(JSON.parse(route.query.loadDocuments))
|
||||
const {data,error} = await supabase.from("documents").select('*, createdDocument(id,documentNumber,title,contact(email))').in('id',JSON.parse(route.query.loadDocuments))
|
||||
console.log(JSON.parse(route.query.loadDocuments))
|
||||
const data = await useFiles().selectSomeDocuments(JSON.parse(route.query.loadDocuments))
|
||||
console.log(data)
|
||||
|
||||
|
||||
|
||||
if(error) console.log(error)
|
||||
if(data) loadedDocuments.value = data
|
||||
|
||||
//console.log(loadedDocuments.value)
|
||||
|
||||
if(loadedDocuments.value.length > 0) {
|
||||
emailData.value.subject = loadedDocuments.value[0].createdDocument.title
|
||||
emailData.value.to = loadedDocuments.value[0].createdDocument.contact ? loadedDocuments.value[0].createdDocument.contact.email : ""
|
||||
console.log(loadedDocuments.value[0])
|
||||
emailData.value.subject = loadedDocuments.value[0].createddocument.title
|
||||
|
||||
if(loadedDocuments.value[0].createddocument.contact && loadedDocuments.value[0].createddocument.contact.email) {
|
||||
console.log("Contact")
|
||||
emailData.value.to = loadedDocuments.value[0].createddocument.contact.email
|
||||
} else if(loadedDocuments.value[0].createddocument.customer && loadedDocuments.value[0].createddocument.customer.infoData.email) {
|
||||
|
||||
|
||||
emailData.value.to = loadedDocuments.value[0].createddocument.customer.infoData.email
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -121,7 +132,7 @@ const sendEmail = async () => {
|
||||
|
||||
for await (const doc of loadedDocuments.value) {
|
||||
|
||||
const {data,error} = await supabase.storage.from("files").download(doc.path)
|
||||
const {data,error} = await supabase.storage.from("filesdev").download(doc.path)
|
||||
|
||||
body.attachments.push({
|
||||
filename: doc.path.split("/")[doc.path.split("/").length -1],
|
||||
@@ -191,30 +202,39 @@ const sendEmail = async () => {
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UDivider class="my-3"/>
|
||||
<UInput
|
||||
class="w-full my-1"
|
||||
placeholder="Empfänger"
|
||||
variant="ghost"
|
||||
v-model="emailData.to"
|
||||
/>
|
||||
<UInput
|
||||
class="w-full my-1"
|
||||
placeholder="Kopie"
|
||||
variant="ghost"
|
||||
v-model="emailData.cc"
|
||||
/>
|
||||
<UInput
|
||||
class="w-full my-1"
|
||||
placeholder="Blindkopie"
|
||||
variant="ghost"
|
||||
v-model="emailData.bcc"
|
||||
/>
|
||||
<UInput
|
||||
placeholder="Betreff"
|
||||
class="w-full my-1"
|
||||
variant="ghost"
|
||||
v-model="emailData.subject"
|
||||
/>
|
||||
<UFormGroup
|
||||
label="Empfänger"
|
||||
>
|
||||
<UInput
|
||||
class="w-full my-1"
|
||||
v-model="emailData.to"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Kopie"
|
||||
>
|
||||
<UInput
|
||||
class="w-full my-1"
|
||||
v-model="emailData.cc"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Blindkopie"
|
||||
>
|
||||
<UInput
|
||||
class="w-full my-1"
|
||||
placeholder=""
|
||||
v-model="emailData.bcc"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Betreff"
|
||||
>
|
||||
<UInput
|
||||
class="w-full my-1"
|
||||
v-model="emailData.subject"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
</UDashboardToolbar>
|
||||
@@ -227,7 +247,7 @@ const sendEmail = async () => {
|
||||
class="mx-auto w-10 h-10"
|
||||
/>
|
||||
<span class="text-center text-2xl">Anhänge hochladen</span>-->
|
||||
<input
|
||||
<UInput
|
||||
id="inputAttachments"
|
||||
type="file"
|
||||
multiple
|
||||
@@ -242,13 +262,14 @@ const sendEmail = async () => {
|
||||
class="list-disc"
|
||||
v-for="doc in loadedDocuments"
|
||||
>
|
||||
<span v-if="doc.createdDocument">Dokument - {{doc.createdDocument.documentNumber}}</span>
|
||||
<span v-if="doc.createddocument">Dokument - {{doc.createddocument.documentNumber}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<Tiptap
|
||||
class="mt-3"
|
||||
@updateContent="contentChanged"
|
||||
:preloadedContent="preloadedContent"
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ definePageMeta({
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
const toast = useToast()
|
||||
@@ -16,22 +17,29 @@ const toast = useToast()
|
||||
|
||||
const timeTypes = dataStore.getTimeTypes
|
||||
const timeInfo = ref({
|
||||
user: "",
|
||||
start: "",
|
||||
end: null,
|
||||
profile: "",
|
||||
startDate: "",
|
||||
endDate: null,
|
||||
notes: null,
|
||||
projectId: null,
|
||||
project: null,
|
||||
type: null
|
||||
})
|
||||
|
||||
const filterUser = ref(user.value.id || "")
|
||||
const filterUser = ref(profileStore.activeProfile.id || "")
|
||||
|
||||
const times = ref([])
|
||||
|
||||
const setup = async () => {
|
||||
times.value = await useSupabaseSelect("times","*, profile(*)")
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
|
||||
let times = dataStore.times
|
||||
//let times = times.value
|
||||
|
||||
if(dataStore.hasRight('viewTimes')) {
|
||||
/*if(dataStore.hasRight('viewTimes')) {
|
||||
if(filterUser.value !== "") {
|
||||
times = times.filter(i => i.user === filterUser.value)
|
||||
}
|
||||
@@ -39,9 +47,9 @@ const filteredRows = computed(() => {
|
||||
times = times.filter(i => i.user === user.value.id)
|
||||
} else {
|
||||
times = []
|
||||
}
|
||||
}*/
|
||||
|
||||
return times
|
||||
return times.value
|
||||
|
||||
})
|
||||
|
||||
@@ -62,42 +70,34 @@ const columns = [
|
||||
{
|
||||
key:"state",
|
||||
label: "Status",
|
||||
sortable:true
|
||||
},
|
||||
{
|
||||
key: "user",
|
||||
label: "Benutzer",
|
||||
sortable:true
|
||||
},
|
||||
{
|
||||
key:"start",
|
||||
key:"startDate",
|
||||
label:"Start",
|
||||
sortable:true
|
||||
},
|
||||
{
|
||||
key: "endDate",
|
||||
label: "Ende",
|
||||
},
|
||||
{
|
||||
key:"type",
|
||||
label:"Typ",
|
||||
sortable:true
|
||||
},
|
||||
{
|
||||
key: "end",
|
||||
label: "Ende",
|
||||
sortable:true
|
||||
},
|
||||
{
|
||||
key: "duration",
|
||||
label: "Dauer",
|
||||
sortable:true
|
||||
},
|
||||
{
|
||||
key: "projectId",
|
||||
key: "project",
|
||||
label: "Projekt",
|
||||
sortable:true
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
label: "Notizen",
|
||||
sortable:true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -109,9 +109,9 @@ const configTimeMode = ref("create")
|
||||
|
||||
const startTime = async () => {
|
||||
console.log("started")
|
||||
timeInfo.value.user = user.value.id
|
||||
timeInfo.value.profile = profileStore.activeProfile.id
|
||||
timeInfo.value.start = new Date().toISOString()
|
||||
timeInfo.value.tenant = dataStore.currentTenant
|
||||
timeInfo.value.tenant = profileStore.currentTenant
|
||||
|
||||
const {data,error} = await supabase
|
||||
.from("times")
|
||||
@@ -123,7 +123,7 @@ const startTime = async () => {
|
||||
} else if(data) {
|
||||
//timeInfo.value = data[0]
|
||||
await dataStore.fetchTimes()
|
||||
runningTimeInfo.value = dataStore.times.find(time => time.user === user.value.id && !time.end)
|
||||
runningTimeInfo.value = dataStore.times.find(time => time.profile === profileStore.activeProfile.id && !time.end)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -154,15 +154,15 @@ const stopStartedTime = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
if(dataStore.times.find(time => time.user == user.value.id && !time.end)) {
|
||||
runningTimeInfo.value = dataStore.times.find(time => time.user == user.value.id && !time.end)
|
||||
if(times.value.find(time => time.profile == profileStore.activeProfile.id && !time.end)) {
|
||||
runningTimeInfo.value = times.value.find(time => time.profile == profileStore.activeProfile.id && !time.end)
|
||||
}
|
||||
|
||||
|
||||
const createTime = async () => {
|
||||
const {data,error} = await supabase
|
||||
.from("times")
|
||||
.insert({...itemInfo.value, tenant: dataStore.currentTenant})
|
||||
.insert({...itemInfo.value, tenant: profileStore.currentTenant})
|
||||
.select()
|
||||
|
||||
if(error) {
|
||||
@@ -240,14 +240,13 @@ const setState = async (newState) => {
|
||||
Erstellen
|
||||
</UButton>
|
||||
<USelectMenu
|
||||
v-if="dataStore.hasRight('viewTimes')"
|
||||
:options="dataStore.profiles"
|
||||
:options="profileStore.profiles"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
v-model="filterUser"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getProfileById(filterUser) ? dataStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
|
||||
{{profileStore.getProfileById(filterUser) ? profileStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
@@ -339,14 +338,14 @@ const setState = async (newState) => {
|
||||
label="Benutzer:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.profiles"
|
||||
:options="profileStore.profiles"
|
||||
v-model="itemInfo.user"
|
||||
option-attribute="fullName"
|
||||
value-attribute="id"
|
||||
:disabled="(configTimeMode === 'create' ? false : itemInfo.state !== 'Entwurf') || (!dataStore.hasRight('createTime') || !dataStore.hasRight('createOwnTime'))"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.profiles.find(profile => profile.id === itemInfo.user) ? dataStore.profiles.find(profile => profile.id === itemInfo.user).fullName : "Benutzer auswählen"}}
|
||||
{{profileStore.profiles.find(profile => profile.id === itemInfo.user) ? profileStore.profiles.find(profile => profile.id === itemInfo.user).fullName : "Benutzer auswählen"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
@@ -444,7 +443,7 @@ const setState = async (newState) => {
|
||||
>{{row.state}}</span>
|
||||
</template>
|
||||
<template #user-data="{row}">
|
||||
{{dataStore.profiles.find(profile => profile.id === row.user) ? dataStore.profiles.find(profile => profile.id === row.user).fullName : row.user }}
|
||||
{{profileStore.profiles.find(profile => profile.id === row.user) ? profileStore.profiles.find(profile => profile.id === row.user).fullName : row.user }}
|
||||
</template>
|
||||
|
||||
<template #start-data="{row}">
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const dataStore = useDataStore()
|
||||
|
||||
const mode = ref(route.params.mode || "show")
|
||||
|
||||
|
||||
const itemInfo = ref({
|
||||
resources: []
|
||||
})
|
||||
const oldItemInfo = ref({})
|
||||
|
||||
const resourceToAdd = ref(dataStore.activeProfile.id)
|
||||
/*const mapResources = () => {
|
||||
console.log(itemInfo.value.resources)
|
||||
itemInfo.value.resources.map(resource => {
|
||||
console.log(resource)
|
||||
return {
|
||||
id: resource.id,
|
||||
type: resource.type
|
||||
}
|
||||
})
|
||||
}*/
|
||||
|
||||
|
||||
const setupPage = async () => {
|
||||
if(mode.value === "show"){
|
||||
itemInfo.value = await useSupabaseSelectSingle("events",route.params.id,"*, project(id,name)")
|
||||
} else if(mode.value === "edit" && route.params.id) {
|
||||
itemInfo.value = await useSupabaseSelectSingle("events",route.params.id,"*")
|
||||
|
||||
}
|
||||
|
||||
if(route.query.start) itemInfo.value.start = new Date(route.query.start.replace(" ", "+"))
|
||||
if(route.query.end) itemInfo.value.end = new Date(route.query.end.replace(" ", "+"))
|
||||
if(route.query.resources) {
|
||||
itemInfo.value.resources = JSON.parse(route.query.resources).map(resource => {
|
||||
return dataStore.getResourcesList.find(i => i.id === resource)
|
||||
})
|
||||
}
|
||||
if(route.query.project) itemInfo.value.project = route.query.project
|
||||
if(itemInfo.value.id) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="mode === 'show' ? 'Termin: ' + itemInfo.title : 'Neuen Termin erstellen'">
|
||||
<template #right>
|
||||
<UButton
|
||||
color="rose"
|
||||
@click="router.push(route.params.id ? `/events/show/${route.params.id}` : `/events`)"
|
||||
v-if="mode === 'edit'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
@click="dataStore.createNewItem('events',itemInfo)"
|
||||
v-if="mode === 'edit' && !route.params.id"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="dataStore.updateItem('events',itemInfo,oldItemInfo)"
|
||||
v-else-if="mode === 'edit' && route.params.id"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click="router.push(`/events/edit/${itemInfo.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<!-- <UDashboardToolbar>
|
||||
|
||||
</UDashboardToolbar>-->
|
||||
<UTabs
|
||||
v-if="mode === 'show'"
|
||||
:items="[{label:'Informationen'},{label:'Logbuch'}]"
|
||||
class="p-5"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<UCard class="mt-5">
|
||||
<div v-if="item.label === 'Informationen'">
|
||||
<div class="truncate" >
|
||||
<p>Titel: {{itemInfo.title ? itemInfo.title : ''}}</p>
|
||||
<p>Typ: {{itemInfo.type? itemInfo.type : ''}}</p>
|
||||
<p>Start: {{dayjs(itemInfo.start).format("DD.MM.YY HH:mm")}}</p>
|
||||
<p>Ende: {{dayjs(itemInfo.end).format("DD.MM.YY HH:mm")}}</p>
|
||||
<p>Projekt: {{itemInfo.project ? itemInfo.project.name : ''}}</p>
|
||||
<p>Link: <a v-if="itemInfo.link" :href="itemInfo.link">{{itemInfo.link }}</a></p>
|
||||
<p>Resources: {{itemInfo.resources.map((x) => `${x.type} - ${x.title}`).join(",")}}</p>
|
||||
<p>Notizen: {{itemInfo.notes ? itemInfo.notes : ''}}</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-if="item.label === 'Logbuch'">
|
||||
<HistoryDisplay
|
||||
type="event"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
/>
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
</UTabs>
|
||||
<UForm class="p-5" v-if="mode === 'edit'">
|
||||
<UFormGroup
|
||||
label="Titel:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.title"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Projekt:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.project"
|
||||
:options="dataStore.projects"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
searchable-placeholder="Suche..."
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getProjectById(itemInfo.project) ? dataStore.getProjectById(itemInfo.project).name : "Kein Projekt ausgewählt"}}
|
||||
</template>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Typ:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.type"
|
||||
:options="dataStore.getEventTypes"
|
||||
option-attribute="label"
|
||||
value-attribute="label"
|
||||
>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Link:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.link"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Notizen:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.notes"
|
||||
rows="3"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Start:"
|
||||
>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
variant="outline"
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.start ? dayjs(itemInfo.start).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
v-model="itemInfo.start"
|
||||
mode="dateTime"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Ende:"
|
||||
>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
variant="outline"
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.end ? dayjs(itemInfo.end).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker
|
||||
v-model="itemInfo.end"
|
||||
mode="dateTime"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Resource:"
|
||||
class="w-full"
|
||||
>
|
||||
<InputGroup class="w-full">
|
||||
<USelectMenu
|
||||
v-model="resourceToAdd"
|
||||
:options="dataStore.getResourcesList"
|
||||
option-attribute="title"
|
||||
value-attribute="id"
|
||||
class="w-full"
|
||||
></USelectMenu>
|
||||
<UButton
|
||||
@click="itemInfo.resources.push(dataStore.getResourcesList.find(i => i.id === resourceToAdd))"
|
||||
:disabled="itemInfo.resources.find(i => i.id === resourceToAdd)"
|
||||
>
|
||||
+ Hinzufügen
|
||||
</UButton>
|
||||
</InputGroup>
|
||||
</UFormGroup>
|
||||
<UTable
|
||||
v-if="itemInfo.resources.length > 0"
|
||||
:rows="itemInfo.resources"
|
||||
:columns="[
|
||||
{
|
||||
key:'type',
|
||||
label: 'Type'
|
||||
}, {
|
||||
key: 'title',
|
||||
label: 'Name'
|
||||
}, {
|
||||
key: 'remove'
|
||||
}
|
||||
]"
|
||||
>
|
||||
<template #remove-data="{row}">
|
||||
<UButton
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@click="itemInfo.resources = itemInfo.resources.filter(i => i.id !== row.id)"
|
||||
>
|
||||
Entfernen
|
||||
</UButton>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,127 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Termine" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/events/edit`)">+ Termin</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/events/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Termine anzuzeigen' }"
|
||||
>
|
||||
<template #start-data="{row}">
|
||||
{{dayjs(row.start).format("DD.MM.YY HH:mm")}}
|
||||
</template>
|
||||
<template #end-data="{row}">
|
||||
{{dayjs(row.end).format("DD.MM.YY HH:mm")}}
|
||||
</template>
|
||||
<template #project-data="{row}">
|
||||
{{row.project ? dataStore.getProjectById(row.project).name: ""}}
|
||||
</template>
|
||||
<template #resources-data="{row}">
|
||||
{{row.resources ? row.resources.map(i => i.title).join(", ") : ""}}
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/events/[mode]")
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: 'title',
|
||||
label: "Titel:",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "start",
|
||||
label: "Start",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "end",
|
||||
label: "Ende"
|
||||
},
|
||||
{
|
||||
key: "resources",
|
||||
label: "Resourcen"
|
||||
},
|
||||
{
|
||||
key: "project",
|
||||
label: "Projekt"
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
|
||||
const searchString = ref('')
|
||||
const filteredRows = computed(() => {
|
||||
if(!searchString.value) {
|
||||
return dataStore.events
|
||||
}
|
||||
|
||||
return dataStore.events.filter(item => {
|
||||
return Object.values(item).some((value) => {
|
||||
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
550
pages/files/index.vue
Normal file
550
pages/files/index.vue
Normal file
@@ -0,0 +1,550 @@
|
||||
<script setup>
|
||||
|
||||
|
||||
import {BlobReader, BlobWriter, ZipWriter} from "@zip.js/zip.js";
|
||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
import DocumentDisplayModal from "~/components/DocumentDisplayModal.vue";
|
||||
import DocumentUploadModal from "~/components/DocumentUploadModal.vue";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
/*'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},*/
|
||||
'+': () => {
|
||||
//Hochladen
|
||||
uploadModalOpen.value = true
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
let entry = renderedFileList.value[selectedFileIndex.value]
|
||||
|
||||
if(entry.type === "file") {
|
||||
showFile(entry.id)
|
||||
console.log(entry)
|
||||
} else {
|
||||
changeFolder(currentFolders.value.find(i => i.id === entry.id))
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedFileIndex.value < renderedFileList.value.length - 1) {
|
||||
selectedFileIndex.value += 1
|
||||
} else {
|
||||
selectedFileIndex.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedFileIndex.value === 0) {
|
||||
selectedFileIndex.value = renderedFileList.value.length - 1
|
||||
} else {
|
||||
selectedFileIndex.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const slideover = useSlideover()
|
||||
const modal = useModal()
|
||||
|
||||
dataStore.fetchDocuments()
|
||||
|
||||
const uploadModalOpen = ref(false)
|
||||
const createFolderModalOpen = ref(false)
|
||||
const uploadInProgress = ref(false)
|
||||
const fileUploadFormData = ref({
|
||||
tags: ["Eingang"],
|
||||
path: "",
|
||||
tenant: profileStore.currentTenant,
|
||||
folder: null
|
||||
})
|
||||
|
||||
const files = useFiles()
|
||||
|
||||
let tags = dataStore.getDocumentTags
|
||||
|
||||
const displayMode = ref("list")
|
||||
const displayModes = ref([{label: 'Liste',key:'list', icon: 'i-heroicons-list-bullet'},{label: 'Kacheln',key:'rectangles', icon: 'i-heroicons-squares-2x2'}])
|
||||
|
||||
|
||||
|
||||
const documents = ref([])
|
||||
const folders = ref([])
|
||||
const filetags = ref([])
|
||||
|
||||
const currentFolder = ref(null)
|
||||
|
||||
const loadingDocs = ref(false)
|
||||
const isDragTarget = ref(false)
|
||||
const setupPage = async () => {
|
||||
folders.value = await useSupabaseSelect("folders")
|
||||
|
||||
documents.value = await files.selectDocuments()
|
||||
|
||||
filetags.value = await useSupabaseSelect("filetags")
|
||||
|
||||
if(route.query) {
|
||||
if(route.query.folder) {
|
||||
currentFolder.value = await useSupabaseSelectSingle("folders", route.query.folder)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const dropZone = document.getElementById("drop_zone")
|
||||
dropZone.ondragover = function (event) {
|
||||
console.log(event)
|
||||
isDragTarget.value = true
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
dropZone.ondragleave = function (event) {
|
||||
isDragTarget.value = false
|
||||
}
|
||||
|
||||
dropZone.ondrop = async function (event) {
|
||||
console.log("files dropped")
|
||||
event.preventDefault()
|
||||
|
||||
await uploadFiles(event.dataTransfer.files)
|
||||
isDragTarget.value = false
|
||||
setupPage()
|
||||
}
|
||||
|
||||
|
||||
loadingDocs.value = false
|
||||
|
||||
}
|
||||
setupPage()
|
||||
const currentFolders = computed(() => {
|
||||
if(folders.value.length > 0) {
|
||||
|
||||
let tempFolders = folders.value.filter(i => currentFolder.value ? i.parent === currentFolder.value.id : !i.parent)
|
||||
|
||||
return tempFolders
|
||||
} else return []
|
||||
|
||||
})
|
||||
|
||||
const breadcrumbLinks = computed(() => {
|
||||
|
||||
if(currentFolder.value) {
|
||||
let parents = []
|
||||
|
||||
const addParent = (parent) => {
|
||||
parents.push(parent)
|
||||
if(parent.parent) {
|
||||
addParent(folders.value.find(i => i.id === parent.parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(currentFolder.value.parent) {
|
||||
addParent(folders.value.find(i => i.id === currentFolder.value.parent))
|
||||
}
|
||||
|
||||
return [{
|
||||
label: "Home",
|
||||
click: () => {
|
||||
changeFolder(null)
|
||||
},
|
||||
icon: "i-heroicons-folder"
|
||||
},
|
||||
...parents.map(i => {
|
||||
return {
|
||||
label: folders.value.find(x => x.id === i.id).name,
|
||||
click: () => {
|
||||
changeFolder(i)
|
||||
},
|
||||
icon: "i-heroicons-folder"
|
||||
}
|
||||
}).reverse(),
|
||||
{
|
||||
label: currentFolder.value.name,
|
||||
click: () => {
|
||||
changeFolder(currentFolder.value)
|
||||
},
|
||||
icon: "i-heroicons-folder"
|
||||
}]
|
||||
|
||||
} else {
|
||||
return [{
|
||||
label: "Home",
|
||||
click: () => {
|
||||
changeFolder(null)
|
||||
},
|
||||
icon: "i-heroicons-folder"
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
const filteredDocuments = computed(() => {
|
||||
|
||||
return documents.value.filter(i => currentFolder.value ? i.folder === currentFolder.value.id : !i.folder)
|
||||
|
||||
})
|
||||
|
||||
const changeFolder = async (newFolder) => {
|
||||
loadingDocs.value = true
|
||||
currentFolder.value = newFolder
|
||||
|
||||
if(newFolder) {
|
||||
fileUploadFormData.value.folder = newFolder.id
|
||||
await router.push(`/files?folder=${newFolder.id}`)
|
||||
} else {
|
||||
fileUploadFormData.value.folder = null
|
||||
await router.push(`/files`)
|
||||
}
|
||||
|
||||
setupPage()
|
||||
}
|
||||
|
||||
const createFolderData = ref({})
|
||||
const createFolder = async () => {
|
||||
const {data,error} = await supabase
|
||||
.from("folders")
|
||||
.insert({
|
||||
tenant: profileStore.currentTenant,
|
||||
parent: currentFolder.value ? currentFolder.value.id : undefined,
|
||||
name: createFolderData.value.name,
|
||||
|
||||
})
|
||||
|
||||
createFolderModalOpen.value = false
|
||||
|
||||
setupPage()
|
||||
|
||||
}
|
||||
|
||||
const uploadFiles = async (files) => {
|
||||
uploadInProgress.value = true;
|
||||
|
||||
if(files) {
|
||||
//await dataStore.uploadFiles({tags: ["Ablage"],tenant: profileStore.currentTenant,folder: currentFolder.value.id}, files, true)
|
||||
await dataStore.uploadFiles({tags: ["Ablage"],tenant: profileStore.currentTenant}, files, true)
|
||||
|
||||
} else {
|
||||
await dataStore.uploadFiles(fileUploadFormData.value, document.getElementById("fileUploadInput").files, true)
|
||||
|
||||
}
|
||||
|
||||
|
||||
uploadModalOpen.value = false;
|
||||
uploadInProgress.value = false;
|
||||
}
|
||||
|
||||
const downloadSelected = async () => {
|
||||
const bucket = "files";
|
||||
|
||||
let files = []
|
||||
dataStore.documents.filter(doc => doc.selected).forEach(doc => files.push(doc.path))
|
||||
|
||||
console.log(files)
|
||||
|
||||
// If there are no files in the folder, throw an error
|
||||
if (!files || !files.length) {
|
||||
throw new Error("No files to download");
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
|
||||
// Download each file in the folder
|
||||
files.forEach((file) => {
|
||||
promises.push(
|
||||
supabase.storage.from(bucket).download(`${file}`)
|
||||
);
|
||||
});
|
||||
|
||||
// Wait for all the files to download
|
||||
const response = await Promise.allSettled(promises);
|
||||
|
||||
// Map the response to an array of objects containing the file name and blob
|
||||
const downloadedFiles = response.map((result, index) => {
|
||||
if (result.status === "fulfilled") {
|
||||
|
||||
console.log(files[index].split("/")[files[index].split("/").length -1])
|
||||
|
||||
return {
|
||||
name: files[index].split("/")[files[index].split("/").length -1],
|
||||
blob: result.value.data,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new zip file
|
||||
const zipFileWriter = new BlobWriter("application/zip");
|
||||
const zipWriter = new ZipWriter(zipFileWriter, { bufferedWrite: true });
|
||||
|
||||
// Add each file to the zip file
|
||||
downloadedFiles.forEach((downloadedFile) => {
|
||||
if (downloadedFile) {
|
||||
zipWriter.add(downloadedFile.name, new BlobReader(downloadedFile.blob));
|
||||
}
|
||||
});
|
||||
|
||||
// Download the zip file
|
||||
const url = URL.createObjectURL(await zipWriter.close());
|
||||
const link = document.createElement("a");
|
||||
|
||||
link.href = url;
|
||||
link.setAttribute("download", "documents.zip");
|
||||
|
||||
document.body.appendChild(link);
|
||||
|
||||
link.click();
|
||||
}
|
||||
|
||||
const renderedFileList = computed(() => {
|
||||
let files = filteredDocuments.value.map(i => {
|
||||
return {
|
||||
label: i.path.split("/")[i.path.split("/").length -1],
|
||||
id: i.id,
|
||||
type: "file"
|
||||
}
|
||||
})
|
||||
|
||||
let folders = currentFolders.value.map(i => {
|
||||
return {
|
||||
label: i.name,
|
||||
id: i.id,
|
||||
type: "folder"
|
||||
}
|
||||
})
|
||||
|
||||
return [...folders,...files]
|
||||
|
||||
})
|
||||
const selectedFileIndex = ref(0)
|
||||
|
||||
const showFile = (fileId) => {
|
||||
console.log(fileId)
|
||||
modal.open(DocumentDisplayModal,{
|
||||
documentData: documents.value.find(i => i.id === fileId),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<UDashboardNavbar
|
||||
title="Dateien"
|
||||
>
|
||||
|
||||
</UDashboardNavbar>
|
||||
<UDashboardToolbar>
|
||||
<template #left>
|
||||
<UBreadcrumb
|
||||
:links="breadcrumbLinks"
|
||||
/>
|
||||
</template>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
:options="displayModes"
|
||||
value-attribute="key"
|
||||
option-attribute="label"
|
||||
v-model="displayMode"
|
||||
:ui-menu="{ width: 'min-w-max'}"
|
||||
>
|
||||
<template #label>
|
||||
<UIcon class="w-5 h-5" :name="displayModes.find(i => i.key === displayMode).icon"/>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
|
||||
<UButton @click="modal.open(DocumentUploadModal,{fileData: {folder: currentFolder.id}})">+ Hochladen</UButton>
|
||||
<UButton
|
||||
@click="createFolderModalOpen = true"
|
||||
variant="outline"
|
||||
>+ Ordner</UButton>
|
||||
<UModal
|
||||
v-model="createFolderModalOpen"
|
||||
>
|
||||
<UCard>
|
||||
<template #header>
|
||||
Ordner Erstellen
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<UFormGroup
|
||||
label="Ordner erstellen"
|
||||
>
|
||||
<UInput
|
||||
v-model="createFolderData.name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
@click="createFolder"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
</template>
|
||||
</UCard>
|
||||
|
||||
</UModal>
|
||||
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
<div id="drop_zone" class="h-full scrollList">
|
||||
<UDashboardPanelContent v-if="!isDragTarget" >
|
||||
<div v-if="displayMode === 'list'">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="font-bold">Name</td>
|
||||
<td class="font-bold">Erstellt am</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="(entry,index) in renderedFileList">
|
||||
<td>
|
||||
<UIcon class="mr-1" :name="entry.type === 'folder' ? 'i-heroicons-folder' : 'i-heroicons-document'"/>
|
||||
<a
|
||||
style="cursor: pointer"
|
||||
:class="[...index === selectedFileIndex ? ['text-primary', 'text-xl'] : ['dark:text-white','text-black','text-xl']]"
|
||||
@click="entry.type === 'folder' ? changeFolder(currentFolders.find(i => i.id === entry.id)) : showFile(entry.id)"
|
||||
>{{entry.label}}</a>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="entry.type === 'file'" class="text-xl">{{dayjs(documents.find(i => i.id === entry.id).created_at).format("DD.MM.YY HH:mm")}}</span>
|
||||
<span v-if="entry.type === 'folder'" class="text-xl">{{dayjs(currentFolders.find(i => i.id === entry.id).created_at).format("DD.MM.YY HH:mm")}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else-if="displayMode === 'rectangles'">
|
||||
<div class="flex flex-row w-full flex-wrap" v-if="currentFolders.length > 0">
|
||||
<a
|
||||
class="w-1/6 folderIcon flex flex-col p-5 m-2"
|
||||
v-for="folder in currentFolders"
|
||||
@click="changeFolder(folder)"
|
||||
>
|
||||
|
||||
<UIcon
|
||||
name="i-heroicons-folder"
|
||||
class="w-20 h-20"
|
||||
/>
|
||||
<span class="text-center truncate">{{folder.name}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<UDivider class="my-5" v-if="currentFolder">{{currentFolder.name}}</UDivider>
|
||||
<UDivider class="my-5" v-else>Ablage</UDivider>
|
||||
|
||||
<div v-if="!loadingDocs">
|
||||
<DocumentList
|
||||
v-if="filteredDocuments.length > 0"
|
||||
:documents="filteredDocuments"
|
||||
@selectDocument="(info) => console.log(info)"
|
||||
/>
|
||||
<UAlert
|
||||
v-else
|
||||
class="mt-5 w-1/2 mx-auto"
|
||||
icon="i-heroicons-light-bulb"
|
||||
title="Keine Dokumente vorhanden"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
/>
|
||||
</div>
|
||||
<UProgress
|
||||
animation="carousel"
|
||||
v-else
|
||||
class="w-2/3 my-5 mx-auto"
|
||||
/>
|
||||
</div>
|
||||
</UDashboardPanelContent>
|
||||
<UCard
|
||||
class=" m-5"
|
||||
v-else>
|
||||
<template #header>
|
||||
<p class="mx-auto">Dateien zum hochladen hierher ziehen</p>
|
||||
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<USlideover
|
||||
v-model="uploadModalOpen"
|
||||
>
|
||||
|
||||
<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>
|
||||
Datei Hochladen
|
||||
</template>
|
||||
|
||||
<div class="h-full">
|
||||
<UFormGroup
|
||||
label="Datei:"
|
||||
>
|
||||
<UInput
|
||||
type="file"
|
||||
id="fileUploadInput"
|
||||
multiple
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Tags:"
|
||||
class="mt-3"
|
||||
>
|
||||
<USelectMenu
|
||||
multiple
|
||||
searchable
|
||||
searchable-placeholder="Suchen..."
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
:options="filetags"
|
||||
v-model="fileUploadFormData.tags"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<UButton
|
||||
v-if="!uploadInProgress"
|
||||
class="mt-3"
|
||||
@click="uploadFiles"
|
||||
>Hochladen</UButton>
|
||||
<UProgress
|
||||
v-else
|
||||
animation="carousel"
|
||||
/>
|
||||
</template>
|
||||
</UCard>
|
||||
</USlideover>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.folderIcon {
|
||||
border: 1px solid lightgrey;
|
||||
border-radius: 10px;
|
||||
color: dimgrey;
|
||||
}
|
||||
|
||||
.folderIcon:hover {
|
||||
border: 1px solid #69c350;
|
||||
color: #69c350;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
</style>
|
||||
20
pages/historyitems/index.vue
Normal file
20
pages/historyitems/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar>
|
||||
<template #center>
|
||||
<h1
|
||||
:class="['text-xl','font-medium']"
|
||||
>Zentrales Logbuch</h1>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<HistoryDisplay/>
|
||||
</UDashboardPanelContent>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -8,21 +8,22 @@ definePageMeta({
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
|
||||
const {vendors} = storeToRefs(useDataStore())
|
||||
const {fetchVendorInvoices} = useDataStore()
|
||||
const availableDocuments = ref([])
|
||||
const setup = async () => {
|
||||
let filetype = (await supabase.from("filetags").select().eq("tenant",profileStore.currentTenant).eq("incomingDocumentType","invoices").single()).data.id
|
||||
console.log(filetype)
|
||||
let ids = (await supabase.from("files").select("id").eq("tenant",profileStore.currentTenant).eq("type", filetype)).data.map(i => i.id)
|
||||
availableDocuments.value = await useFiles().selectSomeDocuments(ids)
|
||||
|
||||
const availableDocuments = computed(() => {
|
||||
}
|
||||
|
||||
//console.log(dataStore.documents.filter(i => i.tags.includes('Eingangsrechnung') && !dataStore.incominginvoices.find(x => x.document === i.id)))
|
||||
//console.log(dataStore.documents.filter(i => i.tags.includes('Eingangsrechnung')).length)
|
||||
|
||||
return dataStore.documents.filter(i => i.tags.includes('Eingangsrechnung') && !i.tags.includes("Archiviert") && !dataStore.incominginvoices.find(x => x.document === i.id))
|
||||
})
|
||||
setup()
|
||||
|
||||
|
||||
//let currentDocument = ref(null)
|
||||
@@ -56,7 +57,7 @@ const itemInfo = ref({
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
if((mode.value === "show" || mode.value === "edit" ) && route.params.id){
|
||||
itemInfo.value = await dataStore.getIncomingInvoiceById(Number(route.params.id))
|
||||
itemInfo.value = await useSupabaseSelectSingle("incominginvoices",route.params.id)
|
||||
//currentDocument.value = await dataStore.getDocumentById(currentVendorInvoice.value.document)
|
||||
}
|
||||
console.log(itemInfo.value)
|
||||
|
||||
479
pages/incomingInvoices/create.vue
Normal file
479
pages/incomingInvoices/create.vue
Normal file
@@ -0,0 +1,479 @@
|
||||
<script setup>
|
||||
import InputGroup from "~/components/InputGroup.vue";
|
||||
import dayjs from "dayjs";
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
|
||||
const itemInfo = ref({
|
||||
vendor: 0,
|
||||
expense: true,
|
||||
reference: "",
|
||||
date: null,
|
||||
dueDate: null,
|
||||
paymentType: "Überweisung",
|
||||
description: "",
|
||||
state: "Entwurf",
|
||||
accounts: [
|
||||
{
|
||||
account: null,
|
||||
amountNet: null,
|
||||
amountTax: null,
|
||||
taxType: "19",
|
||||
costCentre: null
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const availableDocuments = ref([])
|
||||
const setup = async () => {
|
||||
let filetype = (await supabase.from("filetags").select().eq("tenant",profileStore.currentTenant).eq("incomingDocumentType","invoices").single()).data.id
|
||||
console.log(filetype)
|
||||
let ids = (await supabase.from("files").select("id").eq("tenant",profileStore.currentTenant).eq("type", filetype).is("incominginvoice",null)).data.map(i => i.id)
|
||||
availableDocuments.value = await useFiles().selectSomeDocuments(ids)
|
||||
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
const useNetMode = ref(false)
|
||||
|
||||
const loadedFile = ref(null)
|
||||
const loadFile = async (id) => {
|
||||
console.log(id)
|
||||
loadedFile.value = await useFiles().selectDocument(id)
|
||||
console.log(loadedFile.value)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const changeNetMode = (mode) => {
|
||||
useNetMode.value = mode
|
||||
|
||||
itemInfo.value.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const taxOptions = ref([
|
||||
{
|
||||
label: "19% USt",
|
||||
percentage: 19,
|
||||
key: "19"
|
||||
},{
|
||||
label: "7% USt",
|
||||
percentage: 7,
|
||||
key: "7"
|
||||
},{
|
||||
label: "Innergemeintschaftlicher Erwerb 19%",
|
||||
percentage: 0,
|
||||
key: "19I"
|
||||
},{
|
||||
label: "Innergemeintschaftlicher Erwerb 7%",
|
||||
percentage: 0,
|
||||
key: "7I"
|
||||
},{
|
||||
label: "§13b UStG",
|
||||
percentage: 0,
|
||||
key: "13B"
|
||||
},{
|
||||
label: "Keine USt",
|
||||
percentage: 0,
|
||||
key: "null"
|
||||
},
|
||||
])
|
||||
|
||||
|
||||
const totalCalculated = computed(() => {
|
||||
let totalNet = 0
|
||||
let totalAmount19Tax = 0
|
||||
let totalAmount7Tax = 0
|
||||
let totalAmount0Tax = 0
|
||||
let totalGross = 0
|
||||
|
||||
itemInfo.value.accounts.forEach(account => {
|
||||
if(account.amountNet) totalNet += account.amountNet
|
||||
|
||||
if(account.taxType === "19" && account.amountTax) {
|
||||
totalAmount19Tax += account.amountTax
|
||||
}
|
||||
})
|
||||
|
||||
totalGross = Number(totalNet + totalAmount19Tax)
|
||||
|
||||
return {
|
||||
totalNet,
|
||||
totalAmount19Tax,
|
||||
totalGross
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const createIncomingInvoice = async () => {
|
||||
const data = await dataStore.createNewItem('incominginvoices',itemInfo.value)
|
||||
|
||||
console.log(data)
|
||||
|
||||
const {error} = await supabase.from("files").update({incominginvoice: data[0].id}).eq("id",loadedFile.value.id)
|
||||
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="'Eingangsbeleg erstellen'">
|
||||
<template #right>
|
||||
<UButton
|
||||
@click="createIncomingInvoice"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardPanelContent>
|
||||
<div v-if="!loadedFile">
|
||||
<DocumentList
|
||||
:documents="availableDocuments"
|
||||
:return-document-id="true"
|
||||
@selectDocument="(documentId) => loadFile(documentId)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="flex justify-between mt-5"
|
||||
>
|
||||
<object
|
||||
v-if="loadedFile"
|
||||
:data="loadedFile.url + '#toolbar=0&navpanes=0&scrollbar=0&statusbar=0&messages=0&scrollbar=0'"
|
||||
type="application/pdf"
|
||||
class="mx-5 documentPreview w-full"
|
||||
/>
|
||||
<div class="w-3/5 mx-5">
|
||||
|
||||
<div v-if="mode === 'show'">
|
||||
|
||||
<div class="truncate mb-5">
|
||||
<p>Status: {{itemInfo.state}}</p>
|
||||
<p>Datum: {{dayjs(itemInfo.date).format('DD.MM.YYYY')}}</p>
|
||||
<p>Fälligkeitsdatum: {{dayjs(itemInfo.dueDate).format('DD.MM.YYYY')}}</p>
|
||||
<p>Lieferant: <nuxt-link :to="`/vendors/show/${itemInfo.vendor}`">{{dataStore.getVendorById(itemInfo.vendor).name}}</nuxt-link></p>
|
||||
<p>Bezahlt: {{itemInfo.paid}}</p>
|
||||
<p>Beschreibung: {{itemInfo.description}}</p>
|
||||
|
||||
<!-- TODO: Buchungszeilen darstellen -->
|
||||
</div>
|
||||
|
||||
<HistoryDisplay
|
||||
type="incomingInvoice"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
:render-headline="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else class=" scrollContainer">
|
||||
<InputGroup class="mb-3">
|
||||
<UButton
|
||||
:variant="itemInfo.expense ? 'solid' : 'outline'"
|
||||
@click="itemInfo.expense = true"
|
||||
>
|
||||
Ausgabe
|
||||
</UButton>
|
||||
<UButton
|
||||
:variant="!itemInfo.expense ? 'solid' : 'outline'"
|
||||
@click="itemInfo.expense = false"
|
||||
>
|
||||
Einnahme
|
||||
</UButton>
|
||||
</InputGroup>
|
||||
|
||||
<UFormGroup label="Lieferant:" required>
|
||||
<InputGroup>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.vendor"
|
||||
:options="dataStore.vendors"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['name','vendorNumber']"
|
||||
class="flex-auto"
|
||||
searchable-placeholder="Suche..."
|
||||
>
|
||||
<template #option="{option}">
|
||||
{{option.vendorNumber}} - {{option.name}}
|
||||
</template>
|
||||
<template #label>
|
||||
{{dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor) ? dataStore.vendors.find(vendor => vendor.id === itemInfo.vendor).name : 'Lieferant auswählen'}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
<UButton
|
||||
@click="router.push('/vendors/create')"
|
||||
>
|
||||
+ Lieferant
|
||||
</UButton>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
class="mt-3"
|
||||
label="Rechnungsreferenz:"
|
||||
required
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.reference"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<InputGroup class="mt-3" gap="2">
|
||||
<UFormGroup label="Rechnungsdatum:" required>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="itemInfo.date ? dayjs(itemInfo.date).format('DD.MM.YYYY') : 'Datum auswählen'" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.date" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Fälligkeitsdatum:" required>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton icon="i-heroicons-calendar-days-20-solid" :label="itemInfo.dueDate ? dayjs(itemInfo.dueDate).format('DD.MM.YYYY') : 'Datum auswählen'" />
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.dueDate" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
</InputGroup>
|
||||
|
||||
|
||||
|
||||
<UFormGroup label="Zahlart:" required>
|
||||
<USelectMenu
|
||||
:options="['Einzug','Kreditkarte','Überweisung','Sonstiges']"
|
||||
v-model="itemInfo.paymentType"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Beschreibung:" required>
|
||||
<UTextarea
|
||||
v-model="itemInfo.description"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<InputGroup class="my-3">
|
||||
<UButton
|
||||
:variant="!useNetMode ? 'solid' : 'outline'"
|
||||
@click="changeNetMode(false)"
|
||||
>
|
||||
Brutto
|
||||
</UButton>
|
||||
<UButton
|
||||
:variant="useNetMode ? 'solid' : 'outline'"
|
||||
@click="changeNetMode(true)"
|
||||
>
|
||||
Netto
|
||||
</UButton>
|
||||
|
||||
|
||||
|
||||
<!-- Brutto
|
||||
<UToggle
|
||||
v-model="useNetMode"
|
||||
@update:model-value="itemInfo.accounts = [{account: null,amountNet: null,amountTax: null,taxType: '19'}]"
|
||||
/>
|
||||
Netto-->
|
||||
</InputGroup>
|
||||
|
||||
<table v-if="itemInfo.accounts.length > 1" class="w-full">
|
||||
<tr>
|
||||
<td>Gesamt exkl. Steuer: </td>
|
||||
<td class="text-right">{{totalCalculated.totalNet.toFixed(2).replace(".",",")}} €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>19% Steuer: </td>
|
||||
<td class="text-right">{{totalCalculated.totalAmount19Tax.toFixed(2).replace(".",",")}} €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gesamt inkl. Steuer: </td>
|
||||
<td class="text-right">{{totalCalculated.totalGross.toFixed(2).replace(".",",")}} €</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div
|
||||
class="my-3"
|
||||
v-for="(item,index) in itemInfo.accounts"
|
||||
>
|
||||
|
||||
<UFormGroup
|
||||
label="Kategorie"
|
||||
class=" mb-3"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.accounts"
|
||||
option-attribute="label"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['label']"
|
||||
searchable-placeholder="Suche..."
|
||||
v-model="item.account"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.accounts.find(account => account.id === item.account) ? dataStore.accounts.find(account => account.id === item.account).label : "Keine Kategorie ausgewählt" }}
|
||||
</template>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Kostenstelle"
|
||||
class=" mb-3"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.getCostCentresComposed"
|
||||
option-attribute="label"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['label']"
|
||||
searchable-placeholder="Suche..."
|
||||
v-model="item.costCentre"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.getCostCentresComposed.find(account => account.id === item.costCentre) ? dataStore.getCostCentresComposed.find(account => account.id === item.costCentre).label : "Keine Kostenstelle ausgewählt" }}
|
||||
</template>
|
||||
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
|
||||
|
||||
|
||||
<InputGroup>
|
||||
<UFormGroup
|
||||
label="Umsatzsteuer"
|
||||
class="w-32"
|
||||
:help="`Betrag: ${item.amountTax ? String(item.amountTax).replace('.',',') : '0,00'} €`"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="taxOptions"
|
||||
v-model="item.taxType"
|
||||
value-attribute="key"
|
||||
option-attribute="label"
|
||||
@change="item.amountTax = Number(((item.amountNet ? item.amountNet : 0) * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2))"
|
||||
>
|
||||
<template #label>
|
||||
<span class="truncate">{{taxOptions.find(i => i.key === item.taxType) ? taxOptions.find(i => i.key === item.taxType).label : ""}}</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
v-if="useNetMode"
|
||||
label="Gesamtbetrag exkl. Steuer in EUR"
|
||||
class="flex-auto truncate"
|
||||
:help="item.taxType !== null ? `Betrag inkl. Steuern: ${String(Number(item.amountNet + item.amountTax).toFixed(2)).replace('.',',')} €` : 'Zuerst Steuertyp festlegen' "
|
||||
|
||||
>
|
||||
<UInput
|
||||
type="number"
|
||||
step="0.01"
|
||||
v-model="item.amountNet"
|
||||
:disabled="item.taxType === null"
|
||||
@keyup="item.amountTax = Number((item.amountNet * (Number(taxOptions.find(i => i.key === item.taxType).percentage)/100)).toFixed(2))"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
</template>
|
||||
</UInput>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
v-else
|
||||
label="Gesamtbetrag inkl. Steuer in EUR"
|
||||
class="flex-auto"
|
||||
:help="item.taxType !== null ? `Betrag exkl. Steuern: ${item.amountNet ? String(item.amountNet.toFixed(2)).replace('.',',') : '0,00'} €` : 'Zuerst Steuertyp festlegen' "
|
||||
|
||||
>
|
||||
<UInput
|
||||
type="number"
|
||||
step="0.01"
|
||||
:disabled="item.taxType === null"
|
||||
v-model="item.amountGross"
|
||||
@keyup="item.amountNet = Number((item.amountGross / (1 + Number(item.taxType)/100)).toFixed(2)),
|
||||
item.amountTax = Number((item.amountGross - item.amountNet).toFixed(2))"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
</UFormGroup>
|
||||
|
||||
|
||||
</InputGroup>
|
||||
|
||||
|
||||
<UButton
|
||||
class="mt-3"
|
||||
@click="itemInfo.accounts = [...itemInfo.accounts.slice(0,index+1),{account:null, amountNet: null, amountTax:null, taxType: '19'} , ...itemInfo.accounts.slice(index+1)]"
|
||||
>
|
||||
Position hinzufügen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="index !== 0"
|
||||
class="mt-3"
|
||||
variant="ghost"
|
||||
color="rose"
|
||||
@click="itemInfo.accounts = itemInfo.accounts.filter((account,itemIndex) => itemIndex !== index)"
|
||||
>
|
||||
Position entfernen
|
||||
</UButton>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UDashboardPanelContent>
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.documentPreview {
|
||||
aspect-ratio: 1 / 1.414;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.scrollContainer {
|
||||
overflow-y: scroll;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
height: 75vh;
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.scrollContainer::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.lineItemRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
@@ -133,7 +133,7 @@ const isPaid = (item) => {
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/incomingInvoices/edit`)">+ Beleg</UButton>
|
||||
<UButton @click="router.push(`/incomingInvoices/create`)">+ Beleg</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UDashboardToolbar>
|
||||
|
||||
@@ -15,48 +15,28 @@
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<UDashboardCard
|
||||
class="mt-3"
|
||||
title="Anwesenheiten"
|
||||
v-if="dataStore.getStartedWorkingTimes().length > 0"
|
||||
>
|
||||
<p v-for="time in dataStore.getStartedWorkingTimes()"><UIcon name="i-heroicons-check"/>{{dataStore.getProfileById(time.profile).fullName}}</p>
|
||||
<display-present-profiles/>
|
||||
</UDashboardCard>
|
||||
|
||||
<!--TODO: Fix Card Table overflowing <UDashboardCard
|
||||
title="Offene Aufgaben"
|
||||
v-if="dataStore.getOpenTasksCount > 0"
|
||||
class="w-1/2 h-1/2"
|
||||
>
|
||||
<UTable
|
||||
:rows="dataStore.tasks.filter(i => i.categorie !== 'Erledigt' && (i.profile === dataStore.activeProfile.id ||!i.profile))"
|
||||
@select="(row) => router.push(`/tasks/show/${row.id}`)"
|
||||
:columns="[
|
||||
{
|
||||
key: 'categorie',
|
||||
label: 'Kategorie'
|
||||
},{
|
||||
key: 'name',
|
||||
label: 'Name'
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #categorie-data="{row}">
|
||||
<span v-if="row.categorie === 'Dringend'" class="text-rose-500">{{row.categorie}}</span>
|
||||
<span v-else-if="row.categorie === 'In Bearbeitung'" class="text-primary-500">{{row.categorie}}</span>
|
||||
<span v-else>{{row.categorie}}</span>
|
||||
</template>
|
||||
</UTable>
|
||||
</UDashboardCard>-->
|
||||
|
||||
<UDashboardCard
|
||||
:ui="{ body: { padding: '!pb-3 !px-5' }}"
|
||||
class="mt-3"
|
||||
>
|
||||
<display-income-and-expenditure/>
|
||||
</UDashboardCard>
|
||||
<UDashboardCard
|
||||
class="w-1/3 mt-3"
|
||||
:ui="{ body: { padding: '!py-5 !px-5' }}"
|
||||
>
|
||||
<display-open-balances/>
|
||||
</UDashboardCard>
|
||||
<UDashboardCard
|
||||
class="w-1/3 mt-3"
|
||||
>
|
||||
<display-running-time/>
|
||||
</UDashboardCard>
|
||||
</UDashboardPanelContent>
|
||||
</UDashboardPanel>
|
||||
</UDashboardPage>
|
||||
@@ -64,11 +44,14 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
import DisplayPresentProfiles from "~/components/noAutoLoad/displayPresentProfiles.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ definePageMeta({
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const profileStore = useProfileStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const router = useRouter()
|
||||
const mode = ref("incoming")
|
||||
@@ -41,8 +42,8 @@ const createMovement = async () => {
|
||||
spaceId: inventoryChangeData.value.destinationSpaceId,
|
||||
projectId: inventoryChangeData.value.destinationProjectId,
|
||||
quantity: inventoryChangeData.value.quantity,
|
||||
profileId: dataStore.activeProfile.id,
|
||||
tenant: dataStore.currentTenant
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
|
||||
movements.push(movement)
|
||||
@@ -61,8 +62,8 @@ const createMovement = async () => {
|
||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
||||
projectId: inventoryChangeData.value.sourceProjectId,
|
||||
quantity: inventoryChangeData.value.quantity * -1,
|
||||
profileId: dataStore.activeProfile.id,
|
||||
tenant: dataStore.currentTenant
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
|
||||
movements.push(movement)
|
||||
@@ -72,16 +73,16 @@ const createMovement = async () => {
|
||||
spaceId: inventoryChangeData.value.sourceSpaceId,
|
||||
projectId: inventoryChangeData.value.sourceProjectId,
|
||||
quantity: inventoryChangeData.value.quantity * -1,
|
||||
profileId: dataStore.activeProfile.id,
|
||||
tenant: dataStore.currentTenant
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
let inMovement = {
|
||||
productId: inventoryChangeData.value.productId,
|
||||
spaceId: inventoryChangeData.value.destinationSpaceId,
|
||||
projectId: inventoryChangeData.value.destinationProjectId,
|
||||
quantity: inventoryChangeData.value.quantity,
|
||||
profileId: dataStore.activeProfile.id,
|
||||
tenant: dataStore.currentTenant
|
||||
profileId: profileStore.activeProfile.id,
|
||||
tenant: profileStore.currentTenant
|
||||
}
|
||||
|
||||
movements.push(outMovement)
|
||||
|
||||
@@ -1,382 +0,0 @@
|
||||
<script setup>
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import dayjs from "dayjs";
|
||||
import {useSupabaseSelectSingle} from "~/composables/useSupabase.js";
|
||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
||||
import DocumentList from "~/components/DocumentList.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
name: null,
|
||||
description: null,
|
||||
quantity: 0
|
||||
})
|
||||
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
if(mode.value === "show"){
|
||||
itemInfo.value = await useSupabaseSelectSingle("inventoryitems", route.params.id, "*, vendor(*), checks(*)")
|
||||
} else if(mode.value === "edit") {
|
||||
itemInfo.value = await useSupabaseSelectSingle("inventoryitems", route.params.id, "*")
|
||||
}
|
||||
}
|
||||
|
||||
const cancelEditorCreate = () => {
|
||||
if(itemInfo.value) {
|
||||
router.push(`/inventoryitems/show/${itemInfo.value.id}`)
|
||||
} else {
|
||||
router.push(`/inventoryitems`)
|
||||
}
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Inventartikel erstellen' : 'Inventartikel bearbeiten')">
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/inventoryitems`)"
|
||||
>
|
||||
Inventar
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
:class="['text-xl','font-medium']"
|
||||
>{{itemInfo.id ? `Inventarartikel: ${itemInfo.name}` : (mode === 'create' ? 'Inventarartikel erstellen' : 'Inventarartikel bearbeiten')}}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@confirmed="dataStore.updateItem('inventoryitems',{...itemInfo, archived: true})"
|
||||
v-if="mode === 'edit'"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie den Inventarartikel {{itemInfo.name}} wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('inventoryitems',itemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('inventoryitems',itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'create'"
|
||||
class="ml-2"
|
||||
@click="dataStore.createNewItem('inventoryitems',itemInfo);
|
||||
itemInfo = {
|
||||
name: null,
|
||||
description: null,
|
||||
quantity: 0
|
||||
}"
|
||||
>
|
||||
Erstellen + Neu
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="cancelEditorCreate"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click=" router.push(`/inventoryitems/edit/${itemInfo.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UTabs
|
||||
:items="[{label: 'Informationen'},{label: 'Dokumente'},{label: 'Überprüfungen'}]"
|
||||
v-if="itemInfo && mode === 'show'"
|
||||
class="p-5"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.label === 'Informationen'" class="flex-row flex mt-5">
|
||||
<div class="w-1/2 mr-5">
|
||||
<UCard>
|
||||
<UAlert
|
||||
v-if="itemInfo.archived"
|
||||
color="rose"
|
||||
variant="outline"
|
||||
title="Objekt archiviert"
|
||||
icon="i-heroicons-light-bulb"
|
||||
class="mb-5"
|
||||
/>
|
||||
<table class="w-full">
|
||||
<tr>
|
||||
<td>Name: </td>
|
||||
<td>{{itemInfo.name}}</td>
|
||||
</tr>
|
||||
<tr v-if="itemInfo.currentSpace">
|
||||
<td>Lagerplatz: </td>
|
||||
<td>{{dataStore.getSpaceById(itemInfo.currentSpace).spaceNumber}} - {{dataStore.getSpaceById(itemInfo.currentSpace).description}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Seriennummer:</td>
|
||||
<td>{{itemInfo.serialNumber}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Menge:</td>
|
||||
<td>{{itemInfo.quantity > 0 ? itemInfo.quantity : 'Einzelarktikel'}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Artikelnummer:</td>
|
||||
<td>{{itemInfo.articleNumber}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hersteller:</td>
|
||||
<td>{{itemInfo.manufacturer}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Herstellernummer:</td>
|
||||
<td>{{itemInfo.manufacturerNumber}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lieferant:</td>
|
||||
<td>{{itemInfo.vendor ? itemInfo.vendor.name : ''}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kaufdatum:</td>
|
||||
<td>{{itemInfo.purchaseDate ? dayjs(itemInfo.purchaseDate).format("DD.MM.YYYY") : ''}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wert bei Kauf:</td>
|
||||
<td>{{itemInfo.purchasePrice ? itemInfo.purchasePrice.toFixed(2).replace(".",",") + " €" : ''}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aktueller Wert:</td>
|
||||
<td>{{itemInfo.currentValue ? itemInfo.currentValue.toFixed(2).replace(".",",") + " €" : ''}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Beschreibung:</td>
|
||||
<td>{{itemInfo.description}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</UCard>
|
||||
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UCard>
|
||||
<HistoryDisplay
|
||||
type="inventoryitem"
|
||||
v-if="itemInfo"
|
||||
render-headline
|
||||
:element-id="itemInfo.id"
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Dokumente'">
|
||||
<UCard>
|
||||
<DocumentUpload
|
||||
type="inventoryitem"
|
||||
:element-id="itemInfo.id"
|
||||
/>
|
||||
|
||||
<DocumentList :documents="dataStore.getDocumentsByProfileId(itemInfo.id)"/>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Überprüfungen'">
|
||||
<UCard>
|
||||
<UTable
|
||||
:rows="itemInfo.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>
|
||||
</template>
|
||||
</UTabs>
|
||||
<UForm
|
||||
v-else-if="mode == 'edit' || mode == 'create'"
|
||||
class="p-5"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<div class="w-1/2 mr-5">
|
||||
<UDivider>
|
||||
Allgemeines
|
||||
</UDivider>
|
||||
<UFormGroup
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Artikelnummer:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.articleNumber"
|
||||
placeholder="Leer lassen für automatische generierte Nummer"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Lagerplatz:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.spaces"
|
||||
v-model="itemInfo.currentSpace"
|
||||
value-attribute="id"
|
||||
>
|
||||
<template #option="{option}">
|
||||
<span class="truncate">{{option.spaceNumber}} - {{option.description}}</span>
|
||||
</template>
|
||||
<template #label>
|
||||
<span v-if="itemInfo.currentSpace">{{dataStore.getSpaceById(itemInfo.currentSpace).spaceNumber }} - {{dataStore.getSpaceById(itemInfo.currentSpace).description}}</span>
|
||||
<span v-else>Kein Lagerplatz ausgewählt</span>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Seriennummer:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.serialNumber"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Menge:"
|
||||
:help="itemInfo.serialNumber ? 'Menge deaktiviert durch Eingabe der Seriennummer' : 'Für Einzelartikel Menge gleich 0'"
|
||||
>
|
||||
<UInput
|
||||
type="number"
|
||||
v-model="itemInfo.quantity"
|
||||
:disabled="itemInfo.serialNumber"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UDivider>
|
||||
Anschaffung
|
||||
</UDivider>
|
||||
<UFormGroup
|
||||
label="Hersteller:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.manufacturer"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Herstellernr.:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.manufacturerNumber"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Lieferant:"
|
||||
>
|
||||
<USelectMenu
|
||||
:options="dataStore.vendors"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
v-model="itemInfo.vendor"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Kaufdatum:"
|
||||
>
|
||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
||||
<UButton
|
||||
icon="i-heroicons-calendar-days-20-solid"
|
||||
:label="itemInfo.purchaseDate ? dayjs(itemInfo.purchaseDate).format('DD.MM.YYYY') : 'Datum auswählen'"
|
||||
variant="outline"
|
||||
/>
|
||||
|
||||
<template #panel="{ close }">
|
||||
<LazyDatePicker v-model="itemInfo.purchaseDate" @close="close" />
|
||||
</template>
|
||||
</UPopover>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Wert bei Kauf:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.purchasePrice"
|
||||
type="number"
|
||||
steps="0.01"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
</template>
|
||||
</UInput>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Aktueller Wert:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.currentValue"
|
||||
type="number"
|
||||
steps="0.01"
|
||||
>
|
||||
<template #trailing>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-xs">EUR</span>
|
||||
</template>
|
||||
</UInput>
|
||||
</UFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<UFormGroup
|
||||
label="Beschreibung:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.description"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
vertical-align: top;
|
||||
padding-bottom: 0.15em;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,117 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Inventar" :badge="filteredRows.length">
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/inventoryitems/create`)">+ Inventarartikel</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/inventoryitems/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Inventarartikel anzuzeigen' }"
|
||||
>
|
||||
|
||||
|
||||
</UTable>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/tasks/create")
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
const items = ref([])
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("inventoryitems","*", "articleNumber")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "articleNumber",
|
||||
label: "Artikelnummer",
|
||||
sortable: true
|
||||
},{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "description",
|
||||
label: "Beschreibung",
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
|
||||
const searchString = ref('')
|
||||
const filteredRows = computed(() => {
|
||||
if(!searchString.value) {
|
||||
return items.value
|
||||
}
|
||||
|
||||
return items.value.filter(product => {
|
||||
return Object.values(product).some((value) => {
|
||||
return String(value).toLowerCase().includes(searchString.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup >
|
||||
|
||||
import {useProfileStore} from "~/stores/profile.js";
|
||||
|
||||
definePageMeta({
|
||||
layout: "notLoggedIn"
|
||||
})
|
||||
@@ -8,8 +10,8 @@ const supabase = useSupabaseClient()
|
||||
const user = useSupabaseUser()
|
||||
const router = useRouter()
|
||||
const colorMode = useColorMode()
|
||||
const dataStore = useDataStore()
|
||||
const toast = useToast()
|
||||
const profileStore = useProfileStore()
|
||||
|
||||
const isLight = computed({
|
||||
get () {
|
||||
@@ -61,7 +63,7 @@ const onSubmit = async (data) => {
|
||||
|
||||
} else {
|
||||
//console.log("Login Successful")
|
||||
dataStore.initializeData(user.id)
|
||||
profileStore.initializeData(user.id)
|
||||
router.push("/")
|
||||
|
||||
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
<script setup>
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import DocumentList from "~/components/DocumentList.vue";
|
||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
||||
import Toolbar from "~/components/Toolbar.vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const supabase = useSupabaseClient()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
|
||||
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({
|
||||
description: {
|
||||
html: ""
|
||||
}
|
||||
})
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
label: "Informationen"
|
||||
},{
|
||||
label: "Projekte"
|
||||
},{
|
||||
label: "Aufgaben"
|
||||
},{
|
||||
label: "Dokumente"
|
||||
}
|
||||
]
|
||||
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
if(mode.value === "show"){
|
||||
itemInfo.value = (await supabase.from("plants").select("*, customer(*)").eq("id",useRoute().params.id).single()).data
|
||||
} else if(mode.value === "edit") {
|
||||
itemInfo.value = (await supabase.from("plants").select().eq("id",useRoute().params.id).single()).data
|
||||
}
|
||||
|
||||
if(mode.value === "create") {
|
||||
let query = route.query
|
||||
if(query.customer) itemInfo.value.customer = Number(query.customer)
|
||||
}
|
||||
}
|
||||
|
||||
const contentChanged = (content) => {
|
||||
itemInfo.value.description.html = content.html
|
||||
itemInfo.value.description.text = content.text
|
||||
itemInfo.value.description.json = content.json
|
||||
}
|
||||
|
||||
setupPage()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Objekt erstellen' : 'Objekt bearbeiten')">
|
||||
<template #left>
|
||||
<UButton
|
||||
icon="i-heroicons-chevron-left"
|
||||
variant="outline"
|
||||
@click="router.push(`/plants`)"
|
||||
>
|
||||
Objekte
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
:class="['text-xl','font-medium']"
|
||||
>{{itemInfo ? `Objekt: ${itemInfo.name}` : (mode === 'create' ? 'Objekt erstellen' : 'Objekt bearbeiten')}}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<ButtonWithConfirm
|
||||
color="rose"
|
||||
variant="outline"
|
||||
@confirmed="dataStore.updateItem('plants',{...itemInfo, archived: true})"
|
||||
v-if="mode === 'edit'"
|
||||
>
|
||||
<template #button>
|
||||
Archivieren
|
||||
</template>
|
||||
<template #header>
|
||||
<span class="text-md text-black font-bold">Archivieren bestätigen</span>
|
||||
</template>
|
||||
Möchten Sie das Objekt {{itemInfo.name}} wirklich archivieren?
|
||||
</ButtonWithConfirm>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('plants',itemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('plants',itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="router.push(itemInfo.id ? `/plants/show/${itemInfo.value.id}` : `/plants/`)"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click="router.push(`/plants/edit/${itemInfo.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UTabs
|
||||
:items="tabItems"
|
||||
v-if="mode === 'show'"
|
||||
class="p-5"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.label === 'Informationen'" class="flex flex-row mt-5">
|
||||
<UCard class="w-1/2 mr-5">
|
||||
<UAlert
|
||||
v-if="itemInfo.archived"
|
||||
color="rose"
|
||||
variant="outline"
|
||||
title="Objekt archiviert"
|
||||
icon="i-heroicons-light-bulb"
|
||||
class="mb-5"
|
||||
/>
|
||||
<div class="text-wrap">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Kunde:</td>
|
||||
<td><nuxt-link v-if="itemInfo.customer" :to="`/customers/show/${itemInfo.customer.id}`">{{itemInfo.customer.name}}</nuxt-link></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="itemInfo.description.html">
|
||||
<p>Notizen:</p>
|
||||
<span v-html="itemInfo.description.html"></span>
|
||||
</div>
|
||||
</UCard>
|
||||
<UCard class="w-1/2">
|
||||
<HistoryDisplay
|
||||
type="plant"
|
||||
v-if="itemInfo.id"
|
||||
:element-id="itemInfo.id"
|
||||
render-headline
|
||||
/>
|
||||
</UCard>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Projekte'">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/projects/create?plant=${itemInfo.id}`)"
|
||||
>
|
||||
+ Projekt
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
|
||||
<UTable
|
||||
:rows="dataStore.getProjectsByPlantId(itemInfo.id)"
|
||||
:columns="[{key: 'name', label: 'Name'}]"
|
||||
@select="(row) => router.push(`/projects/show/${row.id}`)"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Projekte' }"
|
||||
>
|
||||
|
||||
</UTable>
|
||||
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Aufgaben'">
|
||||
<Toolbar>
|
||||
<UButton
|
||||
@click="router.push(`/tasks/create?plant=${itemInfo.id}`)"
|
||||
>
|
||||
+ Aufgabe
|
||||
</UButton>
|
||||
</Toolbar>
|
||||
<UTable
|
||||
:rows="dataStore.getTasksByPlantId(itemInfo.id)"
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine zugehörigen Aufgaben' }"
|
||||
:columns="[{key: 'name', label: 'Name'},{key: 'categore', label: 'Kategorie'}]"
|
||||
@select="(row) => router.push(`/tasks/show/${row.id}`)"
|
||||
>
|
||||
|
||||
</UTable>
|
||||
</div>
|
||||
<div v-else-if="item.label === 'Dokumente'" class="space-y-3">
|
||||
<Toolbar>
|
||||
<DocumentUpload
|
||||
type="plant"
|
||||
:element-id="itemInfo.id"
|
||||
/>
|
||||
</Toolbar>
|
||||
|
||||
<DocumentList :documents="dataStore.getDocumentsByPlantId(itemInfo.id)"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</UTabs>
|
||||
|
||||
|
||||
<UForm
|
||||
v-else-if="mode === 'edit' || mode === 'create'"
|
||||
class="p-5"
|
||||
>
|
||||
<UFormGroup
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup
|
||||
label="Kundennummer:"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="itemInfo.customer"
|
||||
:options="dataStore.customers"
|
||||
option-attribute="name"
|
||||
value-attribute="id"
|
||||
searchable
|
||||
:search-attributes="['name']"
|
||||
>
|
||||
<template #label>
|
||||
{{dataStore.customers.find(customer => customer.id === itemInfo.customer) ? dataStore.customers.find(customer => customer.id === itemInfo.customer).name : "Kunde auswählen"}}
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Notizen:"
|
||||
>
|
||||
<Tiptap
|
||||
v-if="itemInfo.description"
|
||||
@updateContent="contentChanged"
|
||||
:preloadedContent="itemInfo.description.html"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,139 +0,0 @@
|
||||
<template>
|
||||
<UDashboardNavbar title="Objekte" :badge="filteredRows.length" >
|
||||
<template #right>
|
||||
<UInput
|
||||
id="searchinput"
|
||||
v-model="searchString"
|
||||
icon="i-heroicons-funnel"
|
||||
autocomplete="off"
|
||||
placeholder="Suche..."
|
||||
class="hidden lg:block"
|
||||
@keydown.esc="$event.target.blur()"
|
||||
>
|
||||
<template #trailing>
|
||||
<UKbd value="/" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<UButton @click="router.push(`/plants/create`)">+ Objekt</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #left>
|
||||
|
||||
|
||||
<UCheckbox
|
||||
label="Erledigte Anzeigen"
|
||||
v-model="showDone"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<USelectMenu
|
||||
v-model="selectedColumns"
|
||||
icon="i-heroicons-adjustments-horizontal-solid"
|
||||
:options="templateColumns"
|
||||
multiple
|
||||
class="hidden lg:block"
|
||||
by="key"
|
||||
>
|
||||
<template #label>
|
||||
Spalten
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
|
||||
<UTable
|
||||
:rows="filteredRows"
|
||||
:columns="columns"
|
||||
class="w-full"
|
||||
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
|
||||
@select="(i) => router.push(`/plants/show/${i.id}`) "
|
||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Objekte anzuzeigen' }"
|
||||
>
|
||||
<template #customer-data="{row}">
|
||||
{{row.customer ? row.customer.name : ""}}
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {useListFilter} from "~/composables/useSearch.js";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'/': () => {
|
||||
//console.log(searchinput)
|
||||
//searchinput.value.focus()
|
||||
document.getElementById("searchinput").focus()
|
||||
},
|
||||
'+': () => {
|
||||
router.push("/plants/create")
|
||||
},
|
||||
'Enter': {
|
||||
usingInput: true,
|
||||
handler: () => {
|
||||
router.push(`/plants/show/${filteredRows.value[selectedItem.value].id}`)
|
||||
}
|
||||
},
|
||||
'arrowdown': () => {
|
||||
if(selectedItem.value < filteredRows.value.length - 1) {
|
||||
selectedItem.value += 1
|
||||
} else {
|
||||
selectedItem.value = 0
|
||||
}
|
||||
},
|
||||
'arrowup': () => {
|
||||
if(selectedItem.value === 0) {
|
||||
selectedItem.value = filteredRows.value.length - 1
|
||||
} else {
|
||||
selectedItem.value -= 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const router = useRouter()
|
||||
|
||||
const items = ref([])
|
||||
const selectedItem = ref(0)
|
||||
|
||||
const setupPage = async () => {
|
||||
items.value = await useSupabaseSelect("plants","*, customer(id,name)")
|
||||
}
|
||||
|
||||
setupPage()
|
||||
|
||||
const templateColumns = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
sortable: true
|
||||
},{
|
||||
key: "customer",
|
||||
label: "Kunde",
|
||||
sortable: true
|
||||
}
|
||||
]
|
||||
const selectedColumns = ref(templateColumns)
|
||||
const columns = computed(() => templateColumns.filter((column) => selectedColumns.value.includes(column)))
|
||||
|
||||
|
||||
const searchString = ref('')
|
||||
|
||||
const filteredRows = computed(() => {
|
||||
return useListFilter(searchString.value, items.value)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,179 +0,0 @@
|
||||
<script setup>
|
||||
import HistoryDisplay from "~/components/HistoryDisplay.vue";
|
||||
import DocumentList from "~/components/DocumentList.vue";
|
||||
import DocumentUpload from "~/components/DocumentUpload.vue";
|
||||
import {useSupabaseSelect} from "~/composables/useSupabase.js";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
definePageMeta({
|
||||
middleware: "auth"
|
||||
})
|
||||
|
||||
defineShortcuts({
|
||||
'backspace': () => {
|
||||
router.push("/productcategories")
|
||||
},
|
||||
'arrowleft': () => {
|
||||
if(openTab.value > 0){
|
||||
openTab.value -= 1
|
||||
}
|
||||
},
|
||||
'arrowright': () => {
|
||||
if(openTab.value < 3) {
|
||||
openTab.value += 1
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const id = ref(route.params.id ? route.params.id : null )
|
||||
|
||||
//Working
|
||||
const mode = ref(route.params.mode || "show")
|
||||
const itemInfo = ref({})
|
||||
const openTab = ref(0)
|
||||
|
||||
//Functions
|
||||
const setupPage = async () => {
|
||||
if(mode.value === "show" || mode.value === "edit"){
|
||||
itemInfo.value = await useSupabaseSelectSingle("productcategories",route.params.id,"*")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const cancelEditorCreate = () => {
|
||||
if(itemInfo.value) {
|
||||
router.push(`/productcategories/show/${itemInfo.value.id}`)
|
||||
} else {
|
||||
router.push(`/productcategories/`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
setupPage()
|
||||
</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(`/productcategories`)"
|
||||
>
|
||||
Artikelkategorien
|
||||
</UButton>
|
||||
</template>
|
||||
<template #center>
|
||||
<h1
|
||||
v-if="itemInfo"
|
||||
class="text-xl font-medium"
|
||||
>{{itemInfo.name ? `Artikelkategorie: ${itemInfo.name}` : (mode === 'create' ? 'Artikelkategorie erstellen' : 'Artikelkategorie bearbeiten')}}</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="mode === 'edit'"
|
||||
@click="dataStore.updateItem('productcategories',itemInfo)"
|
||||
>
|
||||
Speichern
|
||||
</UButton>
|
||||
<UButton
|
||||
v-else-if="mode === 'create'"
|
||||
@click="dataStore.createNewItem('productcategories',itemInfo)"
|
||||
>
|
||||
Erstellen
|
||||
</UButton>
|
||||
<UButton
|
||||
@click="cancelEditorCreate"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
>
|
||||
Abbrechen
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="mode === 'show'"
|
||||
@click=" router.push(`/productcategories/edit/${itemInfo.id}`)"
|
||||
>
|
||||
Bearbeiten
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
<UTabs
|
||||
:items="[{label: 'Informationen'}]"
|
||||
v-if="mode === 'show' && itemInfo"
|
||||
class="p-5"
|
||||
v-model="openTab"
|
||||
>
|
||||
<template #item="{item}">
|
||||
<div v-if="item.label === 'Informationen'" class="mr-5 flex flex-row">
|
||||
<div class="w-1/2 mr-5">
|
||||
<UCard>
|
||||
<table class="w-full">
|
||||
<tr>
|
||||
<td>Name: </td>
|
||||
<td>{{itemInfo.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Beschreibung:</td>
|
||||
<td>{{itemInfo.description}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</UCard>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<UCard>
|
||||
<HistoryDisplay
|
||||
type="product"
|
||||
v-if="itemInfo"
|
||||
:element-id="itemInfo.id"
|
||||
render-headline
|
||||
/>
|
||||
</UCard>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
</UTabs>
|
||||
<UForm
|
||||
v-else-if="mode == 'edit' || mode == 'create'"
|
||||
class="p-5"
|
||||
>
|
||||
<UFormGroup
|
||||
label="Name:"
|
||||
>
|
||||
<UInput
|
||||
v-model="itemInfo.name"
|
||||
autofocus
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
label="Beschreibung:"
|
||||
>
|
||||
<UTextarea
|
||||
v-model="itemInfo.description"
|
||||
/>
|
||||
</UFormGroup>
|
||||
</UForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
td {
|
||||
border-bottom: 1px solid lightgrey;
|
||||
vertical-align: top;
|
||||
padding-bottom: 0.15em;
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user